https://web.dev/samesite-cookies-explained/
通过学习如何明确标记跨站点cookie来保护您的站点。
Cookie是一种可用于向网站添加持久状态的方法。多年来,它的能力不断增长和发展,但给平台留下了一些遗留问题。为了解决这个问题,浏览器(包括Chrome、Firefox和Edge)正在改变它们的行为,以强制执行更多隐私保护默认值。
每个cookie都是一个 key=value 的键值对,以及一些控制何时何地使用该cookie的属性。您可能已经使用了这些属性来设置一些内容,比如到期日期或指示cookie应只通过HTTPS发送。服务器通过在响应中发送适当命名的 Set-Cookie 首部来设置cookie。对于所有的细节,您可以深入到RFC6265bis,但现在这里是一个快速复习。
假设您有一个博客,您想在其中向用户显示“最新消息”促销。 用户可以关闭促销,然后他们将在一段时间内不会再次看到它。 您可以将该首选项存储在 cookie 中,将其设置为在一个月(2,600,000 秒)后过期,并且仅通过 HTTPS 发送。 该首部如下所示:
Set-Cookie: promo_shown=1; Max-Age=2600000; Secure

当您的读者查看满足这些要求的页面时,即他们处于安全连接并且cookie未满一个月,那么他们的浏览器将在其请求中发送此首部:
Cookie: promo_shown=1

您还可以在 JavaScript 中使用 document.cookie 添加和读取该站点可用的cookie。 对 document.cookie 进行分配键将使用该键创建或覆盖cookie。 例如,您可以在浏览器的 JavaScript 控制台中尝试以下操作:
> document.cookie = "promo_shown=1; Max-Age=2600000; Secure"
< "promo_shown=1; Max-Age=2600000; Secure"
读取 document.cookie 将输出在当前上下文中可访问的所有cookie,每个cookie由分号分隔:
> document.cookie;
< "promo_shown=1; color_theme=peachpuff; sidebar_loc=left"

如果你在一些热门网站上尝试这个方法,你会注意到大多数网站设置的cookie都远远不止三个。在大多数情况下,在每次到该域的请求中都会发送这些cookie,这有很多问题。对于用户来说,上传带宽通常比下载带宽更受限制,因此所有出站请求的开销都会增加第一个字节的时间延迟。在设置的cookie数量和大小上要保守。使用 Max-Age 属性有助于确保cookie的存在时间不会超过需要的时间。
什么是第一方和第三方cookie?
如果你回到你以前浏览过的同一个站点,你可能会注意到有很多域的cookie,而不仅仅是你当前访问的域。与当前站点的域相匹配的cookie(即浏览器地址栏中显示的内容)称为第一方cookie。类似地,来自当前站点以外的域的cookie称为第三方cookie。这不是一个绝对的标签,而是相对于用户的上下文;同一cookie可以是第一方或是第三方的,具体取决于用户当时所在的站点。

继续上面的例子,假设您的一篇博文中有一只特别神奇的猫的图片,它位于/blog/img/amazing-cat.png
。这是一个如此惊人的图像,所以另一个人直接在他们的网站上使用它。如果访问者曾访问过您的博客,并且有promo_shown
的cookie,然后当他们在另一个人的网站看到amazing-cat.png时,这个cookie
将在图像的请求中被发送。这对任何人来说都不是特别有用的,因为promo_shown没有用于其他人站点上的任何内容
,它只是增加了请求的开销。
如果这是一个意想不到的效果,你为什么要这样做?正是这种机制允许站点在第三方上下文中使用时保持状态。例如,如果您在网站上嵌入了 YouTube 视频,那么访问者将在播放器中看到“稍后观看”选项。如果您的访问者已经登录到 YouTube,则该会话将通过第三方 cookie 在嵌入式播放器中提供——这意味着“稍后观看”按钮只会一次性保存视频,而不是提示他们登录或必须将从您的页面导航离开并返回到 YouTube。

