web 开发发展到现在,前端和后端几乎已经完全分离了,不仅部署的环境分离,很多时候域名也是分离的。今天主要讨论一个不同域名下前后端交互所带来的问题。


我们先来看看 cookie 的写入规则:

当浏览器接收到请求返回时,会尝试解析 response header 中的 Set-Cookie 字段,它的构成一般是这样的:

Set-Cookie: a=1; domain=xwenliang.cn; path=/a; max-age=60; samesite=none; secure; httponly;

上面这行表示浏览器应该写入一条:

键名为 a, 键值为 1, xwenliang.cn 及其子域名在 /a 及其子路径下才可以读取的,有效期为 60 秒的,允许跨站读取的,只允许 https 环境的,不允许开发者读取的 cookie.


根据上面的规则,假如有两个子域名,例如:

a.xwenliang.cn
b.xwenliang.cn

我们可以在写入 cookie 的时候通过设置 domain=xwenliang.cn 直接写入根域名下,如此一来所有子孙域名都可以获取该 cookie. 与之类似的属性还有 path, 将 cookie 写入根路径 path=/, 其他所有子孙路径才可以获取该 cookie.

细心的同学可能会发现,有些 cookie 的前缀是有个 . 的,那有没有这个点代表什么意思呢?RFC6265 中详细解释了 cookie 的使用规范,其中就有对这个 . 的解释:有 . 代表允许子孙域名使用,没有 . 代表只允许当前域名使用不允许其他任何域名使用。 那么如何设置带不带这个 . 呢?这是浏览器的默认行为,如果 Set-Cookie 中明确带有 domain 声明,那设置的 cookie 就是带 . 的,如上面设置了 domain=xwenliang.cn 之后,浏览器写入的 cookie 就是 .xwenliang.cn , 如果不设置该声明,那写入的 cookie 只会是当前地址的 host,也就是 xwenliang.cn.


那么假如前端部署在 a.xwenliang.cn, 后端部署在 b.xwenliang.cn, 我在页面上直接发起对后端的请求,cookie 能被带上吗?毕竟我 cookie 是设置在 xwenliang.cn 下的。

答案是不能。并且后端设置的 cookie 也会被忽略。

a.xwenliang.cn 直接发起对 b.xwenliang.cn 带有同源策略限制的请求,如 XMLHttpRequest 或 fetch 请求,是无法携带任何 cookie 的,包括 domain 设置为根域名 xwenliang.cn 的 cookie, 要想携带 cookie 需要使用 Cross-Origin Resource Sharing. 而 script、img 等非同源策略限制性标签发起的请求则可以直接携带根域名下的 cookie.

同样 a.xwenliang.cn 发起的带有同源策略限制的请求,b.xwenliang.cn 也是无法直接写入 cookie 的。要想写入也需使用 Cross-Origin Resource Sharing 或者使用 script、img 等非同源策略限制性标签发起的请求。

想想我们为什么不使用 script、img 等这些没有同源策略限制的标签来管理跨域 cookie 呢?正是因为他们没有同源策略限制,我们无法限定它们的使用范围。


以上这种情形,早在 2019 年之前,是可以通过设置 Cross-Origin Resource Sharing 来解决的,但这种行为很可能会被恶意利用,比如第三方广告追踪,所以各大厂商纷纷提出要制裁这种方案。

web.dev 提出了过渡方案 samesite, 只有设置了 samesite=none 的 cookie, 才能被上述方案读取。

Google Chrome 最早于 2020 年 2 月 推出的 Chrome 80 默认启用了该设置,如果要取消该设置需要到 chrome://flags/ 中将 SameSite by default cookiesCookies without SameSite must be secureSchemeful Same-Site 这三项置为 Disabled.

而 Apple 更绝,直接在 macOS 10.14 和 iOS 12 全面禁用了第三方 cookie,即使设置了 samesite=none 也是无效的。

使用 Chrome 89.0.4389.114 测试过程中发现,同个主域下的子域名无需设置 samesite=none 或者开启上述三项设置,都可以直接设置成功。如 a.xwenliang.cn 请求 b.xwenliang.cn 的接口,只需要像以前一样设置 Cross-Origin Resource Sharing 即可。

不同主域则需要设置 samesite=none; secure;, 否则会提示需要有该设置:

This Set-Cookie didn’t specify a “SameSite” attribute and was defaulted to “SameSite=Lax”, and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The Set-Cookie had to have been set with “SameSite=None” to enable cross-site usage.

既然 samesite=none; secure; 需要同时设置,那接口提供方只能是 https 的地址了,否则 secure 设置不生效。那如果是 https 的地址请求 http 的接口会怎样呢?直接报错啦:

Mixed Content: The page at ‘https://devfuns.com/index.html’ was loaded over HTTPS, but requested an insecure resource ‘http://qdxinxi.com/’. This request has been blocked; the content must be served over HTTPS.