在前后端分离架构中处理跨域 Cookie

48 阅读5分钟

在前后端分离架构中处理跨域 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

SameSiteSecure 是 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=LaxSameSite=Strict 的 Cookie 可以在这两个域名之间的导航中发送(如果是同站请求)。

本文使用 markdown.com.cn 排版