网络的文化属性之一是默认情况下它往往是开放的。这是让这么多人能够在那里创建自己的内容和应用程序的原因之一。然而,这也带来了许多安全和隐私问题。跨站点请求伪造 (CSRF) 攻击依赖于这样一个事实,即无论是谁发起请求,cookie 都会附加到给定来源的任何请求上。例如,如果您访问 evil.example
那么它可以触发对 your-blog.example
的请求,您的浏览器会很乐意附加相关的 cookie。如果您的博客不小心验证了这些请求,则 evil.example
可能会触发删除帖子或添加自己的内容等操作。
用户也越来越了解如何使用 cookie 跨多个站点跟踪他们的活动。然而,到目前为止,还没有一种方法可以用 cookie 明确地说明您的意图。您的promo_shown的
cookie应仅在第一方上下文中发送,而用于嵌入其他站点的小部件的会话 cookie 则有意在第三方上下文中提供登录状态。
使用SameSite属性显式声明cookie使用情况
SameSite
属性的引入(在RFC6265bis 中定义 )允许您声明您的 cookie 是否应限制为第一方或同一站点上下文。准确理解此处“站点”的含义会很有帮助。站点是指域后缀和它之前的域部分的组合。例如,www.web.dev
域是web.dev
站点的一部分。
关键术语:
如果用户打开www.web.dev
并从static.web.dev
中请求图像,则这是同一站点请求。
在公共后缀列表定义了这一点,因此它不仅仅是像.com这样的顶级域名
,也包括像github.io这样的
服务。这使得your-project.github.io和
my-project.github.io
可以算作单独的站点。
关键术语:
如果用户打开your-project.github.io
并从my-project.github.io
请求图像,则这是一个跨站点请求。
在cookie上引入SameSite属性提供了三种不同的方法来控制这种行为。
您可以选择不指定该属性,也可以使用Strict
或Lax
将 cookie 限制为相同站点的请求。
如果奖SameSite
设置为Strict
,则 cookie 将仅在第一方上下文中发送。就用户而言,只有当 cookie 的站点与浏览器 URL 栏中当前显示的站点相匹配时,才会发送 cookie。因此,如果 promo_shown的
cookie 设置如下:
Set-Cookie: promo_shown=1; SameSite=Strict
当用户在您的网站上时,cookie 将按预期与请求一起发送。但是,当通过链接进入您的网站时,比如从另一个网站或通过朋友的电子邮件,在最初的请求中不会发送 cookie。当您拥有与功能相关的cookie时,这是很好的,这些cookie将始终位于初始导航之后,例如更改密码或进行购买,但对于promo_shown
来说限制太多。如果您的读者通过链接进入网站,他们希望发送 cookie,以便应用他们的偏好。
这就是SameSite=Lax
允许通过这些顶级导航发送 cookie的地方。让我们回顾一下上面的 cat 文章示例,其中另一个站点正在引用您的内容。他们直接使用您的猫照片并提供指向您原始文章的链接。
<p>Look at this amazing cat!</p>
<img src="https://blog.example/blog/img/amazing-cat.png" />
<p>Read the <a href="https://blog.example/blog/cat.html">article</a>.</p>
并且 cookie 已设置为:
Set-Cookie: promo_shown=1; SameSite=Lax
当读者在其他人的博客上时,浏览器请求amazing-cat.png时将不会发送cookie。但是,当读者在您的博客上通过链接访问cat.html时,该请求将包含cookie。这使得Lax对于影响站点显示的cookie来说是一个很好的选择,这些cookie是与用户正在采取的操作相关的而设为Strict有效的cookie。
注意:
Strict
和Lax
都不是您网站安全的完整解决方案。Cookie 是作为用户请求的一部分发送的,您应该像对待任何其他用户输入一样对待它们。这意味着清理和验证输入。切勿使用 cookie 来存储您认为是服务器端机密的数据。
最后还有一个选项,可以选择不指定值,该值以前是隐式声明您希望在所有上下文中发送 cookie 的方式。在RFC6265bis的最新草案中, 通过引入SameSite=None
新值明确了这一点. 这意味着您可以使用None
明确传达您有意希望在第三方上下文中发送 cookie。

