🍪 让你从此告别“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 永不迷路!🚀

相关推荐
universe_012 分钟前
day25|学习前端js
前端·笔记
MrSYJ6 分钟前
UserDetailService是在什么环节生效的,为什么自定义之后就能被识别
java·spring boot·后端
张志鹏PHP全栈7 分钟前
Rust第一天,安装Visual Studio 2022并下载汉化包
后端
Zuckjet7 分钟前
V8 引擎的性能魔法:JSON 序列化的 2 倍速度提升之路
前端·chrome·v8
MrSkye7 分钟前
🔥React 新手必看!useRef 竟然不能触发 onChange?原来是这个原因!
前端·react.js·面试
estarlee13 分钟前
公交线路规划免费API接口详解
后端
wayman_he_何大民14 分钟前
初识机器学习算法 - AUM时间序列分析
前端·人工智能
juejin_cn15 分钟前
前端使用模糊搜索fuse.js和拼音搜索pinyin-match提升搜索体验
前端
无责任此方_修行中26 分钟前
从 HTTP 轮询到 MQTT:我们在 AWS IoT Core 上的架构演进与实战复盘
后端·架构·aws
考虑考虑32 分钟前
postgressql更新时间
数据库·后端·postgresql