5

前端必要懂的,完整的 HTTP cookie 指南

 2 years ago
source link: https://segmentfault.com/a/1190000040964954
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者:valentinog
译者:前端小智
来源:valentinog

有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

我们正在招聘,年薪:25-60万+奖金,有兴趣的可以点我了解详情。

Web 开发中的 cookie 是什么?

cookie 是后端可以存储在用户浏览器中的小块数据。 Cookie 最常见用例包括用户跟踪,个性化以及身份验证。

Cookies 具有很多隐私问题,多年来一直受到严格的监管。

在本文中,主要侧重于技术方面:学习如何在前端和后端创建,使用 HTTP cookie。

后端示例是Flask编写的。如果你想跟着学习,可以创建一个新的Python虚拟环境,移动到其中并安装Flask

mkdir cookies && cd $_

python3 -m venv venv
source venv/bin/activate

pip install Flask

在项目文件夹中创建一个名为flask app.py的新文件,并使用本文的示例在本地进行实验。

谁创建 cookies ?

首先,cookies 从何而来? 谁创建 cookies ?

虽然可以使用document.cookie在浏览器中创建 cookie,但大多数情况下,后端的责任是在将响应客户端请求之前在请求中设置 cookie。

后端是指可以通过以下方式创建 Cookie:

  • 后端实际应用程序的代码(Python、JavaScript、PHP、Java)
  • 响应请求的Web服务器(Nginx,Apache)

后端可以在 HTTP 请求求中 Set-Cookie 属性来设置 cookie,它是由键/值对以及可选属性组成的相应字符串:

Set-Cookie: myfirstcookie=somecookievalue

什么时候需要创建 cookie? 这取决于需求。

cookie 是简单的字符串。在项目文件夹中创建一个名为flask_app.py的Python文件,并输入以下内容:

from flask import Flask, make_response

app = Flask(__name__)


@app.route("/index/", methods=["GET"])
def index():
    response = make_response("Here, take some cookie!")
    response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"
    return response

然后运行应用程序:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

当该应用程序运行时,用户访问http://127.0.0.1:5000/index/,后端将设置一个具有键/值对的名为Set-Cookie的响应标头。

127.0.0.1:5000是开发中的 Flask 应用程序的默认侦听地址/端口)。

Set-Cookie标头是了解如何创建cookie的关键:

response.headers["Set-Cookie"] = "myfirstcookie=somecookievalue"

大多数框架都有自己设置 cookie 的方法,比如Flask的set_cookie()

如何查看 cookies ?

访问http://127.0.0.1:5000/index/后,后端将在浏览器中设置cookie。 要查看此cookie,可以从浏览器的控制台调用document.cookie

或者可以在开发人员工具中选中Storage选项卡。单击cookie,会看到 cookie 具体的内容:

在命令行上,还可以使用curl查看后端设置了哪些 cookie

curl -I http://127.0.0.1:5000/index/

可以将 Cookie 保存到文件中以供以后使用:

curl -I http://127.0.0.1:5000/index/ --cookie-jar mycookies

在 stdout 上显示 cookie:

curl -I http://127.0.0.1:5000/index/ --cookie-jar -

请注意,没有HttpOnly属性的cookie,在浏览器中可以使用document.cookie上访问,如果设置了 HttpOnly 属性,document.cookie就读取不到。

Set-Cookie: myfirstcookie=somecookievalue; HttpOnly

现在,该cookie 仍将出现在 Storage 选项卡中,但是 document.cookie返回的是一个空字符串。

从现在开始,为方便起见,使用Flask的 response.set_cookie() 在后端上创建 cookie。

我有一个 cookie,现在怎么办?

你的浏览器得到一个 cookie。现在怎么办呢?一旦有了 cookie,浏览器就可以将cookie发送回后端。

这有许多用途发如:用户跟踪、个性化,以及最重要的身份验证。

例如,一旦你登录网站,后端就会给你一个cookie:

Set-Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r

为了在每个后续请求中正确识别 我们的身份,后端会检查来自请求中浏览器的 cookie

要发送Cookie,浏览器会在请求中附加一个Cookie标头:

Cookie: userid=sup3r4n0m-us3r-1d3nt1f13r

cookie 可以设置过期时间: Max-Age 和 expires

