使用 HTTP Cookie

Cookie(也称为网络 Cookie 或浏览器 Cookie)是服务器发送给用户网络浏览器的一小段数据。浏览器可以存储 Cookie、创建新 Cookie、修改现有 Cookie,并在后续请求中将其发送回同一服务器。Cookie 使网络应用程序能够存储有限数量的数据并记住状态信息;默认情况下,HTTP 协议是无状态的

在本文中,我们将探讨 Cookie 的主要用途,解释使用它们的最佳实践,并审视它们对隐私和安全的影响。

Cookie 的用途

通常,服务器会使用 HTTP Cookie 的内容来确定不同请求是否来自同一个浏览器/用户,然后酌情发出个性化或通用响应。以下描述了一个基本的用​​户登录系统:

  1. 用户向服务器发送登录凭据,例如通过表单提交。
  2. 如果凭据正确,服务器会更新 UI 以指示用户已登录,并返回一个包含会话 ID 的 Cookie,该 Cookie 会记录用户在浏览器上的登录状态。
  3. 稍后,用户导航到同一网站上的另一个页面。浏览器会随相应的请求一起发送包含会话 ID 的 Cookie,以表明它仍然认为用户已登录。
  4. 服务器检查会话 ID,如果仍然有效,则向用户发送新页面的个性化版本。如果无效,则删除会话 ID,并向用户显示该页面的通用版本(或者显示“拒绝访问”消息并要求重新登录)。

visual representation of the above sign-in system description

Cookie 主要用于三个目的:

  • 会话管理:用户登录状态、购物车内容、游戏分数或服务器需要记住的任何其他与用户会话相关的详细信息。
  • 个性化:用户偏好,例如显示语言和 UI 主题。
  • 跟踪:记录和分析用户行为。

数据存储

在网络早期没有其他选择时,Cookie 被用于一般客户端数据存储目的。现在推荐使用现代存储 API,例如Web Storage API (localStoragesessionStorage) 和IndexedDB

它们在设计时就考虑到了存储,从不向服务器发送数据,并且没有使用 Cookie 进行存储的其他缺点:

  • 浏览器通常限制每个域的最大 Cookie 数量(因浏览器而异,通常为数百个)和每个 Cookie 的最大大小(通常为 4KB)。存储 API 可以存储更多数据。
  • Cookie 会随每个请求发送,因此它们可能会降低性能(例如在缓慢的移动数据连接上),尤其是当您设置了大量 Cookie 时。

注意:要查看存储的 Cookie(以及网页正在使用的其他存储),您可以使用 Firefox 开发者工具中的存储检查器,或 Chrome 开发者工具中的应用程序面板

创建、移除和更新 Cookie

收到 HTTP 请求后,服务器可以通过响应发送一个或多个Set-Cookie 标头,每个标头都会设置一个单独的 Cookie。Cookie 通过指定一个名称-值对来设置,如下所示:

http
Set-Cookie: <cookie-name>=<cookie-value>

以下 HTTP 响应指示接收浏览器存储一对 Cookie:

http
HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: yummy_cookie=chocolate
Set-Cookie: tasty_cookie=strawberry

[page content]

注意:了解如何在各种服务器端语言/框架中使用 Set-Cookie 标头:PHPNode.jsPythonRuby on Rails

发出新请求时,浏览器通常会在Cookie HTTP 标头中将先前存储的当前域的 Cookie 发送回服务器:

http
GET /sample_page.html HTTP/2.0
Host: www.example.org
Cookie: yummy_cookie=chocolate; tasty_cookie=strawberry

您可以指定一个过期日期或时间段,在此之后 Cookie 应被删除且不再发送。根据创建 Cookie 时Set-Cookie 标头中设置的属性,它们可以是永久会话Cookie:

  • 永久 Cookie 在 Expires 属性中指定的日期之后删除

    http
    Set-Cookie: id=a3fWa; Expires=Thu, 31 Oct 2021 07:28:00 GMT;
    

    或在 Max-Age 属性中指定的期限之后删除

    http
    Set-Cookie: id=a3fWa; Max-Age=2592000
    

    注意:Expires 的可用时间比 Max-Age 长,但是 Max-Age 更不容易出错,并且在两者都设置时具有优先权。其原理是,当您设置 Expires 日期和时间时,它们是相对于设置 Cookie 的客户端而言的。如果服务器设置为不同的时间,这可能会导致错误。

  • 会话 Cookie——没有 Max-AgeExpires 属性的 Cookie——在当前会话结束时删除。浏览器定义“当前会话”何时结束,有些浏览器在重新启动时使用会话恢复。这可能导致会话 Cookie 无限期地持续存在。

    注意:如果您的网站对用户进行身份验证,它应该在用户进行身份验证时重新生成并重新发送会话 Cookie,即使是已经存在的 Cookie。这种方法有助于防止会话固定攻击,其中第三方可以重用用户的会话。

