前端 withCredentials 打开后跨域失败,服务端要回哪几个头?

fetch 一写上 credentials: "include",或者老代码里把 xhr.withCredentials 设成 true,跨域请求突然全红。Network 面板里偶尔还能瞥见响应,页面脚本这边却只有一个含糊的失败。对照 MDN 的 CORS 指南 会发现:带凭证时,服务端头的写法比「随便回个 Allow-Origin: *」严一截,而且失败细节故意不对 JS 开放。

带凭证时浏览器在卡什么

MDN 写得很清楚:默认的跨域 fetch / XMLHttpRequest 不会带 Cookie 等凭证。你显式打开以后,响应里必须出现类似:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://app.example.com

这里的 Origin 不能是 *。文档在 credentialed requests 一节反复强调:带凭证时服务器必须回明确源,不能用通配符。若站点按请求里的 Origin 动态回写允许列表,响应里通常还要带 Vary: Origin,避免缓存把 A 源的头复用给 B 源。

预检(OPTIONS)如果存在,预检响应同样要表明允许凭证,实际请求才会继续。预检本身按规范不应夹带凭证。反例很常见:业务接口已经按源回写了,预检还在回 Access-Control-Allow-Origin: *,或者漏了 Allow-Credentials。浏览器一拦,前端 Promise 直接 reject。本地用浏览器插件「临时放行 CORS」调通了,一上预发又红,多半就是插件掩盖了头字段问题,别把那种环境当验收标准。

Allow-Headers / Allow-Methods 在带凭证场景里同样不要写 *,改成明确列表。需要暴露给前端读的响应头,再用 Access-Control-Expose-Headers 逐个点名。只回了 Allow-Origin 和 Allow-Credentials、却漏了自定义头白名单时,带 Authorization 或业务头的预检也会挂。

MDN 还说明:CORS 失败的具体原因对 JavaScript 不透明,代码只能知道出错了。要对着开发者工具控制台看细节,别指望在 catch 里读到「哪一个头不对」。这是安全设计,不是框架 bug。

和 Cookie 的 SameSite 不是同一层

CORS 头过了,只表示「这个源的脚本被允许读这次跨域响应」。Cookie 会不会被带上、第三方策略会不会扣掉,还要看 Set-Cookie 上的 SameSite,以及 Secure 有没有配上。SameSite 的 Lax / Strict / None(None 需 Secure)管的是 Cookie 何时跨站发送;这篇管的是脚本读响应时服务端要声明什么。两层都过,登录态跨域才站得住。只改其中一层,控制台报错看起来会很像。排障时先分清是「响应被 CORS 挡住」还是「请求根本没带上 Cookie」。WebView 或内嵌页里两边策略叠在一起时,更要按层拆,不要一上来就改前端 credentials 开关碰运气。第三方 Cookie 策略收紧以后,CORS 全绿但登录态仍丢的情况也会变多。这时候应回到 SameSite 那一层继续查。(站内若有 SameSite / WebView 登录排查帖,发布后可补互链。)

最小正反例

正例(示意,源换成你们前端真实源):

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

反例:开着 credentials,却回 Access-Control-Allow-Origin: *——浏览器会拒绝把响应交给脚本。另一类反例是实际请求头对了、预检仍是 * 或缺 Allow-Credentials,表现同样是跨域失败。第三类是 Origin 写死成生产域名,预发、本地改 host 后对不上,看起来像「偶发 CORS」。

核对顺序可以是:前端有没有真的开 credentials。响应(含预检)有没有同时带上 Allow-Credentials: true 和明确的 Allow-Origin。自定义方法与请求头在不在允许列表里。然后再去看 Cookie 的 SameSite。网关和源站各回一套 CORS 头时,以浏览器实际收到的那份为准,别只看源站日志。不要为了「先让请求通」把 Allow-Origin 改回 * 还开着凭证。这种组合官方文档已经写明会被拒绝。

你们线上是网关统一回 CORS,还是每个服务自己拼头?有没有预检和实际请求两套配置不一致的情况,可以回帖贴打码后的响应头。