Chrome 80 后默认设置 samesite 为 Lax
最近本地开发请求测试环境接口发现登陆态一直失效导致无法获取数据,排查到最后,发现是 Chrome 80 后默认设置 samesite 为 Lax,导致跨站请求不发送 cookie,测试环境认为本地环境没有登录态,自动从 localhost 跳转到测试环境去。
那么什么是 SameSite ? Chrome 为什么要做这样的更新?
Cookie
了解 SameSite 前我们要先了解 Cookie 是什么。
一般我们都会说 “HTTP 是一个无状态的协议”,不过要注意这里的 HTTP 其实是指 HTTP 1.x,而所谓无状态协议,简单的理解就是即使同一个客户端连续两次发送请求给服务器,服务器也识别不出这是同一个客户端发送的请求,这导致的问题就比如你加了一个商品到购物车中,但因为识别不出是同一个客户端,你刷新下页面就没有了。
为了解决 HTTP 无状态导致的问题,后来出现了 Cookie。不过这样说可能会让你产生一些误解,首先无状态并不是不好,有优点,但也会导致一些问题。而 Cookie 的存在也不是为了解决通讯协议无状态的问题,只是为了解决客户端与服务端会话状态的问题,这个状态是指后端服务的状态而非通讯协议的状态。
什么是 cookie
作为一段一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成,
尽管我们在浏览器里查看到了 Cookie,这并不意味着 Cookie 文件只是存放在浏览器里的。实际上,Cookies 相关的内容还可以存在本地文件里,就比如说 Mac 下的 Chrome,存放目录就是 ~/Library/Application Support/Google/Chrome/Default
,里面会有一个名为 Cookies 的数据库文件,你可以使用 sqlite 软件打开它, 存放在本地的好处就在于即使你关闭了浏览器,Cookie 依然可以生效。
如何设置
Cookie 是怎么设置的呢?简单来说就是:
- 客户端发送 HTTP 请求到服务器
- 当服务器收到 HTTP 请求时,在响应头里面添加一个 Set-Cookie 字段
- 浏览器收到响应后保存下 Cookie
- 之后对该服务器每一次请求中都通过 Cookie 字段将 Cookie 信息发送给服务器
属性
Name/Value
用 JavaScript 操作 Cookie 的时候注意对 Value 进行编码处理。
Expires
Expires 用于设置 Cookie 的过期时间。比如:
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
当 Expires 属性缺省时,表示是会话性 Cookie,表示的就是会话性 Cookie。当为会话性 Cookie 的时候,值保存在客户端内存中,并在用户关闭浏览器时失效。需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期 Cookie 也会被保留下来,就好像浏览器从来没有关闭一样。
与会话性 Cookie 相对的是持久性 Cookie,持久性 Cookies 会保存在用户的硬盘中,直至过期或者清除 Cookie。这里值得注意的是,设定的日期和时间只与客户端相关,而不是服务端。
Max-Age
Max-Age 用于设置在 Cookie 失效之前需要经过的秒数。比如:
Set-Cookie: id=a3fWa; Max-Age=604800;
Max-Age 可以为正数、负数、甚至是 0。 如果 max-Age 属性为正数时,浏览器会将其持久化,即写到对应的 Cookie 文件中。 当 max-Age 属性为负数,则表示该 Cookie 只是一个会话性 Cookie。 当 max-Age 为 0 时,则会立即删除这个 Cookie。 假如 Expires 和 Max-Age 都存在,Max-Age 优先级更高。
Domain
Domain 指定了 Cookie 可以送达的主机名。假如没有指定,那么默认值为当前文档访问地址中的主机部分(但是不包含子域名)。 像 Domain 就是 .shopee.com,这样无论是 a.shopee.com 还是 b.shopee.com 都可以使用 Cookie。 在这里注意的是,不能跨域设置 Cookie,比如 shopee 域名下的页面把 Domain 设置成百度是无效的:
Set-Cookie: qwerty=219ffwef9w0f; Domain=baidu.com; Path=/; Expires=Wed, 30 Aug 2020 00:00:00 GMT
Path
Path 指定了一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部。比如设置 Path=/docs
,/docs/Web/
下的资源会带 Cookie 首部,/test
则不会携带 Cookie 首部。
Domain 和 Path 标识共同定义了 Cookie 的作用域:即 Cookie 应该发送给哪些 URL。
Secure属性(重点关注)
标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。使用 HTTPS 安全协议,可以保护 Cookie 在浏览器和 Web 服务器间的传输过程中不被窃取和篡改。
HTTPOnly
设置 HTTPOnly 属性可以防止客户端脚本通过 document.cookie 等方式访问 Cookie,有助于避免 XSS 攻击。
SameSite
作用
这个属性可以让 cookie 在跨站请求情况下不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。
属性值
SameSite 可以有下面三种值:
- Strict: 仅允许一方请求携带 Cookie,即浏览器将只发送相同站点请求的 Cookie,即当前网页 URL 与请求目标 URL 完全一致。
- Lax:允许部分第三方请求携带 Cookie
- None: 无论是否跨站都会发送 Cookie
Chrome 之前默认是 None 的,Chrome80 后基于安全考虑默认是 Lax,于是导致了这次问题的出现。
跨域和跨站
首先要理解的一点就是跨站和跨域是不同的。同站(same-site)/跨站(cross-site)」和第一方(first-party)/第三方(third-party)是等价的。但是与浏览器同源策略(SOP)中的「同源(same-origin)/跨域(cross-origin)」是完全不同的概念。
同源策略的同源是指两个 URL 的协议/主机名/端口一致。例如,https://www.baidu.com/search,它的协议是 https,主机名是 www.baidu.com,端口是 443。
同源策略作为浏览器的安全基石,其「同源」判断是比较严格的,相对而言,Cookie中的「同站」判断就比较宽松:只要两个 URL 的 eTLD+1 相同即可,不需要考虑协议和端口。其中,eTLD 表示有效顶级域名,注册于 Mozilla 维护的公共后缀列表(Public Suffix List)中,例如,.com、.co.uk、.github.io 等。eTLD+1 则表示,有效顶级域名+二级域名,例如 baidu.com 等。 举几个例子,www.shopee.com 和 www.baidu.com 是跨站,www.a.shopee.com 和 www.b.shopee.com 是同站,a.github.io 和 b.github.io 是跨站(注意是跨站)。
也就是说,跨站必定跨域,但跨域不一定跨站,所以要明白 SameSite 不一定影响跨域请求。
改变
请求类型 | 示例 | None(正常情况) | Lax |
---|---|---|---|
链接 | <a href="..."></a> | 发送 Cookie | 发送 Cookie |
预加载 | <link rel="prerender" href="..."/> | 发送 Cookie | 发送 Cookie |
GET 表单 | <form method="GET" action="..."> | 发送 Cookie | 发送 Cookie |
POST 表单 | <form method="POST" action="..."> | 发送 Cookie | 不发送 |
iframe | <iframe src="..."></iframe> | 发送 Cookie | 不发送 |
AJAX | $.get("...") | 发送 Cookie | 不发送 |
Image | <img src="..."> | 发送 Cookie | 不发送 |
从上图可以看出,对大部分 web 应用而言,Post 表单,iframe,AJAX,Image 这四种情况从以前的跨站会发送三方 Cookie,变成了不发送。
- Post表单:应该的,学 CSRF 总会举表单的例子。
- iframe:iframe 嵌入的 web 应用有很多是跨站的,都会受到影响。
- AJAX(XMLHttpRequest):可能会影响部分前端取值的行为和结果。
- Image:图片一般放 CDN,大部分情况不需要 Cookie,故影响有限。但如果引用了需要鉴权的图片,可能会受到影响。
除了这些还有 script 的方式(JSONP),这种方式也不会发送 Cookie,如果涉及到跨站也有可能会被影响。
影响
举例子:
- a 域名下页面请求 b 域名下的接口获取登录信息,由于 Cookie 丢失,用户无法登录,页面还会误判断用户登录失效或者是禁止了第三方 cookie 功能(本次遇到的场景);
- a 域名下页面内嵌 b 域名下页面(iframe),大多是跨站请求,操作会失效;
- 埋点系统,会把用户 id 信息埋到 Cookie 中,用于日志上报,如果系统是单独的域名,与业务域名分开,也会受到影响;
- ……
解决
1. 设置 SameSite 为 none。
以 Adobe 网站为例子 https://www.adobe.com/sea/
HTTP 接口不支持 SameSite=none, 如果你想加 SameSite=none 属性,那么该 Cookie 就必须同时加上 Secure 属性,表示只有在 HTTPS 协议下该 Cookie 才会被发送。
需要 UA 检测,部分浏览器不支持 SameSite=none: IOS 12 的 Safari 以及老版本的一些 Chrome 会把 SameSite=none 识别成 SameSite=Strict,所以服务端必须在下发 Set-Cookie 响应头时进行 User-Agent 检测,对这些浏览器不下发 SameSite=none 属性
但如果测试环境接口为 http 的话且没法升级为 https 的情况下,可以考虑下面的第二种方案
2. 禁止 Chrome 这项
访问 chrome://flags/#same-site-by-default-cookies, 禁用 "samesize by default cookie"这个属性
这个会忽略 Chrome 80 版本默认设置 samesite 为 lax 的功能,所以你又能愉快的开发了,但只适用于本地个人开发使用,如果是线上生产环境,还是乖乖地按第一种方案进行改造升级。
延伸
Cookie 的优缺点
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
其优缺点:大小、安全、增加请求大小等
SameSite 和 withCredentials
我们知道 XHR 有个 withCredentials 属性也有类似的功能,区别在于前者是跨站后者是跨域:
XMLHttpRequest responses from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request, regardless of Access-Control- header values. ——from withCredentials MDN
不同域下的 XmlHttpRequest
响应,不论其 Access-Control- header
设置什么值,都无法为它自身站点设置 cookie
值,除非它在请求之前将 withCredentials
设为true。
也就是说 withCredentials 更加严格且一刀切,如果希望在跨域情况下能携带 cookie,需要同时在请求时设置 withCredentials 为 true,同时后端设置 Access-Control-Allow-Credentials
为 true,这个是浏览器同源策略的考虑,而 SameSite 则更加符合 cookie 的规则,相当于对于允许跨域的情况下,对跨站场景再加一道拦截(Strict、Lax、None)。
冲突
例如,当 a.domain1.com 请求 b.domain2.com 时,跨站且跨域的情况下,如果 SameSite 为 Strict 或者 Lax,即使 withCredentials 为 true,也无法发送 cookie。