记一次 CORS 深水坑:开启 withCredentials 后Response headers 只剩 content-type

背景与现象

  1. 前端业务需要读取接口返回的自定义响应头(如 x-debug-id、x-trace-id 等),用来作为链路追踪。
  1. 浏览器 Network 面板能看到这些头,但在代码里读取接口返回 res.headers 时,只剩"简单响应头"(几乎只剩 content-type)。

排查过程:从"封装锅"到"CORS withCredentials"的反转

一开始以为是封装的请求库有问题,按"最可疑处优先"的思路做了这几步:

  1. 分析封装请求库是否有拦截器对 headers 字段做了限制过滤返回,排查下来无可疑点。

  2. 对比其他请求方式:axios、xhr 请求。

    javascript 复制代码
    import axios from 'axios'
    const res = await axios.post('xxxxxxx')
    • 结果正常!!!通过控制台打印可看到返回的 response headers 能看到更多头。
  3. 控制变量定位

    • 逐步排除了 URL、方法、请求体等差异,只有 "一个变量" 差异:withCredentials 。

    • 现象:

      • 打开 withCredentials: true → 只能读到 content-type 响应头。
      • 关掉 withCredentials: false → 可读到更多自定义头。
    • 基本锁定方向:与 "withCredentials" 传参相关,日常开发中使用请求库初始化随手就传入配置: withCredentials: true。

根因:带凭证的跨域请求(withCredentials)触发更严格的 CORS 规则

  1. JS 可读响应头是受限的:默认只能读"简单响应头"(Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)。
  1. 要读自定义响应头:后端必须设置 Access-Control-Expose-Headers,而且在带凭证请求下更严格:

    • 不能用 * 通配暴露响应头(会被忽略)

    • 必须显式列出要暴露的每个响应头(如 x-request-id, x-debugger-id),下图可看到 expose-headers 设置了通配符* ,所以无法显示。

  1. 带凭证的额外要求:

    • Access-Control-Allow-Origin 必须是精确来源(不能是 *)

    • cors 必须返回 Access-Control-Allow-Credentials: true

问题拓展:什么情况下浏览器会默认携带cookie

1.请求与当前页面必须"同源"

浏览器遵循同源策略(Same-Origin Policy) ,仅当请求的目标URL与当前页面URL的协议、域名、端口三者完全一致时,才会默认携带Cookie。

  • 同源定义 :

    若当前页面URL为 https://example.com:8080/page,则以下请求为同源:

    • https://example.com:8080/api/data(协议、域名、端口均一致)
      以下请求为非同源(默认不携带Cookie):
    • http://example.com:8080/api/data(协议不同:http vs https)
    • https://sub.example.com:8080/api/data(域名不同:sub.example.com vs example.com)
    • https://example.com:80/api/data(端口不同:80 vs 8080)
2. Cookie自身属性未限制携带行为

即使请求同源,若Cookie设置了以下属性,浏览器仍可能默认不携带:

Cookie属性 作用 对"默认携带"的影响
Secure 仅允许HTTPS协议的请求携带Cookie。 若当前页面为HTTP协议,而Cookie设置了Secure,则请求时默认不携带(因HTTP不满足HTTPS条件)。
SameSite=Strict 仅允许"完全同源"的请求携带(禁止跨站请求携带,如从A网站跳转至B网站的请求)。 同源请求下仍默认携带;但跨站请求(即使目标URL与当前页面同源,如从外部链接直接打开同源页面)可能被限制(需结合浏览器实现)。
HttpOnly 禁止JavaScript通过document.cookie访问Cookie,但不影响请求携带。 不影响默认携带,仅限制前端脚本读取,请求时浏览器仍会自动附加。

2. 相关机制

浏览器默认携带Cookie的行为,本质是 "同源信任"与"安全隔离" 平衡的结果,核心机制包括:

1. 同源策略(Same-Origin Policy)

同源策略是浏览器的核心安全机制,目的是防止不同源的网站非法访问彼此的资源(包括Cookie)。对于同源请求,浏览器认为"可信",因此自动附加Cookie;对于非同源请求,默认"不信任",需额外配置才允许携带Cookie(见下文"跨域场景")。

2. Cookie的"作用域"与"路径"匹配

Cookie的Domain和Path属性定义了其"作用域",浏览器仅会向匹配Domain和Path的请求携带Cookie:

  • Domain:指定Cookie所属域名(如Domain=example.com,则a.example.com和b.example.com均可携带,但需满足同源或跨域配置)。
  • Path:指定Cookie生效的路径(如Path=/api,则仅/api/data等路径的请求会携带,/page路径不携带)。

默认情况下 ,若Cookie未显式设置Domain和Path,浏览器会自动将其作用域设为"当前页面的域名+当前路径"(如当前页面为https://example.com/admin/list,则Cookie默认Domain=example.com、Path=/admin/),此时同源且路径匹配的请求会默认携带。

3. 跨域请求的"非默认"携带规则

非同源请求(跨域)下,浏览器默认不携带Cookie,需同时满足以下条件才会携带(此时不属于"默认"行为,需前端显式配置):

  • 前端配置 :通过XMLHttpRequest或fetch设置withCredentials: true(如Axios中配置{ withCredentials: true })。
  • 后端配置 :响应头中返回Access-Control-Allow-Credentials: true,且Access-Control-Allow-Origin必须指定具体源(不能为*通配符)。
  • Cookie属性 :Cookie需设置SameSite=None(允许跨站携带)和Secure(仅HTTPS下生效),否则浏览器会因安全限制拒绝携带。

问题修复

这里不赘述了,相信看完上面 原因分析 和 问题拓展,大家心里已有答案~

相关推荐
lovingsoft27 分钟前
Chrome Lighthouse 详解
前端·chrome
Qlly37 分钟前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝41 分钟前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦41 分钟前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡1 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI1 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95271 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人1 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载1 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码
葡萄城技术团队2 小时前
用 SpreadJS 数据验证实现省市区三级联动
前端