默认情况下,cookie 在用户关闭会话时即关闭浏览器时过期。要持久化cookie,我们可以通过expiresMax-Age属性

Set-Cookie: myfirstcookie=somecookievalue; expires=Tue, 09 Jun 2020 15:46:52 GMT; Max-Age=1209600

注意:Max-Age优先于expires

cookie的作用域是网站路径: path 属性

考虑该后端,该后端在访问http://127.0.0.1:5000/时为其前端设置了一个新的 cookie。 相反,在其他两条路径上,我们打印请求的cookie

from flask import Flask, make_response, request

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d", path="/about/")
    return response


@app.route("/about/", methods=["GET"])
def about():
    print(request.cookies)
    return "Hello world!"


@app.route("/contact/", methods=["GET"])
def contact():
    print(request.cookies)
    return "Hello world!"

运行该应用程序:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

在另一个终端中,如果我们与根路由建立连接,则可以在Set-Cookie中看到cookie:

curl -I http://127.0.0.1:5000/ --cookie-jar cookies

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 23
Set-Cookie: id=3db4adj3d; Path=/about/
Server: Werkzeug/1.0.1 Python/3.8.3
Date: Wed, 27 May 2020 09:21:37 GMT

请注意,此时 cookie 具有Path属性:

Set-Cookie: id=3db4adj3d; Path=/about/

/about/ 路由并保存 cookit

curl -I http://127.0.0.1:5000/about/ --cookie cookies

在 Flask 应用程序的终端中运行如下命令,可以看到:

ImmutableMultiDict([('id', '3db4adj3d')])
127.0.0.1 - - [27/May/2020 11:27:55] "HEAD /about/ HTTP/1.1" 200 -

正如预期的那样,cookie 返回到后端。 现在尝试访问 /contact/ 路由:

url -I http://127.0.0.1:5000/contact/ --cookie cookies

在 Flask 应用程序的终端中运行如下命令,可以看到:

ImmutableMultiDict([])
127.0.0.1 - - [27/May/2020 11:29:00] "HEAD /contact/ HTTP/1.1" 200 -

这说明啥?cookie 的作用域是Path 。具有给定路径属性的cookie不能被发送到另一个不相关的路径,即使这两个路径位于同一域中。

这是cookie权限的第一层。

在cookie创建过程中省略Path时,浏览器默认为/

cookie 的作用域是域名: domain 属性

cookie 的 Domain 属性的值控制浏览器是否应该接受cookie以及cookie返回的位置。

让我们看一些例子。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

主机不匹配(错误的主机)

查看 https://serene-bastion-01422.herokuapp.com/get-wrong-domain-cookie/设置的cookie:

Set-Cookie: coookiename=wr0ng-d0m41n-c00k13; Domain=api.valentinog.com

这里的 cookie 来自serene-bastion-01422.herokuapp.com,但是Domain属性具有api.valentinog.com

浏览器没有其他选择来拒绝这个 cookie。比如 Chrome 会给出一个警告(Firefox没有)

主机不匹配(子域名)

查看 https://serene-bastion-01422.herokuapp.com/get-wrong-subdomain-cookie/设置的cookie:

Set-Cookie: coookiename=wr0ng-subd0m41n-c00k13; Domain=secure-brushlands-44802.herokuapp.com

这里的 Cookie 来自serene-bastion-01422.herokuapp.com,但“Domain”属性是secure-brushlands-44802.herokuapp.com

它们在相同的域上,但是子域名不同。 同样,浏览器也拒绝此cookie:

主机匹配(整个域)

查看 https://www.valentinog.com/get-domain-cookie.html设置的cookie:

set-cookie: cookiename=d0m41n-c00k13; Domain=valentinog.com

此cookie是使用 Nginx add_header在Web服务器上设置的:

add_header Set-Cookie "cookiename=d0m41n-c00k13; Domain=valentinog.com";

这里使用 Nginx 中设置cookie的多种方法。 Cookie 是由 Web 服务器或应用程序的代码设置的,对于浏览器来说无关紧要。

重要的是 cookie 来自哪个域。

在此浏览器将愉快地接受cookie,因为Domain中的主机包括cookie所来自的主机。

换句话说,valentinog.com包括子域名www.valentinog.com