要立即删除 Cookie,请使用相同的名称、路径和域(如果已指定)再次设置 Cookie,并将其 Expires 属性设置为过去的日期或将其 Max-Age 属性设置为 0 或负值。这会指示浏览器立即删除 Cookie。例如:

http
Set-Cookie: id=a3fWa; Max-Age=0

您还可以使用Clear-Site-Data 响应头清除与可注册域关联的所有 Cookie。例如,从 https://foo.example.com/ 发送的以下头将清除由 example.com 及其所有子域(例如 all.bar.example.com)发送的所有 Cookie。

http
Clear-Site-Data: "cookies"

有一些技术旨在在 Cookie 被删除后重新创建它们。这些被称为“僵尸”Cookie。这些技术违反了用户隐私和控制的原则,可能违反数据隐私法规,并可能使使用它们的网站面临法律责任。

要通过 HTTP 更新 Cookie,服务器可以发送一个Set-Cookie 标头,其中包含现有 Cookie 的名称和新值。例如:

http
Set-Cookie: id=new-value

您可能希望这样做有几个原因,例如用户更新了他们的偏好设置,并且应用程序希望在客户端数据中反映这些更改(您也可以使用客户端存储机制,例如Web Storage)。

通过 JavaScript 更新 Cookie

在浏览器中,您可以使用Document.cookie 属性或异步Cookie Store API 通过 JavaScript 创建新的 Cookie。请注意,以下所有示例都使用 Document.cookie,因为它是支持最广泛/最成熟的选项。

js
document.cookie = "yummy_cookie=chocolate";
document.cookie = "tasty_cookie=strawberry";

您还可以访问现有 Cookie 并为其设置新值:

js
console.log(document.cookie);
// logs "yummy_cookie=chocolate; tasty_cookie=strawberry"

document.cookie = "yummy_cookie=blueberry";

console.log(document.cookie);
// logs "tasty_cookie=strawberry; yummy_cookie=blueberry"

出于安全目的,您无法通过在发起请求时直接发送更新的 Cookie 标头来更改 Cookie 值,例如通过fetch()XMLHttpRequest

有一些充分的理由不应该允许 JavaScript 根本修改 Cookie。您可以通过在其创建期间指定HttpOnly 属性来阻止 JavaScript 访问 Cookie。有关更多详细信息,请参阅安全部分。

安全

当您在 Cookie 中存储信息时,默认情况下,所有 Cookie 值对最终用户都是可见的,并且可以由最终用户更改。您真的不希望您的 Cookie 被滥用——例如被不良行为者访问/修改,或发送到不应该发送到的域。潜在的后果可能从令人烦恼的(应用程序无法正常工作或表现出奇怪的行为)到灾难性的。例如,犯罪分子可以窃取会话 ID 并使用它来设置一个 Cookie,使其看起来像是他们以其他人的身份登录,从而控制他们的银行或电子商务账户。

您可以通过多种方式保护您的 Cookie,本节将对此进行回顾。

阻止访问您的 Cookie

您可以通过两种方式确保 Cookie 安全发送且不会被意外方或脚本访问:使用 Secure 属性和 HttpOnly 属性:

http
Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly
  • 带有 Secure 属性的 Cookie 仅通过 HTTPS 协议随加密请求发送到服务器。它绝不会随不安全的 HTTP 发送(除了在 localhost 上),这意味着中间人攻击者无法轻易访问它。不安全的站点(URL 中带有 http:)无法设置带有 Secure 属性的 Cookie。但是,不要假设 Secure 可以阻止对 Cookie 中敏感信息的所有访问。例如,有权访问客户端硬盘(或 JavaScript,如果未设置 HttpOnly 属性)的人可以读取和修改信息。

  • 带有 HttpOnly 属性的 Cookie 无法被 JavaScript 访问,例如使用Document.cookie;它只能在到达服务器时才能访问。例如,持久化用户会话的 Cookie 应该设置 HttpOnly 属性——将它们提供给 JavaScript 会非常不安全。这种预防措施有助于缓解跨站脚本(XSS)攻击。

注意:根据应用程序的不同,您可能希望使用服务器查找的不透明标识符,而不是直接将敏感信息存储在 Cookie 中,或者研究替代的身份验证/保密机制,例如JSON Web Tokens

定义 Cookie 的发送位置

DomainPath 属性定义了 Cookie 的范围:Cookie 发送到哪些 URL。

  • Domain 属性指定哪个服务器可以接收 Cookie。如果指定,Cookie 在指定的服务器及其子域上可用。例如,如果您从 mozilla.org 设置 Domain=mozilla.org,则 Cookie 在该域和 developer.mozilla.org 等子域上可用。

    http
    Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Domain=mozilla.org
    

    如果 Set-Cookie 标头未指定 Domain 属性,则 Cookie 在设置它的服务器上可用,但不在其子域上可用。因此,指定 Domain 比省略它限制更少。请注意,服务器只能将其 Domain 属性设置为其自己的域或父域,而不能设置为子域或其他域。因此,例如,域为 foo.example.com 的服务器可以将属性设置为 example.comfoo.example.com,但不能设置为 bar.foo.example.comelsewhere.com(Cookie 仍然会发送到子域,例如 bar.foo.example.com)。有关更多详细信息,请参阅无效域

  • Path 属性指示请求 URL 中必须存在的 URL 路径,才能发送 Cookie 标头。例如:

    http
    Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Path=/docs
    

    %x2F ("/") 字符被视为目录分隔符,子目录也匹配。例如,如果您设置 Path=/docs,这些请求路径将匹配:

    • /docs
    • /docs/
    • /docs/Web/
    • /docs/Web/HTTP

    但这些请求路径不匹配:

    • /
    • /docsets
    • /en-US/docs

    注意:path 属性允许您根据网站的不同部分控制浏览器发送哪些 Cookie。它不作为安全措施,并且不保护免受从不同路径对 Cookie 的未经授权的读取。

使用 SameSite 控制第三方 Cookie

SameSite 属性允许服务器指定是否/何时随跨站点请求发送 Cookie——即第三方 Cookie。跨站点请求是指站点(可注册域)和/或方案(http 或 https)与用户当前访问的站点不匹配的请求。这包括当点击其他网站上的链接导航到您的网站时发送的请求,以及嵌入式第三方内容发送的任何请求。

SameSite 有助于防止信息泄露,保护用户隐私,并提供针对跨站点请求伪造攻击的一些保护。它有三个可能的值:StrictLaxNone

  • Strict 导致浏览器仅在响应来自 Cookie 源站点的请求时发送 Cookie。当您的 Cookie 与始终位于初始导航之后的功能(例如身份验证或存储购物车信息)相关时,应该使用此选项。

    http
    Set-Cookie: cart=110045_77895_53420; SameSite=Strict
    

    注意:用于敏感信息的 Cookie 也应该具有较短的生命周期

  • Lax 类似,只是浏览器在用户导航到 Cookie 的源站点时也会发送 Cookie(即使用户来自不同的站点)。这对于影响站点显示的 Cookie 很有用——例如,您可能在您的网站上有一个包含联盟链接的合作伙伴产品信息。当该链接被跟踪到合作伙伴网站时,他们可能希望设置一个 Cookie,说明已跟踪了联盟链接,这将显示奖励横幅并在购买产品时提供折扣。

    http
    Set-Cookie: affiliate=e4rt45dw; SameSite=Lax
    
  • None 指定在源请求和跨站点请求中都发送 Cookie。如果您希望随嵌入在其他站点中的第三方内容(例如广告技术或分析提供商)发出的请求发送 Cookie,这很有用。请注意,如果设置了 SameSite=None,则还必须设置 Secure 属性——SameSite=None 需要安全上下文

    http
    Set-Cookie: widget_session=7yjgj57e4n3d; SameSite=None; Secure; HttpOnly
    

如果没有设置 SameSite 属性,则默认将 Cookie 视为 Lax

由于 Cookie 机制的设计,服务器无法确认 Cookie 是否从安全来源设置,甚至无法判断 Cookie 最初在何处设置。

子域上的应用程序可以设置具有 Domain 属性的 Cookie,这使得可以在所有其他子域上访问该 Cookie。这种机制可能在会话固定攻击中被滥用。

作为纵深防御措施,您可以使用Cookie 前缀对支持的用户代理中的 Cookie 属性施加特定限制。所有 Cookie 前缀都以双下划线(__)开头并以破折号(-)结尾。有四个前缀可用:

  • __Secure-:名称以 __Secure- 开头的 cookie 必须由安全页面 (HTTPS) 设置 Secure 属性。
  • __Host-:名称以 __Host- 开头的 cookie 必须由安全页面 (HTTPS) 设置 Secure 属性。此外,它们不能指定 Domain 属性,并且 Path 属性必须设置为 /。这保证了此类 cookie 仅发送给设置它们的主机,而不发送给域上的任何其他主机。它还保证它们在主机范围内设置,并且不能被该主机上的任何路径覆盖。这种组合产生了一个尽可能接近将来源视为安全边界的 cookie。
  • __Http-:名称以 __Http- 开头的 Cookie 必须由安全页面 (HTTPS) 设置 Secure 标志,并且还必须设置 HttpOnly 属性以证明它们是通过 Set-Cookie 标头设置的(它们不能通过 JavaScript 功能(例如Document.cookieCookie Store API)设置或修改)。
  • __Host-Http-:名称以 __Host-Http- 开头的 cookie 必须由安全页面 (HTTPS) 设置 Secure 标志,并且必须设置 HttpOnly 属性以证明它们是通过 Set-Cookie 头设置的。此外,它们还具有与 __Host- 前缀 cookie 相同的限制。这种组合产生了一个尽可能接近将来源视为安全边界的 cookie,同时确保开发人员和服务器操作员知道其范围仅限于 HTTP 请求。

浏览器将拒绝不符合其限制的带有这些前缀的 Cookie。由于应用程序服务器在确定用户是否已通过身份验证或 CSRF 令牌是否正确时仅检查特定的 Cookie 名称,因此这实际上是针对会话固定的防御措施。

注意:在服务器上,Web 应用程序必须检查包括前缀在内的完整 Cookie 名称。用户代理在请求的Cookie 标头中发送 Cookie 之前不会去除前缀。

有关 Cookie 前缀和浏览器支持当前状态的更多信息,请参阅Set-Cookie 参考文章的前缀部分

隐私与追踪

早些时候我们讨论了 SameSite 属性如何用于控制何时发送第三方 Cookie,以及这如何有助于保护用户隐私。隐私是构建网站时非常重要的考虑因素,如果做得好,可以与您的用户建立信任。如果做得不好,它可能会完全侵蚀这种信任并导致各种其他问题。

第三方 Cookie 可以由通过<iframe> 嵌入在网站中的第三方内容设置。它们有许多合法用途,包括共享用户配置文件信息、计算广告展示次数或跨不同相关域收集分析数据。

然而,第三方 Cookie 也可能被用来创建令人毛骨悚然、侵入性的用户体验。第三方服务器可以根据同一浏览器在访问多个网站时发送给它的 Cookie 来创建用户浏览历史和习惯的配置文件。典型的例子是,当您在一个网站上搜索产品信息时,无论您走到哪里,都会被类似产品的广告追逐。

浏览器供应商知道用户不喜欢这种行为,因此都已开始默认阻止第三方 Cookie,或者至少计划朝这个方向发展。第三方 Cookie(或仅跟踪 Cookie)也可能被其他浏览器设置或扩展程序阻止。

注意:Cookie 阻止可能导致某些第三方组件(例如社交媒体小部件)无法按预期运行。随着浏览器对第三方 Cookie 施加更多限制,开发人员应该开始寻找减少对它们依赖的方法。

有关第三方 Cookie、与之相关的问题以及可用替代方案的详细信息,请参阅我们的第三方 Cookie 文章。有关隐私的更多信息,请参阅我们的隐私着陆页。

涵盖 Cookie 使用的法律法规包括:

这些法规具有全球影响力。它们适用于来自这些司法管辖区(欧盟和加利福尼亚州,但加利福尼亚州的法律仅适用于总收入超过 2500 万美元的实体等)的用户访问的任何万维网站。

这些法规包括以下要求:

  • 通知用户您的网站使用 Cookie。
  • 允许用户选择退出接收部分或全部 Cookie。
  • 允许用户在不接收 Cookie 的情况下使用您的大部分服务。

您所在地区可能还有其他管理 Cookie 使用的法规。您有责任了解并遵守这些法规。有些公司提供“Cookie 横幅”代码,可帮助您遵守这些法规。

注意:公司应披露其网站上使用的 Cookie 类型,以实现透明度并遵守法规。例如,请参阅Google 关于其使用的 Cookie 类型的通知,以及 Mozilla 的网站、通信和 Cookie 隐私通知

另见