在前后端分离架构中处理跨域 Cookie
在前后端分离架构中,当 A 域名请求 B 域名的接口时(跨域),浏览器出于安全考虑,默认会进入 “无凭证模式”。想让 Cookie 正常工作,必须打通客户端与服务端的双向通道。
1. 客户端的核心开关:Request.credentials
fetch API 的 credentials 选项决定了浏览器是否在请求中携带 Cookie、HTTP Auth 或 SSL 证书。它有三个值:
🔹 omit (忽略)
- 行为:绝对不带 Cookie。
- 发送时:即使浏览器有目标域名的 Cookie,请求头里也不会有 Cookie 字段。
- 接收时:即使服务器返回
Set-Cookie,浏览器也直接丢弃,不写入 Storage。 - 场景:不需要鉴权的公开 API,或不想暴露隐私的请求。
🔹 same-origin (默认值)
-
行为:仅同域带 Cookie。
-
发送时:
- 请求
api.same-domain.com-> 带 Cookie。 - 请求
api.cross-domain.com-> 不带 Cookie。
- 请求
-
接收时:同理,跨域响应的
Set-Cookie会被忽略。 -
场景:传统的单体应用或严格同域部署的前后端。
🔹 include (包含)
-
行为:总是带 Cookie (包括跨域)。
-
发送时:无论请求谁,只要本地有该域名的 Cookie,就会带上。
-
接收时:允许跨域响应写入 Cookie。
-
关键限制:
- 这并不意味着“想发就发”。开启
include后,浏览器会强制检查服务器的 CORS 响应头。如果服务器没授权,浏览器会报 CORS Error,并且拒绝让 JS 读取响应内容。
- 这并不意味着“想发就发”。开启
2. 服务端的通行证:CORS Headers
当客户端开启 credentials: 'include' 发起跨域请求时,这是浏览器最敏感的时刻。服务器必须严格返回以下 Header,缺一不可:
🔒 Access-Control-Allow-Credentials: true
- 含义:告诉浏览器,“我同意你带着 Cookie 进来,也同意让你把
Set-Cookie带回去”。 - 后果:如果没加这行,浏览器会拦截 Cookie 发送,或者忽略
Set-Cookie。
🔒 Access-Control-Allow-Origin (严禁通配符)
-
含义:明确列出允许跨域的来源域名。
-
规则:
- ✅ 合法:
Access-Control-Allow-Origin: https://client.example.com - ❌ 非法:
Access-Control-Allow-Origin: *
- ✅ 合法:
-
原因:当涉及到凭证(Credentials)时,浏览器认为
*太不安全了。你必须显式指定来源域名。通常后端代码会动态读取请求头里的Origin字段,然后回填到这里。
3. 典型失败案例 (Debug 指南)
案例 A:前端加了 include,后端没改
- 请求:
fetch(..., { credentials: 'include' }) - 响应:后端默认配置,无 Credentials 头。
- 结果:浏览器控制台报错
CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.请求失败,Cookie 未发送。
案例 B:后端用了通配符 *
- 响应:
Access-Control-Allow-Origin: *+Access-Control-Allow-Credentials: true - 结果:报错。规范禁止这两者同时出现。必须把
*换成具体域名。
案例 C:一切配置正确,但 Cookie 还是没存进去
- 原因:虽然 CORS 过了,但 Cookie 自身的
SameSite属性可能是Lax(默认)。 - 解决:跨域场景下(特别是 iFrame 或 POST 请求),Cookie 必须设置
SameSite=None; Secure。
4. SameSite 和 Secure
SameSite 和 Secure 是 Cookie 的两个极其重要的属性,专门用来控制 Cookie 在不同场景下的发送行为。简单理解:
- Secure:管 加密(只能 HTTPS 发,HTTP 不发)。
- SameSite:管 隐私和CSRF(别人家的网站能不能发我家的 Cookie?)。
🔒 Secure (安全)
-
含义:这个 Cookie 极其重要,必须在加密通道里传输。
-
行为:
- 如果页面是
https://:浏览器会发送这个 Cookie。 - 如果页面是
http://:浏览器绝对不发送这个 Cookie。
- 如果页面是
-
强制要求:如果你把
SameSite设置为None(允许跨站),浏览器强制要求你也必须开启Secure。也就是说,跨站 Cookie 必须是 HTTPS 的。
🚧 SameSite (同站策略)
这是为了防止 CSRF(跨站请求伪造) 攻击而生的。它决定了:“当用户从 A 网站点击链接去 B 网站(或者 A 嵌入了 B 的资源)时,要不要带上 B 的 Cookie?”它有三个值:
1. Strict (严格)
-
含义:完全禁止跨站发送。
-
行为:
- 只有在当前网页 URL 和 API 域名完全一致时才发送。
-
场景:即使用户点击链接从
a.com跳转到b.com,这第一次跳转请求也不会带b.com的 Cookie(导致用户到了 B 站可能是未登录状态)。安全性最高,但体验极差。
2. Lax (宽松 - 默认值)
-
含义:限制跨站发送,但允许“导航”行为。
-
行为:
- 允许:用户点击链接跳转 (
<a>)、浏览器地址栏输入、GET 表单提交。 - 禁止:跨站图片 (
<img>)、iFrame、Ajax (fetch)、POST 表单。
- 允许:用户点击链接跳转 (
-
现状:这是现代浏览器(Chrome 80+)的默认值。
-
坑点:如果你在 H5 (
a.com) 里嵌入了 CDN 图片 (cdn.b.com),默认情况下浏览器是不会带 Cookie 的!这也是你这次踩坑的原因之一。
3. None (无限制)
- 含义:总是发送 (允许跨站)。
- 行为:无论你是同站还是跨站,无论是图片、iFrame 还是 Ajax,只要有 Cookie 就发。
- 前提:必须配合 Secure 属性一起使用 (
SameSite=None; Secure)。 - 场景:第三方登录、广告追踪、以及你的 CDN 鉴权场景(如果不用 iOS ITP 拦截的话)。
💡 总结图表
| SameSite 值 | Cookie 发送场景 | 允许跨站请求类型 | 强制 Secure? | 安全性 | 体验 |
|---|---|---|---|---|---|
| Strict | 仅同站 | 无 | 否 | 最高 | 差 (跳转丢失登录态) |
| Lax (默认) | 同站 + 顶级导航 | 链接跳转 (<a>)、GET 表单 | 否 | 中等 | 好 (平衡) |
| None | 任何情况 | 所有 (XHR, img, iframe 等) | 是 | 低 (需防 CSRF) | 最佳 (功能最全) |
项目踩坑点:H5 加载 CDN 图片属于 “第三方资源” 场景。默认是
Lax,所以不带 Cookie。如果要带,后端必须设置SameSite=None; Secure。(但即便这样,iOS 的 ITP 依然会拦截,所以 iOS 属于物理打击)。
5. 跨域 VS 跨站
-
Fetch/XHR 请求: 是 跨域 (Cross-Origin) 的。你需要处理 CORS 头(
Access-Control-Allow-Origin)。 -
Cookie: 是 同站 (Same-Site) 的。
- 如果你设置 Cookie 的
Domain=.client.com,那么这两个域名都可以访问该 Cookie。 SameSite=Lax或SameSite=Strict的 Cookie 可以在这两个域名之间的导航中发送(如果是同站请求)。
- 如果你设置 Cookie 的
本文使用 markdown.com.cn 排版