ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

跨域与CORS全解析:同源策略、预检请求、代理与排查实战

跨域与CORS全解析:同源策略、预检请求、代理与排查实战 1. 先把跨域这件事说透同源策略到底在防什么前端调接口调不通控制台红字刷一屏十有八九是跨域。这个场景我估计每个写过前后端分离项目的人都遇到过本地页面跑在http://localhost:5173后端接口挂在http://api.xxx.com点一下按钮Network 面板里请求状态是红的控制台里躺着一条Access to fetch at ... has been blocked by CORS policy。新手看到这个的第一反应往往是我代码写错了然后开始到处搜浏览器禁止跨域访问解决办法搜出来的东西五花八门有让你加个--disable-web-security启动参数的有让你装插件的有让你上 JSONP 的还有人直接甩一段 Nginx 配置过来。信息过载反而更懵。我写这篇东西的目的是把跨域这件事从玄学报错还原成一套浏览器按规则执行的判定流程。你搞懂了这套流程再回头看那些解决办法就会发现它们其实就分成三档改后端、加中间层、以及少数确实需要绕的场景。文章适合三类人看一是刚接触前后端分离、被 CORS 反复折磨的新手二是带项目、需要给团队定一套统一跨域方案的负责人三是运维或网关同学需要在 Nginx、云负载这一层把跨域收口的人。全文不堆概念尽量用我实际踩过的坑来讲配置都能直接抄。1.1 同源策略的真实身份它是浏览器的自我保护不是 bug先把一个观念摆正跨域拦截不是你的服务器拒绝了你而是浏览器在请求发出去之后、结果返回给你的 JS 之前主动把响应内容藏起来了。这话有点绕举个具体例子你就明白。你用浏览器打开http://a.com的页面页面里的 JS 用fetch(http://b.com/user)去请求另一个站点的接口。这个请求实际上是发出去了的服务器b.com也正常处理并返回了 200。问题出在返回的路上——浏览器检查了一下响应头发现b.com没有声明我允许a.com来读我于是浏览器就把这个响应没收了你的 JS 里拿到的是一个 CORS 错误而不是数据。那浏览器图什么图的是保护你。假设没有同源策略你打开一个恶意页面它里面的 JS 可以悄悄带着你在bank.com的登录 Cookie 去请求bank.com/transfer然后读取返回结果你全程无感。同源策略就是给这种跨站读取上了一把锁。所以它本质上是一个读限制而不是写限制——这也是为什么很多简单请求比如表单提交、img、script能跨域发出去但读不到结果。理解了这一点你就明白为什么关掉浏览器安全策略这种做法只能应急你等于把自己浏览器的那把锁卸了代价是访问任何网站时都裸奔。1.2 判定的三要素协议、主机、端口缺一不同源所谓同源判断标准就三个词协议、主机、端口。三个完全一样才算同源任何一个不同都算跨域。我列个表你可以对着自己项目的地址比一比页面地址接口地址是否同源原因http://a.comhttp://a.com/api是三要素一致http://a.comhttps://a.com/api否协议不同http://a.comhttp://b.a.com/api否子域名不同http://a.comhttp://a.com:8080/api否端口不同http://localhost:5173http://127.0.0.1:8080/api否主机和端口都不同这张表里有几个坑是新手最容易栽的。第一个是localhost和127.0.0.1不算同源虽然它们指向的是同一台机器但在浏览器眼里就是两个不同的主机名很多同学本地开发时前端跑localhost、后端配置里写127.0.0.1然后死活调不通就是这个原因。第二个是协议降级https页面去请求http接口不仅是跨域还会被浏览器判为混合内容Mixed Content直接拦掉属于双重问题。第三个是端口差别开发时前端 3000、后端 8000 是最常见的跨域组合很多人以为只要 IP 一样就行其实端口不一样照样跨。还有一类更隐蔽的主域名和 www 子域。example.com和www.example.com之间也是跨域早年大家用document.domain来打通现在这个属性已经被主流浏览器废弃因为会引起安全问题所以别再想着用它了老老实实配 CORS 或者走代理。1.3 为什么 Postman 能通、浏览器却报错这是我带新人时被问得最多的一个问题我用 Postman / curl 请求一模一样接口秒回怎么一放进页面就报跨域答案在 1.1 那段已经埋了同源策略是浏览器独有的机制Postman、curl、服务端之间互相调用根本不经过浏览器的那套判定。Postman 是一个独立的 HTTP 客户端它只管把请求发出去、把响应拿回来没有页面来源这个概念自然也就无所谓跨域。curl 同理。所以当你听到有人说接口没问题啊我这边通了别急着怀疑自己先确认对方是不是用 Postman 测的。反过来这个特性也给了我们一个非常有用的排查手段如果 Postman 通、浏览器不通那问题 100% 在浏览器这一侧要么是 CORS 响应头没配对要么是请求的方式触发了预检但预检没被正确处理。这个二分法能帮你省掉大量瞎猜的时间。顺带说一句服务端之间的调用比如你的 Java 后端去请求另一个后端也完全不受同源策略约束那是另一套信任模型的事。跨域这个问题只属于浏览器里的 JS这个特定角色。2. 报错信息速查不同红字对应不同病根控制台里的红字不是随便标红的每一种都对应一个很具体的失败环节。学会读这些报错你能把排查时间从半小时压缩到两分钟。我按出现的频率从高到低排一下。2.1 三条最常见的报错逐条翻译第一种No Access-Control-Allow-Origin header is present on the requested resource直译就是响应里没有带允许跨域的来源头。这是最典型的一种说明后端压根没配 CORS或者配了但没生效。如果你确定后端配了那十有八九是走到了某个没经过 CORS 处理的分支——比如异常处理器直接返回了错误页绕过了框架的 CORS 中间件或者被 Nginx 反代后Nginx 把后端返回的 CORS 头给覆盖、清空了。这个坑我后面在排查部分会专门讲。第二种The Access-Control-Allow-Origin header contains multiple values xxx, xxx, but only one is allowed看到这个基本可以断定CORS 头被加了两遍。常见于后端框架配了一次Nginx 的add_header又配了一次两个头叠加成了Access-Control-Allow-Origin: http://a.com, http://a.com。浏览器规定这个头只能有一个值多值直接判失败。解决办法是二选一要么后端配、Nginx 不碰要么 Nginx 配、后端别配最忌讳两头都动手。第三种Response to preflight request doesnt pass access control check这条报错的前面通常还有一句Request method: OPTIONS。意思是浏览器先发了一个 OPTIONS 预检请求而这个预检没通过。新手最容易在这里困惑我明明只发了一个 POST怎么就多了个 OPTIONS——这就是下面要讲的预检机制。2.2 简单请求与预检请求为什么有时候多一个 OPTIONS浏览器把跨域请求分成两类简单请求和需要预检的请求。简单请求直接发服务器返回时带上 CORS 头就行非简单请求会先发一个 OPTIONS 探路问服务器我能不能用这个方法、带这些头来请求你服务器点头了浏览器才发真实请求。那什么算简单请求同时满足下面三个条件才算方法是GET、HEAD、POST三者之一请求头只用了Accept、Accept-Language、Content-Language、Content-Type这几个安全头如果是Content-Type值只能是application/x-www-form-urlencoded、multipart/form-data、text/plain三种。只要有一条不满足就触发预检。这里有个特别隐蔽的点很多前端同学习惯用Content-Type: application/json发 POST而application/json不在这三个白名单里所以哪怕你只是发一个最简单的 JSON也会触发 OPTIONS 预检。我见过不少人为了避开预检把application/json改成text/plain然后手动JSON.stringify虽然能绕过预检但这是拿规范换取性能的取巧做法接口语义会变乱不建议在正式项目里这么干。预检请求本身也是一个跨域请求它需要服务器返回正确的方法和头声明并且最好带上Access-Control-Max-Age做缓存否则每次真实请求前都要多走一轮 OPTIONS接口一多延迟肉眼可见地涨。2.3 一张表定位问题报错现象与根因对照我把常见的现象、对应根因和快速处理方式整理成一张对照表排查时直接对照着看现象最可能的根因处理方向无 Allow-Origin 头后端未配 CORS或响应被代理层覆盖补后端 CORS 或网关配置Allow-Origin 有多个值后端与网关重复添加只保留一处配置预检 OPTIONS 返回 404/405服务器未放行 OPTIONS 方法放行 OPTIONS正确返回 204带 Cookie 时报 Allow-Credentials 错误Allow-Origin 用了通配符改成回显具体来源本地 localhost 报错而域名正常主机名不匹配统一用同一主机名偶发跨域、刷新就好命中缓存或预检过期设 Max-Age检查缓存策略提示遇到跨域报错第一步永远是打开 Network 面板找到那条红色的请求看它的 Request Headers 里有没有Origin看 Response Headers 里到底返回了哪些Access-Control-*头。报错文本是结论请求和响应头才是证据。3. 后端方案CORS 响应头怎么配才不出错只要条件允许改后端、正规配 CORS 永远是第一选择。它规范、安全、对前端透明而且能精确控制哪些来源能访问。剩下的代理、转发都是在前端或网关层做补救。这一章把 CORS 的核心响应头一个个拆开讲。3.1 五个核心响应头逐个拆解CORS 的响应头其实不多常用的就五个但它们之间的关系很容易搞混尤其是有 Cookie 的场景。Access-Control-Allow-Origin允许的来源。可以写死一个具体域名https://app.example.com也可以写*表示允许所有。注意带 Cookie 时绝对不能是*浏览器会直接拒绝。Access-Control-Allow-Methods允许的 HTTP 方法比如GET, POST, PUT, DELETE, OPTIONS。这个头只在预检响应里有意义。Access-Control-Allow-Headers允许携带的自定义请求头。前端如果加了Authorization、X-Token之类的头就必须在这里列出来否则预检失败。Access-Control-Allow-Credentials设置为true才允许携带 Cookie。不设或设成false前端就算写了credentials: include也拿不到 Cookie。Access-Control-Max-Age预检结果缓存多少秒。设大一点比如 3600 或 7200能显著减少 OPTIONS 请求数量。还有一个Access-Control-Expose-Headers容易被忽略。默认情况下前端只能读取响应里那几个安全头如Content-Type如果你想让前端拿到X-Total-Count这类自定义头分页场景很常见必须把它列在这个头里否则前端response.headers.get(X-Total-Count)会返回 null。3.2 带 Cookie 的跨域withCredentials 这套组合拳这是跨域里最容易翻车的一块单独拿出来讲。前后端分离项目常用 Cookie 或基于 Cookie 的 Session 做鉴权一旦涉及跨域就要三处同时配合少一处都不行前端请求带上凭证。用fetch是credentials: include用 axios 是withCredentials: true用原生 XHR 是xhr.withCredentials true。后端响应头设置Access-Control-Allow-Credentials: true并且Access-Control-Allow-Origin必须回显具体的来源不能是*。Cookie 本身如果前后端真在不同站点不是不同端口Cookie 还需要SameSiteNone; Secure。这里要强调SameSite的默认值在新版浏览器里已经是Lax意味着很多跨站 Cookie 默认就带不过去这是不少人莫名其妙登录状态丢失的元凶。我踩过一次特别典型的坑前端withCredentials开了后端Allow-Credentials也开了Allow-Origin也回显了来源但登录态就是保不住。查了半天发现是 Cookie 的SameSite没改浏览器压根没把 Cookie 带上去。所以带 Cookie 的跨域记住这三件套要一起配别只顾着代码忘了 Cookie 属性。3.3 各语言框架的落地配置示例理论讲完直接上代码。下面几个是我实际项目里用过、确认可跑的配置。Spring BootJava用WebMvcConfigurer全局配Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(https://app.example.com, http://localhost:5173) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这里我用allowedOriginPatterns而不是allowedOrigins原因是新版 Spring 在allowCredentials(true)时不建议用通配符配allowedOriginsallowedOriginPatterns更灵活也更安全。ExpressNode.js用官方cors中间件最省事const cors require(cors); app.use(cors({ origin: [https://app.example.com, http://localhost:5173], credentials: true, methods: [GET, POST, PUT, DELETE, OPTIONS], maxAge: 3600 }));Nginx如果要在网关层统一加头location /api/ { proxy_pass http://backend:8080/; add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS always; add_header Access-Control-Allow-Headers Content-Type, Authorization, X-Token always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Max-Age 3600 always; if ($request_method OPTIONS) { return 204; } }注意这里的$http_origin是把请求里的 Origin 回显回去配合Allow-Credentials使用。但如果你的后端已经配了 CORSNginx 这里就一个字都别加否则就是你亲手制造了 2.1 节里那个多个值的报错。注意Nginx 的add_header有个经典陷阱——一旦你在某个location里写了add_header父级的add_header就会被覆盖。排查头配了却不生效时先检查是不是这个覆盖机制在作怪。4. 前端与网关方案代理、转发与绕过思路后端不方便改的时候比如接口是第三方提供的或者你根本改不动后端就得靠前端或网关想办法。这一章讲几种主流做法和它们的适用边界。4.1 开发环境代理devServer 与 Vite 的正确打开方式开发阶段最省心的方案就是配置开发服务器代理。原理很简单让浏览器以为自己请求的是同源的开发服务器开发服务器在后台把请求转发给真正的后端因为服务器之间的转发不受同源策略约束。Vite 的配置// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } });webpack devServer 的配置// webpack.config.js devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } }这里有几个参数值得说清楚。changeOrigin: true是必须的它会把转发请求的 Host 头改写成目标地址的 Host很多后端会校验 Host不设这个可能被拒。rewrite/pathRewrite用来去掉路径前缀因为真实后端接口路径里通常没有/api这一段代理转发时需要剥掉。很多人配了代理还是 404八成是忘了 rewrite。还有一个关键认知代理只在开发环境有效。你npm run dev起的那个服务器才有代理能力npm run build之后打包出来的静态文件是没有代理的部署到生产环境必须另想办法见下一节。4.2 Nginx 反向代理生产环境的常规操作生产环境最常见的做法是用 Nginx 把前端静态资源和后端接口收在同一个域名下。这样做的好处是前端页面和接口同源了浏览器层面根本不存在跨域问题也就省掉了所有 CORS 配置。server { listen 80; server_name app.example.com; # 前端静态资源 location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } # 接口反向代理 location /api/ { proxy_pass http://backend:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这套配置里页面是http://app.example.com/接口是http://app.example.com/api/xxx同源浏览器不做任何拦截。前端的代码里接口 baseURL 直接写相对路径/api就行开发和生产还能保持一致。反向代理相比 CORS 的优势一是对前端完全透明不用管 cookie、credentials 那一堆二是可以在网关层统一做鉴权、限流、日志收口更干净。劣势多了一跳理论上有轻微延迟另外如果接口是第三方且不允许你反代有些服务会校验 Referer 或要求直连那就用不了。所以选 CORS 还是反代本质是看接口归属和部署条件。4.3 JSONP 与 postMessage那些年用过的老办法有些老项目或者第三方接口只支持 GET 且不方便改这时可能还会碰到 JSONP。它的原理是利用script标签不受同源策略限制的特性动态创建 scriptsrc 指向接口并带上callbackxxx服务器返回一段xxx({...})的 JS浏览器一执行就相当于调用了你的回调。function jsonp(url, callbackName) { return new Promise((resolve, reject) { const script document.createElement(script); window[callbackName] (data) { resolve(data); document.body.removeChild(script); }; script.src ${url}?callback${callbackName}; script.onerror reject; document.body.appendChild(script); }); }JSONP 的局限很明显只能发 GET不能带自定义头没有错误状态码错误只能靠超时判断并且有安全风险相当于执行对方站点的代码。所以它只适合那种只读、无鉴权、第三方强制的场景新项目一律不推荐。另一个值得知道的是postMessage用于页面和 iframe、或页面和打开的窗口之间跨源通信// 父页面 iframe.contentWindow.postMessage({ type: init, data: 123 }, https://child.example.com); // 子页面 window.addEventListener(message, (e) { if (e.origin ! https://parent.example.com) return; console.log(e.data); });e.origin的校验绝对不能省否则任何页面都能给你发消息等于自己开了个后门。这点在很多示例代码里都被有意无意地省略了实际项目里必须补上。4.4 什么时候该硬绕什么时候必须改后端这里给一个我自己的判断标准避免大家病急乱投医接口是你自己团队的改后端配 CORS或者生产上反代别绕。接口是公司内部其他团队的先推动对方配 CORS推不动就在你们自己的网关加反代别用前端硬绕。接口是第三方公开的、只读的如果对方支持 CORS 就直连不支持且只支持 GET可以考虑用你自己的后端做一层转发服务器转发无跨域。只是本地临时调试用开发代理或者临时用浏览器启动参数放宽策略但只在本地、只在临时场景绝对不能带到线上。用启动参数关掉浏览器安全策略比如给浏览器加禁用同源检查的参数这种做法我强烈不建议形成习惯。它会让你的浏览器在访问所有网站时失去保护而且它掩盖了真正的问题——等你部署到线上代码一跑照样报错。5. 特殊场景与进阶问题前面讲的都是标准情况实操里还有一些边界场景会让人卡很久这里单独拎出来。5.1 localhost、127.0.0.1 与内网地址的那些坑本地开发最经典的组合拳报错就是主机名不统一。前端跑在localhost:5173你在浏览器里手输的是127.0.0.1:5173打开页面然后接口配的后端是localhost:8000——三个地址三种主机写法跨域判定自然会乱。我的建议是本地统一用localhost不要一会localhost一会127.0.0.1。如果你用代理方案这个问题基本不存在因为请求都发给了开发服务器自己。另外要注意新版浏览器对localhost和127.0.0.1有一点特殊照顾被视为潜在可信来源但这不改变它们互相之间是跨源的事实别指望这个特性帮你省事。5.2 私有网络访问与混合内容有一类报错长得不太一样提示大意是某公共网页试图连接到你的专用网络上的设备被阻止。这属于另一套机制——浏览器对从公网页面访问本地或内网地址比如192.168.x.x做了限制目的是防止公网页面偷偷扫描你内网的设备。它和 CORS 不是一回事但经常和跨域一起出现让人误判。如果你在做本地设备联调比如网页控制本地的一台设备就会碰到它。处理思路是把页面和设备的访问方式规划好别让公网页面直接去连内网设备通常需要走一个本地的中间服务或者用同源的方式。这块场景偏特殊具体做法要结合设备形态来定。混合内容Mixed Content是另一个。HTTPS 页面里发起 HTTP 请求浏览器会直接拦截并提示此连接存在安全风险。这种情况光配 CORS 没用因为请求根本没发出去。解决办法只能是把接口也升到 HTTPS或者用同源的相对路径。这个和跨域是两码事但报错位置相近很容易混淆。5.3 多域名动态 Origin 的稳妥处理如果一个后端要同时服务多个前端域名比如主站、H5、管理后台各一个域名Allow-Origin该怎么写直接写*在带 Cookie 场景下不行写死一个又不够用。稳妥做法是读取请求的 Origin跟一份白名单比对命中才回显没命中就不加这个头。const allowList [https://app.example.com, https://h5.example.com]; app.use((req, res, next) { const origin req.headers.origin; if (allowList.includes(origin)) { res.setHeader(Access-Control-Allow-Origin, origin); res.setHeader(Access-Control-Allow-Credentials, true); } res.setHeader(Vary, Origin); next(); });这里Vary: Origin很重要它告诉缓存层同一 URL 的响应会因 Origin 不同而不同否则缓存可能把 A 域名的响应急给了 B 域名导致莫名其妙的鉴权串号。这个头很多人不知道要加线上出问题时极其难查。提示白名单匹配一定要用精确匹配别用origin.includes(example.com)这种写法攻击者用example.com.evil.com就能绕过。这类细节在安全审查时经常被点名。6. 排查与实战技巧那些文档里不写的经验写到这里方案基本齐了。最后这一章是我这些年处理跨域问题沉淀下来的排查套路和踩坑记录价值可能比前面的配置还高。6.1 五步排查清单照着走就行每次遇到跨域我都按这个顺序走基本不会漏看 Network 里那条红请求的 Request Headers确认浏览器有没有带上Origin。没有 Origin说明这可能压根不是跨域别往这方向查。看 Response Headers 里到底有没有Access-Control-Allow-Origin。没有就是后端或网关没配有但值不对就是配置值的问题。判断这是简单请求还是预检。请求方法不是 GET/HEAD/POST或者带了自定义头或者 Content-Type 是application/json那就一定走了 OPTIONS去看那条 OPTIONS 请求的返回。确认 OPTIONS 请求返回了 2xx。如果 OPTIONS 直接 404、405 或者被鉴权拦成 401预检就失败了真实请求根本不会发出去。这也是为什么接口明明返回 200前端却报跨域——你看到的 200 是 OPTIONS 之前的假象或者说你只看了真实请求没看预检。检查是不是配置重复或缓存问题。看响应头有没有重复的 CORS 头清一下浏览器缓存再试。这套流程的好处是它不依赖猜测每一步都有明确的证据支撑把感觉哪里不对变成我看到哪里不对。6.2 常见问题速查表下面这张表是给排查时应急用的对照现象直接看处理方式问题现象排查要点解决方式接口 200 但仍报跨域看是否 OPTIONS 预检失败放行 OPTIONS 并正确返回头改后端没生效看是否被网关覆盖或未重启确认只一处配置重启服务登录态跨域丢失检查 Cookie 的 SameSite设 SameSiteNone; Secure预检每次都不缓存检查 Max-Age 是否设置加 Access-Control-Max-Age前端读不到自定义头检查 Expose-Headers添加 Access-Control-Expose-Headers生产同样配置却报错检查 HTTPS 与混合内容接口统一升 HTTPS间歇性跨域检查缓存层是否缓存了响应加 Vary: Origin6.3 几个我踩过的坑和独家心得最后分享几个具体到肉疼的经历。第一个坑异常处理绕过 CORS。有一次后端接口正常调用没问题一旦触发业务异常比如参数校验失败返回 400前端就报跨域。查了半天发现框架的 CORS 中间件是在正常处理流程里生效的而全局异常处理器直接返回了错误响应绕过了 CORS 处理链。解决办法是在异常处理器里也补上 CORS 头或者把 CORS 配置放到过滤器层让它对所有响应生效。这个坑特别隐蔽因为它只在出错时才出现正常测试根本发现不了。第二个坑Nginxadd_header的覆盖机制。有次在 Nginx 里配了跨域头测试环境好好的上了生产就没了。原因是生产配置里多了个location块里面写了自己的add_header比如缓存控制把父级的 CORS 头全覆盖掉了。Nginx 的add_header是就近覆盖而不是叠加这点和很多人直觉相反。后来我把 CORS 头统一挪到最外层问题就没了。第三个坑忘了OPTIONS请求会走鉴权。有些网关对所有请求做 Token 校验而浏览器的 OPTIONS 预检请求是不会带自定义鉴权头的浏览器自动发的你的 JS 控制不了结果预检被网关拦成 401跨域报错就来了。解决办法是让网关对 OPTIONS 请求放行直接返回 204别走鉴权。注意处理上述这类问题时每次只改一处改完立刻验证。跨域涉及前端、后端、网关三个环节同时改多处会让你根本分不清是哪一处的改动起了作用。第四个心得把跨域配置当成基础设施别每次临时救火。我的做法是在项目脚手架里就把开发代理配好生产环境的 Nginx 反代模板固化下来团队新项目直接复用。这样一来日常开发几乎碰不到跨域真遇到了也是往已知的排查清单上套而不是从零开始搜浏览器禁止跨域访问解决办法。说白了跨域这东西一次把它搞明白配成模板后面就是重复劳动它的难度不在技术本身而在于第一次没人给你讲清楚那套判定逻辑。我个人在实际项目里还有一个习惯就是在后端加一个专门返回当前请求头信息的小开关仅内网可访问排查跨域时用它直接看服务器到底收到了什么、准备返回什么头比在前端猜快得多。这个开关上线前记得关掉或者加权限别留在生产里。关于后续的扩展如果你是把前端部署到 CDN、接口走独立域名这种架构那还要额外考虑 CDN 缓存对 CORS 头的影响以及多个边缘节点配置一致性的问题思路还是这套先定位是请求没出去、预检没过还是响应头不对逐层往下切。跨域归根结底是个证据驱动的排查活掌握了方法论什么架构都难不倒你。
返回列表