🍪 让你从此告别“Cookie去哪儿了?”

经典案发现场:

你(前端,抓狂中):

"后端大佬!我 fetch 加了 credentials: 'include'axios 也开了 withCredentials,为啥 Cookie 还是没带上啊?😭"

后端(一脸无辜):

"我响应头 Access-Control-Allow-Credentials: true 也给了啊,Access-Control-Allow-Origin 也指定你家域名了,咋回事?"

... 半小时的互相"甩锅"后,终于发现:Set-CookieSameSite 设成了 Lax,被 Chrome 默默拦截了!

别慌!

今天咱们就化身技术侦探,把跨域 Cookie 从"发送"到"接收"这条路上的所有关卡、陷阱、隐藏规则,一次性扒个干净!让你从此告别"Cookie去哪儿了"的灵魂拷问。


一、背景:为啥跨域带个Cookie这么"矫情"?

想象 Cookie 是张 VIP 通行证。浏览器这个"保安大队长"有个铁律:同源策略 ------ 只有协议 (http/https)、域名 (a.com)、端口 (8080) 完全一致,才算"自己人",才能自由带证进出。

一旦有不同(比如 http://a.com 访问 https://api.a.com 或者 http://b.com),就是"跨域"。为了安全(防 CSRF 攻击)和隐私(限制第三方追踪),浏览器又加了两道超级安检门:

  1. CORS (跨域资源共享):后端说了算!"你(前端)想带通行证(Cookie)?行,但得先问问我(后端)同不同意,同意的凭证(响应头)得给我看!"
  2. SameSite / Partitioned 属性:浏览器说了算!"就算后端同意了,你这通行证(Cookie)本身也得符合我的安全规定(属性设置)才能放行!"

任何一道安检没通过,你的 Cookie 就像人间蒸发,浏览器还常常不给明确报错! 这就是为啥它这么让人头大。


二、全流程图解

一张图理清 Cookie 的跨域之旅

js 复制代码
[你的网站] https://www.your-app.com
       │
       │ 1. 发起请求 (GET/POST...)
       │    ✅ 关键:前端正确设置"携带凭证"标志
       │        - fetch: `{ credentials: 'include' }`
       │        - axios: `{ withCredentials: true }`
       │        - xhr: `xhr.withCredentials = true`
       ▼
[目标API] https://api.data-service.com/user
       ▲
       │ 2. 后端响应
       │    ✅ 关键:设置正确的 CORS 响应头 + Set-Cookie
       │        - `Access-Control-Allow-Origin: https://www.your-app.com` (精确匹配!)
       │        - `Access-Control-Allow-Credentials: true`
       │        - `Set-Cookie: session=abc123; Domain=.data-service.com; Path=/; Secure; SameSite=None; Partitioned; HttpOnly`
       │
       │ 3. 浏览器安全审核
       │    ✅ 关键:Cookie 属性合规 + CORS 头匹配
       │        - 检查 SameSite, Secure, Domain, Partitioned 等
       │        - 核对 CORS 头是否允许来源和凭证
       │
       │ 4. 存储 Cookie (通过审核后)
       │
       │ 5. 下次请求自动携带 (符合 Domain/Path 规则时)
       └─── 请求头中出现:`Cookie: session=abc123`

⚠️ 重点警告: 1, 2, 3, 4, 5 任何一个环节出错,你的 Cookie 就会在某个环节"神秘消失"!


三、后端配置

如何让浏览器"安心收下"Cookie?(核心响应头 + Set-Cookie)

后端的响应是 Cookie 能否成功落地的第一步!请务必配置好这两组"通关文牒":

1. CORS 响应头 (缺一不可!)

js 复制代码
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.your-app.com  # 绝对不能用 '*'!必须是请求来源的确切域名!
Access-Control-Allow-Credentials: true                 # 明确告诉浏览器:允许前端带凭证(Cookie)来!
Access-Control-Allow-Methods: GET, POST, PUT, DELETE   # 允许的 HTTP 方法
Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header # 允许前端携带的请求头
Access-Control-Expose-Headers: X-Custom-Header         # (可选)允许前端读取的额外响应头
Access-Control-Max-Age: 86400                          # (可选)预检请求缓存时间(秒)

💡 Nginx/反向代理用户请注意: 如果你用了代理,确保这些 CORS 头是最终由你的应用服务器生成,或者你在代理层(如 Nginx)正确添加了它们!代理默认可能会吞掉应用设置的头。

js 复制代码
Set-Cookie: sessionId=xyz789abc;
           Domain=.data-service.com;    # 必须是目标 API 的域或其父域(如 .data-service.com 包含 api.data-service.com)。写错或范围太大(如 .com)会被拒收!
           Path=/;                      # 指定 Cookie 生效的路径。通常设 / 表示全站可用。
           Secure;                      # **强制要求**:Cookie 只通过 HTTPS 传输!本地 HTTP 调试时没有它可能行,上线 HTTPS 必挂!
           SameSite=None;               # **关键钥匙**:明确声明此 Cookie 可用于跨站(跨域)请求!用 Lax/Strict 跨域请求基本带不上。
           Partitioned;                # **未来通行证**:应对 Chrome 等浏览器逐步淘汰第三方 Cookie 的趋势,使用 Partitioned 属性是新的最佳实践,确保 Cookie 在跨站上下文中可用(需要 Chrome 114+,未来更重要)。
           HttpOnly;                    # (推荐)防止 JavaScript 读取,增强安全性(防 XSS)。
           Expires=Wed, 21 Aug 2025 07:28:00 GMT; # 或 Max-Age=2592000; 设置有效期。

🚫 后端常见踩坑点:

  • Domain 写错: 写成 '.com' (范围太大无效)、'api.data-service.com' (但前端是 'app.data-service.com' 且没设父域) 等。精准匹配或合理父域是关键!
  • 漏掉 Secure 本地 http://localhost 测试可能没事,一部署到线上 https 环境,Cookie 就因为缺少 Secure 标记被浏览器拒绝存储或发送!HTTPS 环境必须加!
  • SameSite 设成 Lax/Strict 这是导致"我明明配了 CORS 为啥 Cookie 还不带?"的头号元凶 !跨域请求(尤其是非导航的 POST/PUT/DELETE 或非顶级导航的 iframe 内请求)需要 'SameSite=None'!记住:跨域带 Cookie,'SameSite=None' + 'Secure' 是黄金搭档!
  • 忽略 Partitioned (未来大坑): 随着 Chrome 逐步限制第三方 Cookie,未设置 'Partitioned' 的 'SameSite=None; Secure' Cookie 在跨站 iframe 等场景下可能未来会失效尽早适配 'Partitioned' 是明智之举!

四、前端配置

如何让浏览器"心甘情愿带上"Cookie?(不同请求方式配置)

前端同学,光后端配好了还不够!你得在发起请求时,明确打上"我要带通行证(Cookie)"的信号!而且不同请求库/方式,写法各异,一个字母都不能错!

请求方式 正确姿势 (✅) 错误示范 (❌) / 关键点
fetch fetch('https://api.com/data', { credentials: 'include' }) 不写 credentials 或写成 'same-origin' (只带同源 Cookie)
axios axios.get('https://api.com/data', { withCredentials: true }) 拼错 withCredentials (如 withCredential 少个 s),或 true 写成小写 true (JS 没事,但易错)
XMLHttpRequest const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.withCredentials = true; xhr.send(); 顺序很重要! 必须在 xhr.open() 之后,xhr.send() 之前设置 withCredentials
<form> 提交 <form action="https://api.com/submit" method="post" crossorigin="use-credentials"> 漏掉 crossorigin="use-credentials" 属性 (现代浏览器需要)
<img>/<script> <img src="https://track.com/pixel" crossorigin="use-credentials"> 漏掉 crossorigin="use-credentials" 属性。注意:即使带了,能否成功也受 Cookie 本身 SameSite 等属性限制。

📌 核心口诀: fetchincludeaxioswithCredentialsxhr 认准 withCredentials 属性且注意设置时机! 表单和资源标签记得加 crossorigin="use-credentials"


特别注意: withCredentials是一个特殊的头,和baseURL同一层级设置,其配置后在浏览器请求头的面板中是不可以见的。不能直接在header头中配置,否则cookie不会携带、设置cookie也不会成功。


五、调试宝典:Cookie丢了?按步骤"破案"!

当 Cookie 神秘失踪时,别慌!拿起浏览器开发者工具 (DevTools),化身福尔摩斯,按顺序排查:

  1. 打开 DevTools -> Application 标签页。
  2. 左侧选择 Storage -> Cookies
  3. 在域名列表中找到你的 目标 API 的域名 (如 api.data-service.com)。
  4. 查看你期望的 Cookie (如 sessionId) 是否存在。
js 复制代码
    *   **不存在?** 问题大概率出在**后端 `Set-Cookie` 或浏览器安全策略拦截**上!立刻进行:
        *   检查 **Network 面板** 中对应 API 响应的 **`Set-Cookie` 头** 是否存在且格式正确(尤其 `Domain`, `Secure`, `SameSite=None`)。
        *   检查 **Console 面板** 有没有浏览器发出的黄色⚠️警告!常见如:
            *   `This Set-Cookie was blocked because it had the "SameSite=Lax" attribute but came from a cross-site response...` -> `SameSite` 设错了!
            *   `This Set-Cookie was blocked because it had the "Domain" attribute with a value that is a public suffix...` -> `Domain` 设置范围过大无效。
            *   `This Set-Cookie was blocked because it was not sent over a secure connection and had the "Secure" attribute.` -> 本地 HTTP 测试但 Cookie 设了 `Secure`。
    *   **存在?** 太好了!进入第二步。
  1. Network 标签页中,找到你怀疑没带 Cookie 的那个 具体请求
  2. 选中该请求,查看右侧的 Headers 选项卡。
  3. 展开 Request Headers 部分。
  4. 仔细找,看有没有 Cookie: your_cookie_name=value... 这一行。
js 复制代码
    *   **有!** 恭喜!Cookie 成功带上了!问题可能在后端处理逻辑或后续环节(但这已不属于跨域携带问题)。
    *   **没有!** 问题在于:浏览器有 Cookie,但**这次请求没把它带出来**!重点排查:
        *   **前端"携带凭证"标志**:确认你的 `fetch`/`axios`/`xhr` 配置**绝对正确无误**(回看第四部分表格)。
        *   **Cookie 的 `Path` 和 `Domain` 匹配**:请求的 URL 路径是否在 Cookie 的 `Path` 范围内?请求的域名是否匹配 Cookie 的 `Domain` 或其子域?
        *   **`Secure` Cookie 发给了 HTTP?** 如果 Cookie 有 `Secure` 标记,但当前页面是 `http://`,请求也是 `http://`,浏览器不会发送它。确保全站 HTTPS。
        *   **`SameSite` 严格限制?** 虽然你存了 `SameSite=None` 的 Cookie,但某些非常严格的上下文(如某些 iframe 沙盒环境)可能仍有限制。检查 Console 警告。

https请求下secure无值或为false也会设置失败

设置成功的情况下,浏览器面板中会有两条记录,如下图(设置):

设置成功的情况下携带cookie,如下图(携带):

🔍 第三步:特别关注 - 预检请求 (OPTIONS)

对于非简单请求 (如用了 Content-Type: application/json 或自定义头 X-Whatever),浏览器会先发一个 OPTIONS 预检请求探路。

  • Network 面板找到这个 OPTIONS 请求。
  • 检查它的 Response Headers
    • 必须包含 Access-Control-Allow-Origin (你的确切来源) 和 Access-Control-Allow-Credentials: true
    • 如果请求带了自定义头,Access-Control-Allow-Headers 必须包含这些头名。
    • 如果 OPTIONS 请求失败 (状态码非 2xx),后面的正式请求根本不会发出,自然带不了 Cookie。预检失败是跨域问题的另一大常见原因!

六、小结速查表:关键点一网打尽!

环节 关键动作 避坑要点
后端 CORS Access-Control-Allow-Origin: <精确来源> + Access-Control-Allow-Credentials: true 绝对不能用 '*'
后端 Set-Cookie Domain=<正确域>; Secure; SameSite=None; Partitioned (Path/HttpOnly 按需) Domain 别乱写;SameSite=None 必须SecurePartitioned 是未来必备!
前端携带凭证 fetch: credentials: 'include' axios: withCredentials: true xhr: xhr.withCredentials = true 拼写/值要绝对正确! xhr 顺序 要对 (open 后, send 前)!
调试 - 存储 Application > Cookies > 目标域下是否存在? 看 Console 警告! 没有?查 Set-Cookie 响应头和 Console 拦截警告!
调试 - 携带 Network > 具体请求 > Request Headers > 找 Cookie: ... 有存但没带?查前端凭证配置、Cookie 的 Path/Domain 匹配、是否 HTTPS 环境、SameSite 是否真 None!
调试 - 预检 Network > OPTIONS 请求 > 状态码和 Response Headers 预检失败 (非 2xx) 正式请求不发!检查 OPTIONS 返回的 CORS 头是否齐全正确!

把跨域 Cookie 想象成一次跨国快递

  1. 发件人 (后端):

    • 准备合规包裹 (Set-Cookie) :地址 (Domain) 邮编 (Path) 要准,贴好"易碎品"(HttpOnly)、"空运标"(Secure)、"海关申报单"(SameSite=None + Partitioned),写好有效期 (Expires/Max-Age)。
    • 开具通关许可 (CORS Headers) :明确指定收件国 (Allow-Origin),声明允许携带特殊物品 (Allow-Credentials: true),列出允许的运输方式和物品清单 (Allow-Methods/Headers)。
  2. 快递员 (浏览器):

    • 严格安检:核对包裹信息是否合规(Cookie 属性),检查通关许可是否有效且匹配(CORS 头)。任何一项不符,直接扣留包裹(丢弃 Cookie)或退回(Console 警告)!
    • 安全运输 :按规则 (Secure) 运送包裹。
  3. 寄件人 (前端):

    • 正确下单 (credentials/include/withCredentials) :下单时必须明确勾选"寄送特殊物品/凭证" 的选项!选错或漏选,快递员根本不会去取件(不发送 Cookie)!

只有发件人包裹包得好、许可开得对,快递员安检过得去,寄件人下单下得准,这份珍贵的 Cookie "跨国快递" 才能准确、安全地送达目的地!

另外,后端通常会通过第三方库直接配置跨域,无需像上面一样逐一配置。如:

nestjs中的配置(内置支持):

js 复制代码
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
const app = await NestFactory.create<NestExpressApplication>(AppModule);
  app.enableCors({
    origin: (origin, callback) => {
      // 允许的origin
      const allowedOriginRegexList = [
        /your-app\.com/,
        /localhost/,
        /\d+\.\d+\.\d+\.\d+/,
        // 其它域名...
      ];
      // 本地调试放行
      // if (!origin) return callback(null, true);
      const isOk = allowedOriginRegexList.some((reg) => reg.test(origin));
      callback(null, isOk);
    },
    credentials: true,
    allowedHeaders: ['Content-Type', 'Authorization'],
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
  });

fastify中的配置:

javascript 复制代码
import cors from 'fastify-cors'
// 跨域白名单
app.register(cors,  {
  origin: [
    /your-app\.com/,
    /localhost/,
    /\d+\.\d+\.\d+\.\d+/,
    // 其它域名...
  ],
  credentials: true
})

其它后端框架同理配置接口。

掌握这份通关手册,仔细对照每个环节,你一定能告别 Cookie 跨域丢失的烦恼!Cookie 永不迷路!🚀

相关推荐
想用offer打牌2 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX3 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法4 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端