同时,对valentinog.com的新请求,cookie 都会携带着,以及任何对valentinog.com子域名的请求。

这是一个附加了Cookie的 www 子域请求:

下面是对另一个自动附加cookie的子域的请求

Cookies 和公共后缀列表

查看 https://serene-bastion-01422.herokuapp.com/get-domain-cookie/:设置的 cookie:

Set-Cookie: coookiename=d0m41n-c00k13; Domain=herokuapp.com

这里的 cookie 来自serene-bas-01422.herokuapp.comDomain 属性是herokuapp.com。浏览器在这里应该做什么

你可能认为serene-base-01422.herokuapp.com包含在herokuapp.com域中,因此浏览器应该接受cookie。

相反,它拒绝 cookie,因为它来自公共后缀列表中包含的域。

Public Suffix List(公共后缀列表)。此列表列举了顶级域名和开放注册的域名。浏览器禁止此列表上的域名被子域名写入Cookie。

主机匹配(子域)

查看 https://serene-bastion-01422.herokuapp.com/get-subdomain-cookie/:设置的 cookie:

Set-Cookie: coookiename=subd0m41n-c00k13

当域在cookie创建期间被省略时,浏览器会默认在地址栏中显示原始主机,在这种情况下,我的代码会这样做:

response.set_cookie(key="coookiename", value="subd0m41n-c00k13")

当 Cookie 进入浏览器的 Cookie 存储区时,我们看到已应用Domain

现在,我们有来自serene-bastion-01422.herokuapp.com 的 cookie, 那 cookie 现在应该送到哪里?

如果你访问https://serene-bastion-01422.herokuapp.com/,则 cookie 随请求一起出现:

但是,如果访问herokuapp.com,则 cookie 不会随请求一起出现:

概括地说,浏览器使用以下启发式规则来决定如何处理cookies(这里的发送者主机指的是你访问的实际网址):

  • 如果“Domain”中的域或子域与访问的主机不匹配,则完全拒绝 Cookie
  • 如果 Domain 的值包含在公共后缀列表中,则拒绝 cookie
  • 如果Domain 中的域或子域与访问在主机匹配,则接受 Cookie

一旦浏览器接受了cookie,并且即将发出请求,它就会说:

  • 如果请求主机与我在Domain中看到的值完全匹配,刚会回传 cookie
  • 如果请求主机是与我在“Domain”中看到的值完全匹配的子域,则将回传 cookie
  • 如果请求主机是sub.example.dev之类的子域,包含在example.dev之类的 Domain 中,则将回传 cookie
  • 如果请求主机是例如example.dev之类的主域,而 Domain 是sub.example.dev之类,则不会回传cookie。

Domain 和 Path 属性一直是 cookie 权限的第二层。

Cookies可以通过AJAX请求传递

Cookies 可以通过AJAX请求传播。 AJAX 请求是使用 JS (XMLHttpRequest或Fetch)进行的异步HTTP请求,用于获取数据并将其发送回后端。

考虑 Flask的另一个示例,其中有一个模板,该模板又会加载 JS 文件:

from flask import Flask, make_response, render_template

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")


@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d")
    return response

以下是 templates/index.html 模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button>FETCH</button>
</body>
<script src="{{ url_for('static', filename='index.js') }}"></script>
</html>

下面是 static/index.js 的内容:

const button = document.getElementsByTagName("button")[0];

button.addEventListener("click", function() {
  getACookie();
});

function getACookie() {
  fetch("/get-cookie/")
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.text();
    })
    .then(text => console.log(text));
}

当访问http://127.0.0.1:5000/时,我们会看到一个按钮。 通过单击按钮,我们向/get-cookie/发出获取请求并获取Cookie。 正如预期的那样,cookie 落在浏览器的 Cookie storage中。

对 Flask 应用程序进行一些更改,多加一个路由:

from flask import Flask, make_response, request, render_template, jsonify

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")


@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d")
    return response


@app.route("/api/cities/", methods=["GET"])
def cities():
    if request.cookies["id"] == "3db4adj3d":
        cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
        return jsonify(cities)
    return jsonify(msg="Ops!")

另外,调整一下 JS 代码,用于下请求刚新增的路由:

const button = document.getElementsByTagName("button")[0];

button.addEventListener("click", function() {
  getACookie().then(() => getData());
});

