跨域请求携带Cookie全解析:SameSite、CORS与前端配置指南
发布时间:2026/9/16 6:54:57 锦皓数字建站

跨域请求携带 Cookie这是我见过最容易翻车的问题之一。很多朋友本地联调一切正常代码一上线就发现“登录态丢了”“接口 401 了”第一反应是后端跨域配置没弄好结果折腾半天发现根本不是那么回事。还有人把请求从 HTTP 切到 HTTPSCookie 突然就没了或者换了高版本 Chrome 就不带 Cookie 了这类问题十有八九都出在浏览器对跨域 Cookie 的管控机制上。如果你在做对接第三方登录态、单点登录或者写自动化脚本需要保持会话都会遇到这个坎。今天我把跨域请求自动携带 Cookie 的完整条件拆开讲一遍前端要开什么开关、服务端要配什么响应头、Cookie 自己又要满足什么条件以及最常见的那几个坑到底是怎么踩出来的。1. 跨域Cookie为什么带不上先把原理捋清楚1.1 同源策略和第三方Cookie两套规则别混为一谈先说一个最常见的误区很多人觉得“跨域请求不带 Cookie”就是因为同源策略。这话只对了一半。CORS 同源策略管的是“浏览器能不能让你页面里的 JS 读取到跨域响应内容”而 Cookie 带不带还要看另一套规则Cookie 的归属和第三方 Cookie 管控。Cookie 不是跟页面绑定的而是跟域名绑定的。浏览器在发请求的时候会根据“请求的目标地址”去匹配本地存的 Cookie只有 Domain、Path、协议等属性都对得上才会带上。所以“跨域请求无法带 Cookie”这个说法其实不严谨——从 a.com 的页面去请求 b.com如果 b.com 确实在浏览器里种过匹配的 Cookie理论上这个 Cookie 是会跟着请求发出去的前提是浏览器允许这种“第三方场景”发送。这里引出了第三方 Cookie 的概念。对你当前打开的页面 a.com 来说b.com 给浏览器种的 Cookie就是第三方 Cookie。浏览器对第三方 Cookie 的管控非常严尤其是 Chrome 和 Edge 高版本。而如果页面是 a.example.com请求是 b.example.com虽然对 CORS 来说是跨域但在 Cookie 的 SameSite 判定里它们都属于 example.com 下属于 same-site不会触发第三方 Cookie 拦截。这就是为什么“跨域”和“跨站”必须分开理解。1.2 一个跨域Cookie请求能成功要过三道关假设页面在 a.com接口在 b.com你想让浏览器在跨域请求里自动带上 b.com 域下的 Cookie整个链路要同时满足下面这些条件前端请求必须显式声明“我要带凭证”XHR 是withCredentials truefetch 是credentials: include。服务端响应头必须包含Access-Control-Allow-Credentials: true同时Access-Control-Allow-Origin不能是*必须明确指向具体来源。请求要带的 Cookie它的Domain必须匹配请求目标 b.comPath也得覆盖请求路径。Cookie 的SameSite属性要允许跨站发送通常得是SameSiteNone同时还要带Secure也就是要求 HTTPS。如果 Cookie 是会话级、没过期时间浏览器一关就没了那你看到的“失效”其实是正常丢失。浏览器本身的隐私设置不能主动拦截第三方 Cookie。如果 Cookie 带Secure属性页面和接口还必须跑在 HTTPS 下。这一串条件环环相扣任何一环断了表现就是“浏览器没带 Cookie”。下面我逐一展开讲每一条都配上实际配置和排查方法。2. 前端必须打开的开关credentials配置不当等于白干2.1 XHR、fetch、axios里怎么设withCredentials先看最基础的 XMLHttpRequest。很多老项目还在用 XHR写法是const xhr new XMLHttpRequest(); xhr.open(GET, https://b.com/api/user); xhr.withCredentials true; xhr.send();withCredentials这个属性默认是false。只要不设成true浏览器发跨域请求时就不会带 Cookie哪怕 Cookie 本身条件全满足。这个东西没有全局开关所以你需要在每次请求前设置或者封装在公共请求函数里。如果你用的是 fetch对应的字段叫credentialsfetch(https://b.com/api/user, { credentials: include, });fetch 的默认值是same-origin也就是同源请求时带 Cookie跨域请求默认不带改成include后跨域请求也会带上 Cookie。还有一个值omit任何情况下都不带一般用不到。axios 在浏览器底层还是 XHR所以你要设的是withCredentials// 单个请求 axios.get(https://b.com/api/user, { withCredentials: true, }); // 全局默认 axios.defaults.withCredentials true;我之前见过不少人只在登录接口配置了withCredentials后面的业务接口全是默认值结果登录成功之后后续请求全都不带会话后端每个接口都返回 401。排查这类问题第一步就是全局搜一下withCredentials到底配了哪几个地方。2.2 前端配置里容易踩的坑别靠手塞Cookie一个经常被误解的点是既然浏览器不自动带那我手动在请求头里加一个Cookie字段行不行不行。浏览器把Cookie列为forbidden header name也就是 unsafe headerJS 里设置它会被直接忽略控制台还会报错Refused to set unsafe header Cookie。Cookie 的携带是浏览器的自主行为只能通过开关来控制不能由 JS 手动指定。另外fetch 的credentials: include不能和mode: no-cors一起用否则会直接抛异常。原因是 no-cors 模式本身就是一种“降级模式”浏览器不允许在这个模式下做带凭证的请求这是安全底线。还有一个细节如果你的项目里用了 jQuery$.ajax对 CORS 的支持也是通过xhrFields配置$.ajax({ url: https://b.com/api/user, xhrFields: { withCredentials: true } });jQuery 默认不带凭证这和历史遗留代码配合跨域后就会出问题。如果你在维护老系统先搜一下项目里有没有类似的$.ajax调用比在服务端加一堆响应头要省事得多。3. 服务端响应头Allow-Credentials与Origin的配合关系3.1 为什么Access-Control-Allow-Origin不能是*前端把withCredentials打开之后浏览器不会立刻就把 Cookie 发出去它还要先检验服务端的响应头是否允许这种“带凭证的跨域请求”。前面说了服务端必须返回Access-Control-Allow-Credentials: true而且这个头必须和Access-Control-Allow-Origin配合。Access-Control-Allow-Origin如果配成*再加上Access-Control-Allow-Credentials: true浏览器会直接拒绝这次跨域请求。道理很简单既然允许携带凭证那就必须明确告诉浏览器“我只信任这些具体的来源”如果对所有域名都开放带凭证访问那等于把用户的登录态暴露给任意恶意网站。这是浏览器层面的硬性安全限制不是后端想不想配的问题。所以生产环境里这个 Origin 要写明确Access-Control-Allow-Origin: https://a.com Access-Control-Allow-Credentials: true有些项目为了省事会动态回显请求头里的Origin比如 Nginx 里写add_header Access-Control-Allow-Origin $http_origin;这样任何来源都能通过但带凭证的接口这么做风险很高。我在实践中一般会在服务端做一个白名单把线上、预发、测试环境的域名都列进去其他源一律不带凭证。3.2 后端框架和Nginx里怎么配置服务端如果用的是 Express配合cors中间件最直接const cors require(cors); app.use(cors({ origin: https://a.com, credentials: true, }));如果服务端用的是 Spring Boot可以在方法上或者类上写CrossOriginCrossOrigin(origins https://a.com, allowCredentials true)如果项目部署在 Nginx 后面跨域头通常由 Nginx 直接补location /api/ { add_header Access-Control-Allow-Origin https://a.com; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS; add_header Access-Control-Allow-Headers Content-Type, Authorization, X-Requested-With; if ($request_method OPTIONS) { return 204; } }这里要注意两点。第一add_header指令在 Nginx 里不是无脑叠加如果某个 location 里存在add_headerNginx 会忽略继承自上一层的add_header所以最好把该配的头都在同一处写完。第二如果请求会返回 4xx 或 5xx某些add_header的响应头可能不会出现在错误响应里保险起见可以加always参数比如add_header Access-Control-Allow-Origin https://a.com always;否则遇到 401/403 这种状态码CORS 头丢了浏览器还是会拦截前端看到的依然是跨域错误。3.3 预检请求OPTIONS也不能马虎跨域请求分两种简单请求和预检请求。简单请求的要求比较苛刻必须是用 GET、POST、HEAD 方法并且 Content-Type 只允许application/x-www-form-urlencoded、multipart/form-data、text/plain。只要你的请求带了application/json的 Content-Type或者塞了自定义请求头浏览器就会先发一个OPTIONS预检请求确认服务端允许之后再发真正的业务请求。预检请求本身一般不会携带 Cookie但服务端必须在OPTIONS响应里正确返回Access-Control-Allow-Methods和Access-Control-Allow-Headers。如果预检没通过真实请求根本不会发出去自然也不会带 Cookie。所以排查问题的时候看到 Network 面板里只有一个OPTIONS请求没有后续的 GET/POST那问题大概率出在预检响应头配置不全而不是 Cookie 本身。4. Cookie自己的门槛SameSite、Secure和Domain决定了命运4.1 SameSite属性高版本浏览器不带Cookie的元凶如果你确认前端开关开了、服务端头也配了请求头里依然没有 Cookie那十有八九是卡在SameSite上。Chrome 80 之后SameSite的默认值变成了Lax这是很多跨域 Cookie 突然失效的根源所以“Chrome 98 无法携带 Cookie”这类问题才会变成热搜。SameSite有三个值Strict最严格。所有跨站请求都不带 Cookie包括用户从其他网站点链接跳转过来浏览器也不会带。Lax默认值。只有“顶层导航”场景下的 GET 请求会带 Cookie比如用户手动输入网址、点普通链接跳转但 XHR、fetch、图片加载、iframe 这些跨站请求都不带。None允许跨站发送但要求必须同时设置Secure属性也就是只能跑在 HTTPS 下。跨域 AJAX 要带 CookieSameSiteLax是不满足条件的你必须把 Cookie 设为SameSiteNone; Secure。这里最容易踩的坑是只写SameSiteNone不写SecureChrome 会直接拒绝这个 Set-Cookie响应头明明发了 CookieApplication 面板里却看不到。还有一种容易混淆的情况前面提过子域之间的“跨域”不一定“跨站”。SameSite是按 eTLD1 来判定站点的比如a.example.com请求b.example.com在 CORS 里是跨域但两个域同属example.com属于 same-siteLax不会拦。所以你不能一遇到跨域带不上 Cookie就直接怪 SameSite要先把站点关系判断清楚。4.2 Secure、HttpOnly、Path、Domain四个属性各自管什么Secure属性要求请求必须走 HTTPS 才允许携带该 Cookie。你本地开发如果是http://localhost有些浏览器会把 localhost 视为安全上下文但一旦部署到测试环境、生产环境地址不是 HTTPSSecure的 Cookie 就永远发不出去。反过来说如果线上已经全站 HTTPS而 Cookie 没加Secure功能虽然能跑但安全性差一截等于允许明文传输会话凭证。HttpOnly经常被人误解。它的作用只是禁止 JS 通过document.cookie读写并不影响浏览器自动发送。很多人看到 Application 面板里 Cookie 没标 HttpOnly 才放心其实不对HttpOnly能防的是 XSS 脚本偷 Cookie跨域携带不受它影响。如果你的脚本里document.cookie读不到某个 Cookie先看看是不是 HttpOnly这个情况是正常的不是故障。Path和Domain决定了 Cookie 的匹配范围。Cookie 只在请求 URL 的路径满足Path前缀时才会带上如果你在/api下种了 Cookie请求/public就不会带。Domain默认是 host-only也就是只匹配种 Cookie 的那个主机。想要a.example.com和b.example.com共享 Cookie可以在Set-Cookie里显式写Domainexample.com但完全不同的两个顶级域比如a.com和b.com是没法通过 Domain 共享同一个 Cookie 的这时候只能靠浏览器自动携带目标域的第三方 Cookie。4.3 会话Cookie和持久化Cookie为什么Cookie会突然失效Cookie 还有个过期属性很多人忽略了。如果Set-Cookie响应头里没有设置Expires或Max-Age那这就是一个会话 Cookie浏览器关闭后就会清掉。用户第二天再来登录态自然没了。像“京东签到 Cookie 总是失效”“网盘登录 Cookie 持久化”这类问题多数都要检查两个点一是服务端Set-Cookie时到底有没有给有效期二是有效期是不是太短。解决方法是Set-Cookie里带上Max-AgeSet-Cookie: sessionabc123; Domainapi.example.com; Path/; SameSiteNone; Secure; Max-Age86400上面这个 Cookie 有效期是 86400 秒也就是 24 小时适合用来做短时会话。如果是“记住我”或者签到类功能你可以把Max-Age调大比如 30 天。但要注意Cookie 有效期一到浏览器就会自动删除前端再做任何配置都没用只能走刷新登录态的接口续期或者让用户重新登录。5. 实战排查联调正常线上失败问题出在哪5.1 前端代理为什么会在本地“掩盖”跨域问题先说一个很坑的场景本地用 Vue 脚手架或者 Webpack 的 devServer 配了代理页面请求/api开发服务器把它转发到https://api.example.com。从浏览器的角度看页面在localhost:5173请求也是发到localhost:5173这是同源请求根本不会触发 CORSCookie 也就不会遇到跨域限制。所以本地联调尤其是登录功能往往一切正常。结果部署到线上前端页面跑在https://app.example.com接口在https://api.example.com浏览器的真实请求完全是另一回事——这是一个标准的跨域请求所有 CORS 和 Cookie 规则都会生效。这解释了为什么很多人本地跑得好好的上线就掉登录。如果你也想搞清楚“代理之后真实的请求地址到底是什么”直接看 DevTools 的 Network 面板请求行里会显示浏览器实际发出去的 URL而服务端收到请求后打印日志时加上 Host、Origin、Referer 字段也能看到最终的真实来源。代理还有一个副作用本地开发时种下的 Cookie 都是种在 localhost 下的生产环境域名一变这些 Cookie 全部对不上号。本质上不是“跨域配置坏了”而是环境变了之后整个 Cookie 体系需要重新适配。5.2 高版本Chrome/Edge不携带Cookie的排查顺序用 Chrome 或者 Edge 高版本的朋友遇到跨域 Cookie 丢失我建议按下面这个顺序排查别一上来就怀疑代码。第一步打开 DevTools 的 Application 面板找到 Cookies先确认目标域下到底有没有对应的 Cookie它的SameSite、Secure、HttpOnly、Expires分别是什么。如果这个 Cookie 根本不存在后面就不用排查了问题出在服务端为什么没种上。第二步切到 Network 面板找到那个失败请求看 Request Headers 里有没有Cookie。如果没有说明浏览器没带。接着看 Response Headers 里有没有Access-Control-Allow-Credentials: true以及Access-Control-Allow-Origin是不是*。第三步看响应头里有没有Set-Cookie如果有再看控制台有没有相关警告。Chrome 对不符合规范的 Cookie 会在控制台打警告比如 “This Set-Cookie was blocked because it had the SameSiteNone attribute but did not have the Secure attribute” 之类这句英文一般就是提示你SameSiteNone必须配Secure。第四步检查浏览器设置。如果用户在chrome://settings/cookies里开了“阻止所有第三方 Cookie”那你的SameSiteNone配置再对也没用浏览器直接不给第三方 Cookie 发出去。遇到这种场景要么引导用户放行要么项目架构上别死磕 Cookie改用 Token 方案。5.3 常见问题速查表我把实际开发中遇到的高频问题整理成了一个表格方便你对着排查。现象可能原因处理思路请求头里没有Cookie前端没开withCredentials/credentials全局搜索确认配置覆盖所有跨域请求请求头里没有Cookie且前端口开了SameSite默认Lax拦截设置SameSiteNone; Secure响应头有Set-Cookie但Application里看不到Secure属性缺失补上Secure确保页面和接口在HTTPS下本地代理正常线上掉登录代理掩盖了跨域问题按生产域名结构重新验证CORS和Cookie服务端Allow-Origin是*请求被拦截*与Allow-Credentials冲突改成明确的白名单OriginCookie自动消失过几天失效会话Cookie或过期时间太短Set-Cookie时加Max-Age并合理设置document.cookie读不到HttpOnly限制属正常浏览器仍会自动发送浏览器换一个就正常第三方Cookie策略差异检查隐私设置或按浏览器分别验证踩过几次坑之后我的体会是跨域带 Cookie 这种事能自己控制的只有前端开关和服务端响应头剩下的全是浏览器的政策和 Cookie 自身的属性在起作用。排查的时候不要猜直接按“请求头有没有带、响应头有没有种、Application 里 Cookie 属性对不对”这个顺序走基本都能定位。同样的机制往后也会越来越严如果项目条件允许长远看把会话凭证从 Cookie 迁到 Token 方案能省去不少跨域场景的麻烦。但短期内尤其是老系统、第三方登录、单点登录这些场景Cookie 还是绕不过去把这套规则吃透至少不会在浏览器升级的时候手足无措。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。