前言
在大多数情况下,设置 Set-Cookie
时,大家可能只会设置 key
和 value
属性。当然大部分场景下也只需要做到这样。但是其实 Cookie
在此之外也有很多重要的属性,比如新加入的 Partitioned
属性就相当重要。
Cookie 的作用
Cookie
最初被设计出来是为了解决"状态管理"问题。在 Web
开发中,状态管理是指如何保持用户与网站交互的连续性和一致性,尤其是在 HTTP
协议这种无状态的通信协议中。 每当用户浏览器向服务器发送请求时,服务器都会把它当作一个全新的请求处理,而不知道它是同一个用户的连续请求。这个特性使得实现如用户登录等需要跟踪用户状态的网站功能变得非常困难。 为了解决这个问题,Cookie
被引入作为一种在客户端存储数据的方法,以便跟踪和识别用户。当用户首次访问一个网站时,服务器可以发送一个或多个 Cookie
到用户的浏览器。浏览器随后会存储这些 Cookie
,并在以后每次用户再次访问该网站时,自动将这些 Cookie
发送回服务器。通过这种方式,服务器可以识别用户并维护用户的状态信息。
如何设置一个 Cookie
在响应头中返回 Set-Cookie
以 nodejs
+ koa
为例。只需要传入 label
和 value
jsx
router.get('/setCookie', (ctx, next) => {
ctx.cookies.set('testName', 'testValue');
ctx.body = 'success';
});
设置成功后,在浏览器中就可以看到了
设置 Cookie 时的选项
为了满足我们各种各样的需求,我们在 Set Cookie
时,也提供了一系列的参数。 具体可以查看 MDN
文档:developer.mozilla.org/zh-CN/docs/...
Domain
配置 Cookie
的所属域名。一般会设置当前域名,或者子域名。默认是设置为当前域名。
jsx
router.get('/setCookie', (ctx, next) => {
// 将 cookie 设置到当前域名下
ctx.cookies.set('testName', 'testValue');
// 将 cookie 设置到顶级域名下
ctx.cookies.set('testName2', 'testValue2', {
domain: "testorigin.com",
});
ctx.body = 'success';
});
可以看到 Set-Cookie
中增加了 Domain
配置 在 Chrome
中可以看到,Domain
列。二者也是不一样的。 它们的区别在于 testName
只有在访问 testsuborigin.testorigin.com
域名下的资源才会自动带上 testName2
在访问 *.testorigin.com
域名下的资源都会带上
Expires
设置 Cookie
的过期时间,默认的有效期为当通会话。如果设置了 Expires
日期,其截止时间与客户端相关,而非服务器的时间。(我用 Chrome
试了一下,只有关闭浏览器他才过期。猜测可能有个失效,在时效内关闭 tab 。也不会清除。)
jsx
router.get('/setCookie', (ctx, next) => {
ctx.cookies.set('testName', 'testValue');
ctx.cookies.set('testName3', 'testValue3', {
expires: new Date('2024-03-22 16:57'),
});
ctx.body = 'success';
});
它在浏览器里长这样
HttpOnly
控制是否可以通过 Document.cookie
属性访问 Cookie
。默认为 true。 可以看到这里只可以获取到 testName4
Max-Age
和 Expires
的作用差不多,指在 Cookie
过期之前需要经过的秒数。秒数为 0 或负值将会使 Cookie
立刻过期。假如同时设置了 Expires
和 Max-Age
属性,那么 Max-Age
的优先级更高。
Path
表示浏览器要发送该 Cookie
标头时,请求的 URL
中所必须存在的路径。 正斜杠(/)
字符可以解释为目录分隔符,且子目录也满足匹配的条件。例如,如果 path=/docs
,那么
- 请求路径
/docs
、/docs/
、/docs/Web/
和/docs/Web/HTTP
都满足匹配条件。 - 请求路径
/
、/docsets
或者/fr/docs
则不满足匹配条件。
SameSite
控制 Cookie
是否随跨站请求一起发送,这样可以在一定程度上防范跨站请求伪造攻击(CSRF
)。
Strict
:关于这个属性的解释,我看了MDN
上的文档,写的不太对。根据我的测试,应该这样解释:如果在a.com
下有一个Strict
属性的Cookie
。则当任何非同源的地址,跳转到a.com
时。都无法携带该 cookie。如果你直接在浏览器中输入a.com
或者从同源地址跳转到a.com
是可以正常携带的。并且跨域请求的话也是无法携带该Cookie
。
比如,当前网页(非 *.github.com)有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。(via 阮一峰)
Lax
:默认值,和Strict
的唯一区别是没有了跳转时限制。None
:当跨域请求时,依然可以携带该Cookie
。这个需要谨慎设置,可能会遭到CSRF
攻击。
Partitioned
这是浏览器新出的一个配置项,需要 Chrome 114
。要解释这个属性的作用,还得从Third-party Cookie
说起。
什么是 Third-party cookie: 用户访问 jd.com 时,会在 jd.com 下种下 cookie。然后,用户又去访问 douyin.com 。这时候,如果 douyin.com 中有访问 jd.com 的资源,就会带上 jd.com 之前种下的 cookie。这也解释了,为啥你在 jd 浏览了什么商品,在 douyin 就能刷到这个商品的广告。😜
不知道小伙伴有没有看到过下面这个警告,是的浏览器要开始禁用 Third-party Cookie
了。 当然,你可以可以通过浏览器的设置,来禁用 Third-party Cookie
。 这么做的目的,是为了保护用户隐私,即为了阻止 Cross-site tracking
(跨站点跟踪)。
什么是跨站点跟踪: 用户访问 site-a.example ,其中嵌入了来自 3rd-party.example 的内容。 3rd-party.example 在用户的设备上设置cookie。 用户访问 site-b.example ,它也嵌入了 3rd-party.example 。这个新的 3rd-party.example 实例仍然能够访问用户在上一页时设置的cookie。(via MDN)
那么是不是直接禁用 Third-party cookie 就可以了呢,当然也不能这么绝对,因为还有一些合理的 Third-party cookie 。他们不是用来跟踪用户的,例如,跨不同站点的嵌入式地图或聊天小部件的持久化状态,以及为子资源CDN负载平衡和Headless CMS提供程序持久化配置信息。(via MDN) 那么 Partitioned
是如何做到的呢?
举个例子: 用户访问 shoppy.example ,其中嵌入了来自 3rd-party.example/chat 的第三方聊天服务,为需要帮助的用户提供支持。 3rd-party.example/chat 使用 Partitioned 在用户的设备上设置cookie,以在不同的站点子域中保持聊天状态。 Cookie的存储密钥为 {("shoppy.example"), ("3rd-party.example/chat")} 。 用户访问各种子域以解决他们的问题,这些子域也嵌入了 3rd-party.example/chat ,包括 support.shoppy.example 和 checkout.shoppy.example 。新嵌入的实例能够访问cookie,因为分区键仍然匹配。(via MDN)
总结
本文介绍了 Cookie
的用途, 以及 Domain
、Expires
、HttpOnly
等选项的作用。探讨了 SameSite
属性对 CSRF
防御的影响,以及新的 Partitioned
属性如何帮助保护用户隐私,限制 Third-party Cookie
的跨站跟踪功能。