function getACookie() {
  return fetch("/get-cookie/").then(response => {
    // make sure to check response.ok in the real world!
    return Promise.resolve("All good, fetch the data");
  });
}

function getData() {
  fetch("/api/cities/")
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.json();
    })
    .then(json => console.log(json));

当访问http://127.0.0.1:5000/时,我们会看到一个按钮。 通过单击按钮,我们向/get-cookie/发出获取请求以获取Cookie。 Cookie出现后,我们就会对/api/cities/再次发出Fetch请求。

在浏览器的控制台中,可以看到请求回来 的数据。另外,在开发者工具的Network选项卡中,可以看到一个名为Cookie的头,这是通过AJAX请求传给后端。

只要前端与后端在同一上下文中,在前端和后端之间来回交换cookie就可以正常工作:我们说它们来自同一源。

这是因为默认情况下,Fetch 仅在请求到达触发请求的来源时才发送凭据,即 Cookie

cookie 不能总是通过AJAX请求传递

考虑另一种情况,在后端独立运行,可以这样启动应用程序:

FLASK_ENV=development FLASK_APP=flask_app.py flask run

现在,在 Flask 应用程序之外的其他文件夹中,创建index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button>FETCH</button>
</body>
<script src="index.js"></script>
</html>

使用以下代码在同一文件夹中创建一个名为index.js的 JS 文件:

button.addEventListener("click", function() {
  getACookie().then(() => getData());
});

function getACookie() {
  return fetch("http://localhost:5000/get-cookie/").then(response => {
    // make sure to check response.ok in the real world!
    return Promise.resolve("All good, fetch the data");
  });
}

function getData() {
  fetch("http://localhost:5000/api/cities/")
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.json();
    })
    .then(json => console.log(json));
}

在同一文件夹中,从终端运行:

npx serve

此命令为您提供了要连接的本地地址/端口,例如http://localhost:42091/。 访问页面并尝试在浏览器控制台打开的情况下单击按钮。 在控制台中,可以看到:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)

因为 http://localhost:5000/http://localhost:42091/.不同。 它们是不同的域,因此会 CORS 的限制。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

处理 CORS

CORS 是一个 W3C 标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。

整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

默认情况下,除非服务器设置了Access-Control-Allow-Origin的特定HTTP标头,否则浏览器将阻止AJAX对非相同来源的远程资源的请求。

要解决此第一个错误,我们需要为Flask配置CORS:

pip install flask-cors

然后将 CORS 应用于 Flask:

from flask import Flask, make_response, request, render_template, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app=app)


@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")


@app.route("/get-cookie/", methods=["GET"])
def get_cookie():
    response = make_response("Here, take some cookie!")
    response.set_cookie(key="id", value="3db4adj3d")
    return response


@app.route("/api/cities/", methods=["GET"])
def cities():
    if request.cookies["id"] == "3db4adj3d":
        cities = [{"name": "Rome", "id": 1}, {"name": "Siena", "id": 2}]
        return jsonify(cities)
    return jsonify(msg="Ops!")

现在尝试在浏览器控制台打开的情况下再次单击按钮。在控制台中你应该看到

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/api/cities/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)

尽管我们犯了同样的错误,但这次的罪魁祸首是第二个路由。

你可以通过查看 “Network” 标签中的请求来确认,没有发送此类Cookie:

为了在不同来源的Fetch请求中包含cookie,我们必须提credentials 标志(默认情况下,它是相同来源)。

如果没有这个标志,Fetch 就会忽略 cookie,可以这样修复:

const button = document.getElementsByTagName("button")[0];

button.addEventListener("click", function() {
  getACookie().then(() => getData());
});

function getACookie() {
  return fetch("http://localhost:5000/get-cookie/", {
    credentials: "include"
  }).then(response => {
    // make sure to check response.ok in the real world!
    return Promise.resolve("All good, fetch the data");
  });
}

function getData() {
  fetch("http://localhost:5000/api/cities/", {
    credentials: "include"
  })
    .then(response => {
      // make sure to check response.ok in the real world!
      return response.json();
    })
    .then(json => console.log(json));
}

credentials: "include" 必须在第一个 Fetch 请求中出现,才能将Cookie保存在浏览器的Cookie storage 中:

fetch("http://localhost:5000/get-cookie/", {
    credentials: "include"
  })

它还必须在第二个请求时出现,以允许将cookie传输回后端

  fetch("http://localhost:5000/api/cities/", {
    credentials: "include"
  })

再试一次,我们还需要在后端修复另一个错误:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/get-cookie/. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).

为了允许在CORS请求中传输cookie,后端还需要设置 Access-Control-Allow-Credentials标头。

CORS(app=app, supports_credentials=True)

要点:为了使Cookie在不同来源之间通过AJAX请求传递,可以这样做:

  • credentials: "include" 用于前端的 fetch 请求中
  • Access-Control-Allow-CredentialsAccess-Control-Allow-Origin 用于后端

cookie可以通过AJAX请求传递,但是它们必须遵守我们前面描述的域规则。

Cookie 的 Secure 属性

Secure 属性是说如果一个 cookie 被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用 http 协议是不发送的。换句话说,cookie 是在https的情况下创建的,而且他的Secure=true,那么之后你一直用https访问其他的页面(比如登录之后点击其他子页面),cookie会被发送到服务器,你无需重新登录就可以跳转到其他页面。但是如果这是你把url改成http协议访问其他页面,你就需要重新登录了,因为这个cookie不能在http协议中发送。

可以这样设置 Secure 属性

response.set_cookie(key="id", value="3db4adj3d", secure=True)

如果要在真实环境中尝试,请可以运行以下命令,并注意curl在此处是不通过HTTP保存cookie:

curl -I http://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -

相反,通过HTTPS,cookie 出现在cookie jar中:

curl -I https://serene-bastion-01422.herokuapp.com/get-secure-cookie/ --cookie-jar -

cookie jar 文件:

serene-bastion-01422.herokuapp.com      FALSE   /       TRUE    0

不要被Secure欺骗:浏览器通过HTTPS接受cookie,但是一旦cookie进入浏览器,就没有任何保护。

因为带有 Secure 的 Cookie 一般也不用于传输敏感数据.

Cookie 的 HttpOnly 属性

如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。

XSS 全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。

如果有设置 HttpOnly 看起来是这样的:

Set-Cookie: "id=3db4adj3d; HttpOnly"

在 Flask 中

response.set_cookie(key="id", value="3db4adj3d", httponly=True)

这样,cookie 设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息。如果在控制台中进行检查,则document.cookie将返回一个空字符串。

何时使用HttpOnly? cookie 应该始终是HttpOnly的,除非有特定的要求将它们暴露给运行时 JS。

可怕的 SameSite 属性

first-party cookie 和 third-party cookie

查看https://serene-bastion-01422.herokuapp.com/get-cookie/ 中所携带的 Cookie

Set-Cookie: simplecookiename=c00l-c00k13; Path=/

first-party是指你登录或使用的网站所发行的 cookie,而third-party cookie 常为一些广告网站,有侵犯隐私以及安全隐患。

我们将这类 Cookie 称为 first-party。 也就是说,我在浏览器中访问该URL,并且如果我访问相同的URL或该站点的另一个路径(假设Path为/),则浏览器会将cookie发送回该网站。

现在考虑在https://serene-bastion-01422.herokuapp.com/get-frog/上的另一个网页。 该页面设置了一个cookie,此外,它还从https://www.valentinog.com/cookie-frog.jpg托管的远程资源中加载图像。

该远程资源又会自行设置一个cookie:

我们将这种 cookie 称为third-party(第三方) Cookie。

第三方 Cookie 除了用于 CSRF 攻击,还可以用于用户追踪。比如,Facebook 在第三方网站插入一张看不见的图片。

![](facebook.com)

浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。

使用 SameSite 属性

Cookie 的SameSite 属性用来限制third-party Cookie,从而减少安全风险。它可以设置三个值。

  • Strict

Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。

Set-Cookie: CookieName=CookieValue; SameSite=Strict;

这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。

Set-Cookie: CookieName=CookieValue; SameSite=Lax;

导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。

设置了StrictLax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。

Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

下面的设置无效。

Set-Cookie: widget_session=abc123; SameSite=None

下面的设置有效。

Set-Cookie: widget_session=abc123; SameSite=None; Secure

Cookies 和 认证

