- Web 开发中的 cookie 是什么?
- 后端配置
- 谁创建 cookies ?
- 如何查看 cookies ?
- 我有一个 cookie,现在怎么办?
- cookie 可以设置过期时间: Max-Age 和 expires
- cookie 的作用域是网站路径: path 属性
- cookie 的作用域是域名: domain 属性
- Cookies 和公共后缀列表
- Cookies 可以通过 AJAX 请求传递
- cookie 不能总是通过 AJAX 请求传递
- 处理 CORS
- Cookie 的 Secure 属性
- Cookie 的 HttpOnly 属性
- 可怕的 SameSite 属性
- Cookies 和 认证
- 总结
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
:
设置了Strict
或Lax
以后,基本就杜绝了 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
头中,服务器可以包括一个名为session
、session id
或类似的cookie
。
这是浏览器可以清楚看到的唯一标识符。 每当通过身份验证的用户向后端请求新页面时,浏览器就会发回会话cookie
。
基于会话的身份验证是有状态的,因为后端必须跟踪每个用户的会话。这些会话的存储可能是:
- 数据库
- 像 Redis 这样的键/值存储
- 文件系统
在这三个会话存储中,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? ,如下几点:
- 仅使用 HTTPS
- 尽可能带有 HttpOnly 属性
- 正确的 SameSite 配置
- 不携带敏感数据