☆ 如果您提供其他站点使用的服务,例如小部件、嵌入内容、附属程序、广告或跨多个站点的登录,那么您应该使用 None
来确保您的意图是明确的。
更改默认行为而不使用SameSite
虽然SameSite属性得到广泛支持,但遗憾的是,它没有被开发人员广泛采用。到处发送cookie的开放默认值意味着所有用例都可以工作,但用户容易受到CSRF和无意信息泄漏的影响。为了鼓励开发人员表明他们的意图,并为用户提供更安全的体验,IETF提案《越来越好的Cookie》列出了两个关键更改:
- 没有
SameSite
属性的cookie将被视为SameSite=Lax
. 带有SameSite=None
的cookie还必须指定Secure
,这意味着它们需要一个安全的上下文。
Chrome在84版时就实现了这种默认行为。Firefox 可以从Firefox 69开始对它们进行测试,并将使它们在将来成为默认行为。要在Firefox中测试这些行为,请打开about:config
并设定network.cookie.sameSite.laxByDefault
. Edge 也计划更改其默认行为。
默认情况下的SameSite=Lax
不设置该属性
Set-Cookie: promo_shown=1
如果你送一个不指定任何
SameSite
属性的cookie…
应用默认行为
Set-Cookie: promo_shown=1; SameSite=Lax
浏览器会将该cookie视为指定了SameSite=Lax。
虽然这是为了应用更安全的默认值,但理想情况下,您应该设置一个显式的SameSite属性,而不是依赖浏览器为您应用该属性。这使您对cookie的意图更加明确,并提高了跨浏览器获得一致体验的机会。
注意:
Chrome应用的默认行为比显式的SameSite=Lax稍微宽松一些,因为它将允许某些cookie在顶级POST请求中被发送。您可以在Blink-dev公告中看到确切的详细信息。这是一种临时缓解措施,您仍然应该使用 SameSite=None; Secure 来修复您的跨站点cookie。
SameSite=None
必须是安全的
拒绝
Set-Cookie: widget_session=abc123; SameSite=None
没有设置
Secure
的cookie将被拒绝.
接受
Set-Cookie: widget_session=abc123; SameSite=None; Secure
必须确保将 SameSite=None 与 Secure 属性配对。
您可以从Chrome 76开始测试此行为,方法是启用 about://flags/#cookies-without-same-site-must-be-secure。在Firefox 69中通过 about:config
设置 network.cookie.sameSite.noneRequiresSecure 。
在设置新cookie和主动刷新现有cookie时,即使它们没有接近到期日期,也要应用此方法。
☆ 如果您依赖于在您的网站上提供第三方内容的任何服务,您还应该向提供商确认他们正在更新其服务。您可能需要更新您的依赖项或代码片段,以确保您的站点获得新的行为。
这两个更改对浏览器都是向后兼容的,他们有的正确实现了SameSite
属性的早起版本,或者根本不支持它。通过将这些更改应用于cookie,您可以明确它们的预期用途,而不是依赖浏览器的默认行为。同样,任何尚未识别 SameSite=None 的客户端都应该忽略它,并像未设置属性一样继续操作。
警告:
许多旧版本的浏览器(包括Chrome、Safari和UC浏览器)都与新的None
属性不兼容,可能会忽略或限制cookie。这种行为在当前版本中是确定的,但您应该检查您的流量,以确定受影响的用户比例。您可以在Chromium站点上看到已知不兼容客户端的列表。
SameSite
cookie
秘诀
有关如何更新cookie以成功处理对SameSite=None的更改,以及浏览器行为差异的详细信息,请看后续文章,SameSite cookie 秘诀。