身份验证是 web 开发中最具挑战性的任务之一。关于这个主题似乎有很多困惑,因为JWT中的基于令牌的身份验证似乎要取代“旧的”、可靠的模式,如基于会话的身份验证。

来看看 cookie 在这里扮演什么角色。

基于会话的身份验证

身份验证是 cookie 最常见的用例之一。

当你访问一个请求身份验证的网站时,后端将通过凭据提交(例如通过表单)在后台发送一个Set-Cookie标头到前端。

型的会话 cookie 如下所示:

Set-Cookie: sessionid=sty1z3kz11mpqxjv648mqwlx4ginpt6c; expires=Tue, 09 Jun 2020 15:46:52 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax

这个Set-Cookie头中,服务器可以包括一个名为sessionsession id或类似的cookie

这是浏览器可以清楚看到的唯一标识符。 每当通过身份验证的用户向后端请求新页面时,浏览器就会发回会话cookie

基于会话的身份验证是有状态的,因为后端必须跟踪每个用户的会话。这些会话的存储可能是:

  • 像 Redis 这样的键/值存储

在这三个会话存储中,Redis 之类应优先于数据库或文件系统。

请注意,基于会话的身份验证与浏览器的会话存储无关。

之所以称为基于会话的会话,是因为用于用户识别的相关数据存在于后端的会话存储中,这与浏览器的会话存储不同。

何时使用基于会话的身份验证

只要能使用就使用它。基于会话的身份验证是一种最简单、安全、直接的网站身份验证形式。默认情况下,它可以在Django等所有流行的web框架上使用。

但是,它的状态特性也是它的主要缺点,特别是当网站是由负载均衡器提供服务时。在这种情况下,像粘贴会话,或者在集中的Redis存储上存储会话这样的技术会有所帮助。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

关于 JWT 的说明

JWTJSON Web Tokens的缩写,是一种身份验证机制,近年来越来越流行。

JWT 非常适合单页和移动应用程序,但它带来了一系列新挑战。 想要针对API进行身份验证的前端应用程序的典型流程如下:

  • 前端将凭证发送到后端
  • 后端检查凭证并发回令牌
  • 前端在每个后续请求上带上该令牌

这种方法带来的主要问题是:为了使用户保持登录状态,我将该令牌存储在前端的哪个地方?

对于前端开发来说,最自然的事情是将令牌保存在localStorage中。 由于许多原因,这很糟糕。

localStorage很容易从 JS 代码访问,而且它很容易成为XSS攻击的目标。

为了解决此问题,大多数开发人员都将JWT令牌保存在cookie中,以为HttpOnly和Secure可以保护cookie,至少可以免受XSS攻击。

SameSite 设置为 strict 就可以完全保护 JWT免受CSRF攻击

设置为SameSite = Strict的新SameSite属性还将保护您的“熟化” JWT免受CSRF攻击。 但是,由于SameSite = Strict不会在跨域请求上发送cookie,因此,这也完全使JWT的用例无效。

SameSite=Lax呢? 此模式允许使用安全的HTTP方法(即GET,HEAD,OPTIONS和TRACE)将 cookie发送回去。 POST 请求不会以任何一种方式传输 cookie。

实际上,将JWT标记存储在cookielocalStorage中都不是好主意。

如果你确实要使用JWT而不是坚持使用基于会话的身份验证并扩展会话存储,则可能要使用带有刷新令牌的JWT来保持用户登录。

自1994年以来,HTTP cookie一直存在,它们无处不在。

Cookies是简单的文本字符串,但可以通过DomainPath对其权限进行控制,具有Secure的Cookie,只能通过 HTTP S进行传输,而可以使用 HttpOnly从 JS隐藏。

但是,对于所有预期的用途,cookie都可能使用户暴露于攻击和漏洞之中。

浏览器的供应商和Internet工程任务组(Internet Engineering Task Force)年复一年地致力于提高cookie的安全性,最近的一步是SameSite

那么,什么才算是比较安全cookie? ,如下几点:

  • 仅使用 HTTPS
  • 尽可能带有 HttpOnly 属性
  • 正确的SameSite配置
  • 不携带敏感数据

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://gizmodo.com/the-compl...

有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588... 已收录,有一线大厂面试完整考点、资料以及我的系列文章。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK