Cookie 介绍
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态或其它需要记录的信息);
- 个性化设置(如用户自定义设置、主题等);
- 浏览器行为跟踪(如跟踪分析用户行为等);
Cookie 工作流程:
- 客户端发送 HTTP 请求到服务器;
- 当服务器收到 HTTP 请求时,在响应头里面添加一个 Set-Cookie 字段;
- 浏览器收到响应后保存下 Cookie;
- 之后对该服务器每一次请求中都通过 Cookie 字段将 Cookie 信息发送给服务器;
接下来我们就来详细了解一下与 Cookie 相关的两个 HTTP Header。
Response Header:Set-Cookie
当服务器收到 HTTP 请求时,服务器可以在响应头里面添加一个 Set-Cookie 选项。浏览器收到响应后通常会保存下 Cookie,之后对该服务器每一次请求中都通过 Cookie 请求头部将 Cookie 信息发送给服务器。另外,Cookie 的过期时间、域、路径、有效期、适用站点都可以根据需要来指定。
语法:
Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<number>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=None; Secure
// Multiple attributes are also possible, for example:
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly
Cookie 的生命周期
Cookie 的生命周期取决于过期时间(Expires)或有效期(Max-Age)指定的一段时间。当 Cookie 的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。
Expires
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
Max-Age
Set-Cookie: id=a3fWa; Max-Age=3600000;
限制访问 Cookie
有两种方法可以确保 Cookie 被安全发送,并且不会被意外的参与者或脚本访问:Secure 属性和HttpOnly 属性。
Secure
标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端,因此可以预防中间人攻击( Man-in-the-middle attack)。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障,例如,可以访问客户端硬盘的人可以读取它。
HttpOnly
JavaScript Document.cookie API 无法访问带有 HttpOnly 属性的 cookie;此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本攻击(XSS)。
Cookie 的作用域
- Domain 和 Path 定义了 cookie 的作用域(Request URL):即请求哪些URL时可以携带这个 cookie;
- SameSite 则定义了在跨站(Cross Site)的情况下cookie的生效策略:跨站请求时是否能带上 cookie;
Domain
Domain 指定了哪些请求服务(Request URL)可以接受该 Cookie。如果不指定,默认为 origin,不包含子域名。如果指定了Domain,则一般包含子域名。因此,指定 Domain 比省略它的限制要少。当子域需要共享有关用户的信息时,这可能会有所帮助。
例如,如果设置 Domain=mozilla.org,则 Cookie 也将包含在子域名的请求中(如developer.mozilla.org)。
Path
Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。
例如,设置 Path=/docs,则以下地址都会匹配:
- /docs
- /docs/Web/
- /docs/Web/HTTP
SameSite
SameSite 指令则允许服务器要求某个 cookie 在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。
SameSite 可以有下面三种值:
- Strict(严格的) 。Strict 最为严格。如果 SameSite 的值是 Strict,那么浏览器会完全禁止第三方 Cookie。简言之,如果你从站点B的页面中访问站点A的资源,而站点A的某些 Cookie 设置了 SameSite = Strict 的话,那么这些 Cookie 是不会被发送到站点A的服务器上的。只有你从站点A的网页去请求站点A的资源时,才会带上这些 Cookie。
- Lax(宽松的)。Lax 相对宽松一点。在跨站点的情况下,从第三方站点的链接打开和从第三方站点提交 Get 方式的表单这两种方式都会携带 Cookie。但如果在第三方站点中使用 Post 方法,或者通过 img、iframe 等标签加载的 URL,这些场景都不会携带 Cookie。例如,一个用户在 A 站点 点击了一个 B 站点(GET请求),而假如 B 站点 使用了Samesite-cookies=Lax,那么用户可以正常登录 B 站点。相对地,如果用户在 A 站点提交了一个表单到 B站点(POST请求),那么用户的请求将被阻止,因为浏览器不允许使用 POST 方式将 Cookie 从A域发送到B域。
- None。 在任何情况下都会发送 Cookie 数据。
对于防范 CSRF 攻击,我们可以针对实际情况将一些关键的 Cookie 设置为 Strict 或者 Lax 模式,这样在跨站点请求时,这些关键的 Cookie 就不会被发送到服务器,从而使得黑客的 CSRF 攻击失效。
以前,如果 SameSite 属性没有设置,或者没有得到运行浏览器的支持,那么它的行为等同于 None,Cookies 会被包含在任何请求中——包括跨站请求。
大多数主流浏览器正在将 SameSite 的默认值迁移至 Lax。如果想要指定 Cookies 在同站、跨站请求都被发送,现在需要明确指定 SameSite 为 None。
从 Chrome 80 开始,后端设置的 Cookie 如果没有设置 SameSite 属性,将会被处理为 SameSite=Lax。
跨域和跨站
首先要理解的一点就是跨站和跨域是不同的。同站(same-site)/跨站(cross-site)和第一方(first-party)/第三方(third-party)是等价的。但是与浏览器同源策略(SOP)中的同源(same-origin)/跨域(cross-origin)是完全不同的概念。
- 跨域。什么是跨域?访问站点和请求域名的协议/主机名/端口三者必须全部一致,否则就是跨域。
- 跨站。什么是跨站?简单来说,就是一级域名和二级域名相同即可。严格来讲,是URL 的 eTLD+1 相同即可,不需要考虑协议和端口。其中,eTLD 表示有效顶级域名,注册于 Mozilla 维护的公共后缀列表(Public Suffix List)中,例如,.com、.co.uk、.github.io 等。eTLD+1 则表示,有效顶级域名+二级域名,例如 taobao.com 等。举几个例子,www.taobao.com 和 www.baidu.com 是跨站,www.a.taobao.com 和 www.b.taobao.com 是同站。
同源策略作为浏览器的安全基石,其「同源」判断是比较严格的,相对而言,Cookie中的「同站」判断就比较宽松。
Request Header:Cookie
Cookie 是一个 HTTP 请求头,其中含有先前由服务器通过 Set-Cookie 标头投放或通过 JavaScript 的 Document.cookie 方法设置,然后存储到客户端的 HTTP cookie 。它是可选的,而且可能会被忽略,例如在浏览器的隐私设置里面设置为禁用 cookie。
语法:
Cookie: <cookie-list>
Cookie: name=value
Cookie: name=value; name2=value2; name3=value3
XHR withCredentials
XMLHttpRequest.withCredentials 属性是一个 Boolean 类型,它指示了是否该使用类似 Cookies、Authorization Headers (头部授权) 或者 TLS 客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。在同一个站点下使用 withCredentials 属性是无效的。
此外,这个指示也会被用做响应中 Cookies 被忽视的标示。默认值是 false。如果在发送来自其他域的 XMLHttpRequest 请求之前,未设置withCredentials 为 true,那么就不能为它自己的域设置 Cookie 值。而通过设置 withCredentials 为 true 获得的第三方 Cookies,将会依旧享受同源策略,因此不能被通过 document.cookie 或者从头部相应请求的脚本等访问。
注:永远不会影响到同源请求。
目前更推荐使用的 Fetch API 同样有控制 cookie 是否发送的选项 credentials:请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie,必须提供这个选项。