You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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));
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));
}
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 接口,就可以跨域通信。
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)
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’).
Web 开发中的 cookie 是什么?
cookie 是后端可以存储在用户浏览器中的小块数据。 Cookie 最常见用例包括用户跟踪,个性化以及身份验证。
Cookies 具有很多隐私问题,多年来一直受到严格的监管。
在本文中,主要侧重于技术方面:学习如何在前端和后端创建,使用 HTTP cookie。
后端配置
后端示例是Flask编写的。如果你想跟着学习,可以创建一个新的Python虚拟环境,移动到其中并安装Flask
在项目文件夹中创建一个名
为flask app.py
的新文件,并使用本文的示例在本地进行实验。谁创建 cookies ?
首先,cookies 从何而来? 谁创建 cookies ?
虽然可以使用
document.cookie
在浏览器中创建 cookie,但大多数情况下,后端的责任是在将响应客户端请求之前在请求中设置 cookie。后端是指可以通过以下方式创建 Cookie:
后端实际应用程序的代码(Python、JavaScript、PHP、Java)
响应请求的Web服务器(Nginx,Apache)
后端可以在 HTTP 请求求中 Set-Cookie 属性来设置 cookie,它是由键/值对以及可选属性组成的相应字符串:
什么时候需要创建 cookie? 这取决于需求。
cookie 是简单的字符串。在项目文件夹中创建一个名为
flask_app.py
的Python文件,并输入以下内容:然后运行应用程序:
当该应用程序运行时,用户访问
http://127.0.0.1:5000/index/
,后端将设置一个具有键/值对的名为Set-Cookie
的响应标头。(
127.0.0.1:5000
是开发中的 Flask 应用程序的默认侦听地址/端口)。Set-Cookie
标头是了解如何创建cookie的关键:大多数框架都有自己设置 cookie 的方法,比如Flask的
set_cookie()
。如何查看 cookies ?
访问
http://127.0.0.1:5000/index/
后,后端将在浏览器中设置cookie。 要查看此cookie,可以从浏览器的控制台调用document.cookie
:或者可以在开发人员工具中选中
Storage
选项卡。单击cookie,会看到 cookie 具体的内容:在命令行上,还可以使用
curl
查看后端设置了哪些 cookie可以将 Cookie 保存到文件中以供以后使用:
在 stdout 上显示 cookie:
请注意,没有
HttpOnly
属性的cookie
,在浏览器中可以使用document.cookie
上访问,如果设置了HttpOnly
属性,document.cookie
就读取不到。现在,该cookie 仍将出现在
Storage
选项卡中,但是document.cookie
返回的是一个空字符串。从现在开始,为方便起见,使用Flask的
response.set_cookie()
在后端上创建 cookie。我有一个 cookie,现在怎么办?
你的浏览器得到一个 cookie。现在怎么办呢?一旦有了 cookie,浏览器就可以将cookie发送回后端。
这有许多用途发如:用户跟踪、个性化,以及最重要的身份验证。
例如,一旦你登录网站,后端就会给你一个cookie:
为了在每个后续请求中正确识别 我们的身份,后端会检查来自请求中浏览器的 cookie
要发送Cookie,浏览器会在请求中附加一个
Cookie
标头:cookie 可以设置过期时间: Max-Age 和 expires
默认情况下,cookie 在用户关闭会话时即关闭浏览器时过期。要持久化cookie,我们可以通过
expires
或Max-Age
属性注意:Max-Age优先于expires。
cookie的作用域是网站路径: path 属性
考虑该后端,该后端在访问
http://127.0.0.1:5000/
时为其前端设置了一个新的 cookie。 相反,在其他两条路径上,我们打印请求的cookie
:运行该应用程序:
在另一个终端中,如果我们与根路由建立连接,则可以
在Set-Cookie
中看到cookie:请注意,此时 cookie 具有
Path
属性:/about/
路由并保存 cookit在 Flask 应用程序的终端中运行如下命令,可以看到:
正如预期的那样,cookie 返回到后端。 现在尝试访问
/contact/
路由:在 Flask 应用程序的终端中运行如下命令,可以看到:
这说明啥?cookie 的作用域是
Path
。具有给定路径属性的cookie不能被发送到另一个不相关的路径,即使这两个路径位于同一域中。这是cookie权限的第一层。
在cookie创建过程中省略
Path
时,浏览器默认为/
。cookie 的作用域是域名: domain 属性
cookie 的
Domain
属性的值控制浏览器是否应该接受cookie以及cookie返回的位置。让我们看一些例子。
主机不匹配(错误的主机)
查看
https://serene-bastion-01422.herokuapp.com/get-wrong-domain-cookie/
设置的cookie:这里的 cookie 来自serene-bastion-01422.herokuapp.com,但是
Domain
属性具有api.valentinog.com。浏览器没有其他选择来拒绝这个 cookie。比如 Chrome 会给出一个警告(Firefox没有)
主机不匹配(子域名)
查看
https://serene-bastion-01422.herokuapp.com/get-wrong-subdomain-cookie/
设置的cookie:这里的 Cookie 来自
serene-bastion-01422.herokuapp.com
,但**“Domain”**属性是secure-brushlands-44802.herokuapp.com
。它们在相同的域上,但是子域名不同。 同样,浏览器也拒绝此cookie:
主机匹配(整个域)
查看
https://www.valentinog.com/get-domain-cookie.html
设置的cookie:此cookie是使用 Nginx add_header在Web服务器上设置的:
这里使用 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:这里的 cookie 来自
serene-bas-01422.herokuapp.com
,Domain
属性是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:当域在cookie创建期间被省略时,浏览器会默认在地址栏中显示原始主机,在这种情况下,我的代码会这样做:
当 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 文件:
以下是
templates/index.html
模板:下面是
static/index.js
的内容:当访问
http://127.0.0.1:5000/
时,我们会看到一个按钮。 通过单击按钮,我们向/get-cookie/
发出获取请求并获取Cookie。 正如预期的那样,cookie 落在浏览器的 Cookie storage中。对 Flask 应用程序进行一些更改,多加一个路由:
另外,调整一下 JS 代码,用于下请求刚新增的路由:
当访问
http://127.0.0.1:5000/
时,我们会看到一个按钮。 通过单击按钮,我们向/get-cookie/
发出获取请求以获取Cookie。 Cookie出现后,我们就会对/api/cities/
再次发出Fetch请求。在浏览器的控制台中,可以看到请求回来 的数据。另外,在开发者工具的
Network
选项卡中,可以看到一个名为Cookie的头,这是通过AJAX请求传给后端。只要前端与后端在同一上下文中,在前端和后端之间来回交换cookie就可以正常工作:我们说它们来自同一源。
这是因为默认情况下,Fetch 仅在请求到达触发请求的来源时才发送凭据,即
Cookie
。cookie 不能总是通过AJAX请求传递
考虑另一种情况,在后端独立运行,可以这样启动应用程序:
现在,在 Flask 应用程序之外的其他文件夹中,创建
index.html
:使用以下代码在同一文件夹中创建一个名为
index.js
的 JS 文件:在同一文件夹中,从终端运行:
此命令为您提供了要连接的本地
地址/端口
,例如http://localhost:42091/
。 访问页面并尝试在浏览器控制台打开的情况下单击按钮。 在控制台中,可以看到:因为
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:
然后将 CORS 应用于 Flask:
现在尝试在浏览器控制台打开的情况下再次单击按钮。在控制台中你应该看到
尽管我们犯了同样的错误,但这次的罪魁祸首是第二个路由。
你可以通过查看 “Network” 标签中的请求来确认,没有发送此类Cookie:
为了在不同来源的Fetch请求中包含cookie,我们必须提
credentials
标志(默认情况下,它是相同来源)。如果没有这个标志,Fetch 就会忽略 cookie,可以这样修复:
credentials: "include"
必须在第一个 Fetch 请求中出现,才能将Cookie保存在浏览器的Cookie storage 中:它还必须在第二个请求时出现,以允许将cookie传输回后端
再试一次,我们还需要在后端修复另一个错误:
为了允许在CORS请求中传输cookie,后端还需要设置
Access-Control-Allow-Credentials
标头。要点:为了使Cookie在不同来源之间通过AJAX请求传递,可以这样做:
Access-Control-Allow-Credentials
和Access-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 属性
如果要在真实环境中尝试,请可以运行以下命令,并注意
curl
在此处是不通过HTTP
保存cookie:相反,通过HTTPS,cookie 出现在
cookie jar
中:cookie jar 文件:
不要被
Secure
欺骗:浏览器通过HTTPS
接受cookie,但是一旦cookie进入浏览器,就没有任何保护。因为带有 Secure 的 Cookie 一般也不用于传输敏感数据.
Cookie 的 HttpOnly 属性
如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。
如果有设置 HttpOnly 看起来是这样的:
在 Flask 中
这样,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/
中所携带的 Cookiefirst-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 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。
使用 SameSite 属性
Cookie 的SameSite 属性用来限制
third-party
Cookie,从而减少安全风险。它可以设置三个值。Strict
最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
Lax
规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
设置了
Strict
或Lax
以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持SameSite
属性。Chrome 计划将
Lax
变为默认设置。这时,网站可以选择显式关闭SameSite
属性,将其设为None。不过,前提是必须同时设置Secure
属性(Cookie 只能通过 HTTPS 协议发送),否则无效。下面的设置无效。
下面的设置有效。
Cookies 和 认证
身份验证是 web 开发中最具挑战性的任务之一。关于这个主题似乎有很多困惑,因为
JWT
中的基于令牌的身份验证似乎要取代“旧的”、可靠的模式,如基于会话的身份验证。来看看 cookie 在这里扮演什么角色。
基于会话的身份验证
身份验证是 cookie 最常见的用例之一。
当你访问一个请求身份验证的网站时,后端将通过凭据提交(例如通过表单)在后台发送一个
Set-Cookie
标头到前端。型的会话 cookie 如下所示:
这个
Set-Cookie
头中,服务器可以包括一个名为session
、session id
或类似的cookie
。这是浏览器可以清楚看到的唯一标识符。 每当通过身份验证的用户向后端请求新页面时,浏览器就会发回会话
cookie
。基于会话的身份验证是有状态的,因为后端必须跟踪每个用户的会话。这些会话的存储可能是:
在这三个会话存储中,Redis 之类应优先于数据库或文件系统。
请注意,基于会话的身份验证与浏览器的会话存储无关。
之所以称为基于会话的会话,是因为用于用户识别的相关数据存在于后端的会话存储中,这与浏览器的会话存储不同。
何时使用基于会话的身份验证
只要能使用就使用它。基于会话的身份验证是一种最简单、安全、直接的网站身份验证形式。默认情况下,它可以在
Django
等所有流行的web框架上使用。但是,它的状态特性也是它的主要缺点,特别是当网站是由负载均衡器提供服务时。在这种情况下,像粘贴会话,或者在集中的Redis存储上存储会话这样的技术会有所帮助。
关于 JWT 的说明
JWT是
JSON 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
标记存储在cookie
或localStorage
中都不是好主意。如果你确实要使用JWT而不是坚持使用基于会话的身份验证并扩展会话存储,则可能要使用带有刷新令牌的
JWT
来保持用户登录。总结
自1994年以来,HTTP cookie一直存在,它们无处不在。
Cookies是简单的文本字符串,但可以通过Domain和
Path
对其权限进行控制,具有Secure的Cookie,只能通过 HTTP S进行传输,而可以使用HttpOnly
从 JS隐藏。但是,对于所有预期的用途,cookie都可能使用户暴露于攻击和漏洞之中。
浏览器的供应商和Internet工程任务组(Internet Engineering Task Force)年复一年地致力于提高cookie的安全性,最近的一步是
SameSite
。那么,什么才算是比较安全cookie? ,如下几点:
人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
原文:https://gizmodo.com/the-complete-guide-to-cookies-and-all-the-stuff-w-1794247382
文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。
The text was updated successfully, but these errors were encountered: