使用 HTTP Cookie
一个**cookie**(也称为网络 cookie 或浏览器 cookie)是服务器发送到用户网络浏览器的一小段数据。浏览器可以存储 cookie、创建新 cookie、修改现有 cookie 并将其与后续请求一起发送回同一服务器。Cookie 使 Web 应用程序能够存储有限数量的数据并记住状态信息;默认情况下,HTTP 协议是无状态的。
在本文中,我们将探讨 cookie 的主要用途,解释使用 cookie 的最佳实践,并了解其隐私和安全影响。
Cookie 的用途
通常,服务器将使用 HTTP cookie 的内容来确定不同的请求是否来自同一浏览器/用户,然后发出个性化或通用响应。以下是用户登录系统的一个非常简单的描述
- 用户将登录凭据发送到服务器,例如通过表单提交。
- 如果凭据正确,服务器会更新 UI 以指示用户已登录,并使用包含会话 ID 的 cookie 响应,该会话 ID 记录其在浏览器上的登录状态。
- 稍后,用户移动到同一站点上的另一个页面。浏览器将包含会话 ID 的 cookie 与相应的请求一起发送,以指示它仍然认为用户已登录。
- 服务器检查会话 ID,如果它仍然有效,则向用户发送新页面的个性化版本。如果无效,则删除会话 ID,并向用户显示页面的通用版本(或者可能显示“访问被拒绝”消息并要求用户重新登录)。
Cookie 主要用于三个目的
- 会话管理:用户登录状态、购物车内容、游戏得分或服务器需要记住的任何其他与用户会话相关的信息。
- 个性化:用户偏好,例如显示语言和 UI 主题。
- 跟踪:记录和分析用户行为。
数据存储
在 Web 早期,由于没有其他选择,Cookie 用于一般的客户端数据存储目的。现在建议使用现代存储 API,例如Web 存储 API(localStorage
和 sessionStorage
)和IndexedDB。
它们专为存储而设计,从不向服务器发送数据,并且没有使用 cookie 进行存储的其他缺点。
- 浏览器通常限制每个域的最大 cookie 数量(因浏览器而异,通常为数百个),以及每个 cookie 的最大大小(通常为 4KB)。存储 API 可以存储更多数据。
- Cookie 会随每个请求一起发送,因此它们会降低性能(例如在缓慢的移动数据连接上),尤其是在您设置了许多 cookie 的情况下。
创建、删除和更新 Cookie
在收到 HTTP 请求后,服务器可以发送一个或多个带有响应的Set-Cookie
标头,每个标头将设置一个单独的 cookie。简单的 cookie 通过指定名称-值对来设置,如下所示
Set-Cookie: <cookie-name>=<cookie-value>
以下 HTTP 响应指示接收浏览器存储一对 cookie
HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]
注意:了解如何在各种服务器端语言/框架中使用Set-Cookie
标头:PHP、Node.JS、Python、Ruby on Rails。
当发出新请求时,浏览器通常会将先前存储的当前域的 cookie 发送回服务器,位于Cookie
HTTP 标头中
GET /sample_page.html HTTP/2.0
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
删除:定义 cookie 的生命周期
您可以指定一个过期日期或时间段,在此之后应删除 cookie 并且不再发送。根据在创建 cookie 时Set-Cookie
标头中设置的属性,它们可以是永久或会话 cookie
- 永久 cookie 在
Expires
属性中指定的日期后删除或在httpSet-Cookie: id=a3fWa; Expires=Thu, 31 Oct 2021 07:28:00 GMT;
Max-Age
属性中指定的时间段后httpSet-Cookie: id=a3fWa; Max-Age=2592000
注意:
Expires
比Max-Age
可用时间更长,但是Max-Age
出错的可能性更小,并且在两者都设置时优先。这样做的原因是,当您设置Expires
日期和时间时,它们相对于设置 cookie 的客户端。如果服务器设置为不同的时间,这可能会导致错误。 - 会话cookie——没有
Max-Age
或Expires
属性的 cookie——在当前会话结束时删除。浏览器定义“当前会话”何时结束,一些浏览器在重新启动时使用会话恢复。这可能导致会话 cookie 持续无限期。注意:如果您的网站对用户进行身份验证,则应重新生成并重新发送会话 cookie,即使是已存在的 cookie,只要用户进行身份验证。此方法有助于防止会话固定攻击,在这些攻击中,第三方可以重用用户的会话。
有一些技术旨在在 cookie 被删除后重新创建它们。这些被称为“僵尸”cookie。这些技术违反了用户隐私和控制的原则,可能违反数据隐私法规,并且可能会使使用它们的网站承担法律责任。
更新 cookie 值
要通过 HTTP 更新 cookie,服务器可以发送一个带有现有 cookie 的名称和新值的Set-Cookie
标头。例如
Set-Cookie: id=new-value
您可能出于多种原因想要这样做,例如,如果用户更新了其偏好设置并且应用程序希望反映客户端数据中的更改(您也可以使用客户端存储机制(例如Web 存储)来做到这一点)。
通过 JavaScript 更新 cookie
在浏览器中,您可以使用Document.cookie
属性或异步Cookie Store API通过 JavaScript 创建新的 cookie。请注意,以下所有示例都使用Document.cookie
,因为它是支持最广泛/最成熟的选项。
document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
您还可以访问现有 cookie 并为其设置新值,前提是HttpOnly
属性未在其上设置(即在创建它的Set-Cookie
标头中)
console.log(document.cookie);
// logs "yummy_cookie=choco; 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
。有关更多详细信息,请参阅安全部分。
安全
当您将信息存储在 cookie 中时,默认情况下,所有 cookie 值对最终用户都是可见的,并且可以由最终用户更改。您真的不希望您的 cookie 被滥用——例如被恶意攻击者访问/修改,或发送到不应发送到的域。潜在后果的范围从令人烦恼——应用程序无法正常工作或表现出奇怪的行为——到灾难性的。例如,犯罪分子可以窃取会话 ID 并使用它来设置一个 cookie,使其看起来像他们以其他人的身份登录,从而在此过程中控制其银行或电子商务帐户。
您可以通过多种方式保护您的 cookie,本节将对此进行审查。
阻止访问您的 cookie
您可以通过两种方式之一确保 cookie 以安全的方式发送并且不会被意外的方或脚本访问:使用Secure
属性和HttpOnly
属性
Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly
- 具有
Secure
属性的 cookie 仅通过 HTTPS 协议上的加密请求发送到服务器。它永远不会与不安全的 HTTP 一起发送(本地主机除外),这意味着中间人攻击者无法轻松访问它。不安全的站点(URL 中带有http:
)无法设置具有Secure
属性的 cookie。但是,不要假设Secure
可以防止所有对 cookie 中敏感信息的访问。例如,拥有客户端硬盘访问权限(或 JavaScript 如果未设置HttpOnly
属性)的人可以读取和修改信息。 - 具有
HttpOnly
属性的 cookie 不能被 JavaScript 修改,例如使用Document.cookie
;它只能在到达服务器时修改。例如,保留用户会话的 cookie 应设置HttpOnly
属性——让它们对 JavaScript 可用将非常不安全。此预防措施有助于减轻跨站点脚本(XSS)攻击。
注意:根据应用程序,您可能希望使用服务器查找的不透明标识符,而不是将敏感信息直接存储在 cookie 中,或者调查替代的身份验证/机密性机制,例如JSON Web 令牌。
定义 cookie 发送到的位置
Domain
和Path
属性定义 cookie 的范围:cookie 发送到的 URL。
Domain
属性指定哪个服务器可以接收 Cookie。如果指定了,Cookie 可以在指定的服务器及其子域上访问。例如,如果您从mozilla.org
设置了Domain=mozilla.org
,则 Cookie 可在该域及其子域(如developer.mozilla.org
)上使用。如果httpSet-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.com
或foo.example.com
,但不能设置为bar.foo.example.com
或elsewhere.com
(尽管 Cookie 仍会发送到诸如bar.foo.example.com
的子域)。有关更多详细信息,请参阅无效域。Path
属性指示一个 URL 路径,该路径必须存在于请求的 URL 中才能发送Cookie
头部。例如httpSet-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
/fr/docs
使用SameSite
控制第三方 Cookie
SameSite
属性允许服务器指定是否/何时将 Cookie 与跨站点请求一起发送 - 即 第三方 Cookie。跨站点请求是指站点(可注册的域)和/或方案(http 或 https)与用户当前访问的站点不匹配的请求。这包括单击其他站点上的链接以导航到您的站点的请求,以及嵌入的第三方内容发送的任何请求。
SameSite
有助于防止信息泄漏,维护用户隐私,并提供一些针对跨站点请求伪造攻击的保护。它采用三个可能的值:Strict
、Lax
和 None
Strict
使浏览器仅在响应来自 Cookie 原站点的请求时发送 Cookie。当您拥有与始终位于初始导航之后的函数相关的 Cookie 时,例如身份验证或存储购物车信息,应使用此选项。httpSet-Cookie: cart=110045_77895_53420; SameSite=Strict
注意:用于敏感信息的 Cookie 也应具有较短的生命周期。
Lax
类似,但当用户导航到 Cookie 原站点时,浏览器也会发送 Cookie(即使用户来自不同的站点)。这对于影响站点显示的 Cookie 很有用 - 例如,您的网站上可能包含合作伙伴产品信息以及联盟链接。当用户点击该链接访问合作伙伴网站时,他们可能希望设置一个 Cookie 来表明已点击了联盟链接,从而显示奖励横幅并在购买产品时提供折扣。httpSet-Cookie: affiliate=e4rt45dw; SameSite=Lax
None
指定在源请求和跨站点请求中都发送 Cookie。如果您希望将 Cookie 与从嵌入在其他站点中的第三方内容(例如广告技术或分析提供商)发出的请求一起发送,这将很有用。请注意,如果设置了SameSite=None
,则还必须设置Secure
属性 -SameSite=None
需要安全上下文。httpSet-Cookie: widget_session=7yjgj57e4n3d; SameSite=None; Secure; HttpOnly
如果没有设置 SameSite
属性,则 Cookie 默认情况下将被视为 Lax
。
Cookie 前缀
由于 Cookie 机制的特性,服务器无法确认 Cookie 是否是从安全来源设置的,甚至无法确定 Cookie 最初是在哪里设置的。
子域上的易受攻击的应用程序可以使用 Domain
属性设置 Cookie,这使得可以访问所有其他子域上的该 Cookie。这种机制可能被滥用于会话固定攻击。有关主要缓解方法,请参阅会话固定。
但是,作为纵深防御措施,您可以使用 Cookie 前缀来断言有关 Cookie 的特定事实。有两个前缀可用
__Host-
:如果 Cookie 名称具有此前缀,则仅当它也用Secure
属性标记、从安全来源发送、不包含Domain
属性且Path
属性设置为/
时,才会在Set-Cookie
标头中接受它。换句话说,Cookie 是域锁定的。__Secure-
:如果 Cookie 名称具有此前缀,则仅当它用Secure
属性标记并从安全来源发送时,才会在Set-Cookie
标头中接受它。这比__Host-
前缀弱。
浏览器将拒绝不符合其限制的具有这些前缀的 Cookie。这确保了具有前缀的子域创建的 Cookie 仅限于子域或完全被忽略。由于应用程序服务器仅在确定用户是否已通过身份验证或 CSRF 令牌是否正确时检查特定的 Cookie 名称,因此这有效地充当了针对会话固定的防御措施。
注意:在服务器端,Web 应用程序必须检查包含前缀的完整 Cookie 名称。用户代理不会在请求的 Cookie
标头中发送 Cookie 之前从 Cookie 中删除前缀。
有关 Cookie 前缀和浏览器支持的当前状态的更多信息,请参阅Set-Cookie 参考文章的前缀部分。
隐私和跟踪
前面我们讨论了如何使用 SameSite
属性来控制何时发送第三方 Cookie,以及这如何帮助维护用户隐私。隐私在构建网站时是一个非常重要的考虑因素,如果做得好,可以建立用户信任。如果做得不好,它可能会完全破坏这种信任并导致各种其他问题。
第三方 Cookie 可以通过 <iframe>
中的第三方内容嵌入到站点中。它们有许多合法用途,包括共享用户个人资料信息、统计广告展示次数或跨不同相关域收集分析数据。
但是,第三方 Cookie 也可用于创建令人毛骨悚然、侵入性的用户体验。第三方服务器可以根据同一浏览器在访问多个站点时发送给它的 Cookie 创建用户浏览历史记录和习惯的配置文件。一个经典的例子是,当您在一个网站上搜索产品信息时,然后无论您走到哪里,都会被类似产品的广告追赶。
浏览器供应商知道用户不喜欢这种行为,因此都开始默认阻止第三方 Cookie,或者至少制定了朝这个方向发展的计划。其他浏览器设置或扩展程序也可能会阻止第三方 Cookie(或跟踪 Cookie)。
注意:Cookie 阻止可能会导致某些第三方组件(如社交媒体小部件)无法按预期工作。随着浏览器对第三方 Cookie 施加更多限制,开发人员应开始寻找减少对它们的依赖的方法。
请参阅我们的第三方 Cookie文章,了解有关第三方 Cookie、相关问题以及可用替代方案的详细信息。请参阅我们的隐私登录页面,以获取有关隐私的更多一般信息。
与 Cookie 相关的法规
涵盖 Cookie 使用的法律法规包括
- 欧盟的通用数据保护条例 (GDPR)
- 欧盟的电子隐私指令
- 加利福尼亚州消费者隐私法
这些法规具有全球影响力。它们适用于来自这些司法管辖区(欧盟和加利福尼亚州)的用户访问的万维网上的任何站点(需要注意的是,加利福尼亚州的法律仅适用于总收入超过 2500 万美元的实体等)。
这些法规包括以下要求
- 通知用户您的站点使用 Cookie。
- 允许用户选择退出接收部分或全部 Cookie。
- 允许用户在不接收 Cookie 的情况下使用您服务的大部分内容。
您所在地区可能还有其他管理 Cookie 使用的法规。了解并遵守这些法规是您的责任。有一些公司提供“Cookie 横幅”代码,可以帮助您遵守这些法规。
注意:出于透明目的并为了遵守法规,公司应披露其网站上使用的 Cookie 类型。例如,请参阅Google 关于其使用 Cookie 类型的通知和 Mozilla 的网站、通信和 Cookie 隐私声明。
另请参阅
- 相关的 HTTP 头部:
Set-Cookie
、Cookie
- 相关的 JavaScript API:
Document.cookie
、Navigator.cookieEnabled
、Cookie Store API - 第三方 Cookie
- Cookie 规范:RFC 6265
- Cookie、GDPR 和电子隐私指令