拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

CORS跨域原理与配置实战:从同源策略到Nginx代理

CORS跨域原理与配置实战:从同源策略到Nginx代理

"Access to XMLHttpRequest at 'http://api.demo.com/v1/user' from origin 'http://localhost:8080' has been blocked by CORS policy..." 如果你是个前端,看到这句话基本可以确认,跨域问题来了。我第一次独立处理这个报错时,在控制台盯着红字看了整整一下午,起初以为是后端接口挂了,后来才知道是浏览器对跨源请求做了一道安全检查。CORS(Cross-Origin Resource Sharing,跨域资源共享)就是为这道检查设计的一套协议:浏览器先问后端"这个来源的请求你认吗",后端通过响应头回答"认"或"不认",浏览器再决定是否把响应数据交还给页面脚本。这篇文章我会把CORS从原理到配置、从前端到后端的完整链路过一遍,包括实际排查预检失败的过程,以及vue、uniapp这类工程里的配置方式,适合被跨域报错折磨的初级前端,也适合被拉来"帮忙配一下跨域"的后端同事。

1. 跨域到底在拦什么:同源策略的三个判定要素和那个历史命名

1.1 判定同源的三要素

很多人理解同源策略,只知道"域名必须一样",其实浏览器判定同源时看的是三个要素:协议、域名、端口。三者完全一致才算同源,只要有一个不同,这次请求就会被当作跨源请求处理。

我用一张表格把这几种情况列清楚,照着比对就行:

页面地址请求目标判定结果原因
http://localhost:8080/index.htmlhttp://api.demo.com/user跨域域名不同
http://localhost:8080/index.htmlhttps://localhost:8080/user跨域协议不同
http://localhost:8080/index.htmlhttp://localhost:8081/user跨域端口不同
http://localhost:8080/index.htmlhttp://localhost:8080/api/user同源协议、域名、端口一致

注意,localhost:8080和localhost:8081虽然在很多人的直觉里是"同一个域名下的不同端口",但浏览器在判定源时把端口也算了进去,所以它们依然是跨域。这也是开发时非常容易踩的一个点:前端页面跑在8080,后端接口跑在8081,明明都是local环境,却出现了跨域报错。

那浏览器为什么要有同源策略?不是为了给你添堵,而是为了保护你的登录状态。假如没有这道限制,你在论坛浏览时,页面里的一段恶意脚本就可以偷偷向你的邮箱、银行后台发起请求,并且还能读取响应数据。只要用户当前浏览器里带着相关站点的Cookie,攻击者就能冒充你的身份做操作。同源策略的核心逻辑是:页面脚本只能读取"和自己同源"的请求返回数据,其他来源的数据一律不能交给你。它是Web安全里非常基础又非常重要的一道防线。

1.2 浏览器拦的是"读响应",不是"发请求"

这里需要纠正一个流传很广的误解:"跨域请求根本发不出去"。实际上,浏览器允许很多跨域请求被发出,真正拦截的是页面脚本读取响应内容这一步。

举个最直观的例子:<img>标签可以加载任何域名上的图片,<script>标签可以加载任何域名上的脚本,<link>标签可以加载任何域名上的样式表。这些请求都发出去了,浏览器也拿到了数据,但因为标签本身不要求脚本读取返回内容,所以不受同源策略限制。而fetch和XMLHttpRequest这类由脚本发起的请求,浏览器会在响应返回后检查是否有合法的CORS响应头,没有就不让脚本读取数据,顺带在控制台抛一个跨域报错。

这也解释了一个问题:既然<script>标签能跨域加载,是不是可以用它来"绕过"浏览器限制?确实可以,这就是JSONP的思路来源。但script标签走的是GET请求,而且没有统一的错误处理机制,所以在新项目里基本被CORS替代了。后面我会专门做个对比。

1.3 为什么叫"跨域"而不是"跨源"

origin直译是"源头",按道理这个机制应该翻译成"跨源资源共享"。当年中文技术社区在翻译的时候,习惯性地把origin理解成"域",加上大家日常口语里也喜欢说"不同域名"而不是"不同源",cross-origin就慢慢变成了"跨域"。

所以你会在英文文档里看到cors、cross-origin、same-origin policy,回到中文语境其实就是同一个概念。这篇文章后文说的"跨域",也都指浏览器的跨源请求,别再被翻译绕晕。

顺带提一句,搜索引擎热词里经常和它一起出现的"跨时钟域",是完全另一个领域的话题,指的是数字电路设计中数据跨越不同时钟域时的同步处理,和浏览器CORS没有任何关系。搜索跨域相关问题时看到这个词,直接跳过就好。

2. CORS 不是放开限制,而是一次带响应头握手的完整流程

2.1 简单请求和预检请求是怎么区分的

浏览器不是对每个跨域请求都先来一次OPTIONS,它会把请求分成两类:简单请求和预检请求(preflight)。只有被判定为复杂请求时,浏览器才会先发送OPTIONS请求,拿到服务器允许的"通行证"后再发送真实请求。

一个请求要成为简单请求,需要同时满足几个条件:HTTP方法必须是GET、POST、HEAD之一;请求头里只有基本的浏览器自有字段,不能出现Authorization、X-Custom-Token这类自定义头;Content-Type只能是text/plain、multipart/form-data、application/x-www-form-urlencoded这三种之一。只要有一项不满足,比如前端用了application/json发POST,或者带了Authorization头,浏览器就会认为这是复杂请求,触发预检。

为什么浏览器要做这个区分?你可以把简单请求理解成"旧时代可以直接进城的马车",而预检请求则是"货车进城前先要停车登记"。如果所有跨域请求都先来一次OPTIONS,一次接口请求就要变成两次,成本太高;但浏览器又不敢对所有请求都直接放行,所以设计者在当初制定标准时,把那些"历史上本来就被允许跨域发出的请求类型"划成了简单请求,让它们少走一道流程,其他类型就必须先证明自己的安全性。

2.2 握手不是浏览器说了算,而是要看响应头

先看简单请求的路径。假设页面在http://localhost:8080,向后端http://api.demo.com发了一次GET请求,浏览器会在请求头自动带上:

Origin: http://localhost:8080

后端看到这个头后,如果允许这个来源访问,就返回:

Access-Control-Allow-Origin: http://localhost:8080

浏览器拿到响应后,发现返回的Allow-Origin和页面所在源一致,才会把响应数据交给页面脚本。如果后端什么都没配,或者返回的是其他源,浏览器就直接拦截,控制台就会报"No 'Access-Control-Allow-Origin' header"。

再看预检请求的完整链路。前端用axios向http://api.demo.com发一个带Content-Type: application/json的POST请求,浏览器实际发出去的第一个请求是这样的:

OPTIONS /v1/user HTTP/1.1 Origin: http://localhost:8080 Access-Control-Request-Method: POST Access-Control-Request-Headers: content-type

前两个头大家都认识,重点是Access-Control-Request-Method和Access-Control-Request-Headers,它们告诉后端:我接下来想用一个POST请求,并且带上content-type作为内容类型。后端如果同意,会返回:

Access-Control-Allow-Origin: http://localhost:8080 Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: content-type Access-Control-Max-Age: 86400

浏览器看到这些头之后,才真正发出那个POST请求,再检查POST响应的Access-Control-Allow-Origin,确认无误后把数据交给脚本。

你观察一下这个流程,CORS的本质其实不是"后端放开了一个开关",而是浏览器和后端之间进行了两次握手机制。整个链条里任何一环缺了响应头,请求就会在浏览器侧被拦下。

2.3 响应头字段逐个拆解

后端配置CORS时,需要打交道的响应头其实就这几个,我把它们整理成了一张表:

响应头字段作用什么时候需要
Access-Control-Allow-Origin允许访问的来源列表几乎所有跨域场景都需要,必配
Access-Control-Allow-Methods允许的HTTP方法白名单预检请求中检查
Access-Control-Allow-Headers允许的自定义请求头白名单预检请求中检查
Access-Control-Allow-Credentials是否允许携带Cookie等凭证请求需要带Cookie时
Access-Control-Max-Age预检结果可在浏览器端缓存多少秒用于减少OPTIONS请求数量
Access-Control-Expose-Headers让前端JS能够读取哪些响应头前端需要读取自定义响应头时

Access-Control-Allow-Origin是唯一一个"几乎所有跨域场景都必须出现"的字段,所以排查问题时第一眼看它准没错。Allow-Methods和Allow-Headers只在预检阶段被浏览器检查,你即使之前配置过一次,如果改了自定义请求头,也要重新确认这两个字段是否覆盖到了。

Access-Control-Max-Age是一个容易被忽视的优化项。它代表预检结果的缓存时长,比如设置成86400,浏览器在24小时内再次发起相同结构的请求时就不会重新OPTIONS,直接发真实请求。线上请求量大的项目,合理设置这个值能减少不少无效请求。

3. 后端配CORS的三种主流写法:Express、PHP、Spring Boot

3.1 Express / Node.js:中间件一行接入

Node.js生态里解决CORS最省心的方式就是用官方维护的cors中间件。npm install cors之后,几行代码就能把跨域支持打开:

const express = require('express'); const cors = require('cors'); const app = express(); app.use(cors({ origin: 'http://localhost:8080', methods: ['GET', 'POST', 'PUT', 'DELETE'], credentials: true })); app.get('/v1/user', (req, res) => { res.json({ code: 0, data: { name: 'Tom' } }); }); app.listen(8080);

这段配置会把Access-Control-Allow-Origin精确设成http://localhost:8080,同时允许带凭证。但现实项目里,前端可能有好几个域名,比如正式环境、测试环境、后台管理系统各一个,这时候把origin写死成字符串就不够了,需要用动态函数:

const whitelist = ['https://www.demo.com', 'https://admin.demo.com']; app.use(cors({ origin: function (origin, callback) { if (!origin || whitelist.indexOf(origin) !== -1) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true }));

注意这个函数里有个细节:!origin的判断很重要。有些情况下请求会不带Origin头,比如服务端内部脚本发请求,或者某些APP内置浏览器,这时要放行,否则会把原本正常的请求拦掉。

这里也要解释一个高频问题:为什么credentials: true和Access-Control-Allow-Origin: *不能同时用。*的意思是"任何来源都允许访问",如果再允许携带Cookie,就等于把用户凭证暴露给了任意站点,浏览器出于安全考虑会直接拒绝这种组合。所以只要你打算在前端带Cookie或HTTP认证信息,后端返回的Allow-Origin就必须是一个具体来源,不能是通配符。

3.2 PHP:原生header与旧项目改造

PHP项目的处理方式更直接,因为它是每次请求独立执行脚本,直接在入口文件里加响应头就行:

header('Access-Control-Allow-Origin: http://localhost:8080'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization'); header('Access-Control-Allow-Credentials: true'); if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('HTTP/1.1 200 OK'); exit(); }

这段代码里有一个很多老项目容易踩的坑:预检请求一定不能继续往下走进业务逻辑。

我在一个旧系统里见过这样的问题:前端发了一个带Authorization头的POST请求,后端虽然加了CORS响应头,却没有拦截OPTIONS请求,结果OPTIONS请求直接走到了用户登录校验逻辑里,返回了401。浏览器一看见预检响应不是200/2xx,就直接把后续请求拦下,前端一脸困惑"后端不是配了跨域吗"。所以PHP项目在处理OPTIONS时,确认响应头都带上之后,最好直接在入口处截断并返回空响应体。

如果你用的是Laravel这类框架,通常有更优雅的中间件方案,可以直接在app/Http/Kernel.php里注册全局中间件,或者使用官方推荐的跨域中间件包。原理还是那套响应头,只是框架帮你做了封装。

3.3 Spring Boot / Java:全局CORS配置

Java后端更推荐用框架级别的全局配置,而不是在每个Controller里手动加响应头。Spring Boot项目实现WebMvcConfigurer接口,覆盖addCorsMappings方法即可:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

addMapping("/**")表示对所有接口生效,你也可以指定成/api/**,这样能精细控制只有API路径支持跨域。如果有网关层(比如Spring Cloud Gateway),一般要在网关统一配置CORS,避免下游服务重复设置后出现响应头冲突。

另外,如果你用SecurityConfig配了Spring Security,注意跨域配置要写在过滤器链里,有些开发者把CORS配置写在ControllerAdvice里,结果被安全过滤器挡住,导致OPTIONS请求根本没有机会执行到那一步。这是Java项目里跨域问题的最常见漏网点之一。

3.4 为什么说CORS比JSONP更主流

还有一个绕不开的老话题:JSONP。既然script标签可以绕过CORS限制,那用JSONP行不行?技术上确实行,但局限于场景。我这里简单对比一下两者:

对比项JSONPCORS
支持方法仅GETGET、POST、PUT、DELETE等
传输机制<script>标签动态加载XHR / fetch
自定义请求头不支持支持
错误处理较弱,难以拿到状态码标准HTTP错误机制
前端改动需封装callback正常发请求即可
适用场景老系统小接口改造新项目和绝大多数线上系统

所以结论很明确:新项目优先CORS,JSONP只适合那些不方便改响应头、又只需要GET数据的特殊老接口。我用"退路方案"这个词形容它,一点也不夸张。

4. 前端联调时的隐藏坑位:credentials、Origin null与预检缓存

4.1 credentials和通配符水火不容

前端联调里最常见的报错变体,就是后端配了Access-Control-Allow-Origin: *,但前端请求带着凭证。你只要在axios里设置了withCredentials: true,或者fetch里写了credentials: 'include',浏览器发现响应头是通配符*,直接给你报跨域错误。

这是因为浏览器策略很严格:CORS规范规定,当请求需要携带凭证时,Allow-Origin不允许为通配符,必须明确写出来源;同时Access-Control-Allow-Credentials要显式设为true。所以前端一旦发现需要带Cookie或Token的跨域请求,要同步确认后端返回的不是*,两边一起调,缺一不可。

这里有一个很容易混淆的技术细节:像Authorization这种请求头,虽然也叫"凭证",但它触发的是预检机制,跟Cookie的credentials是两个维度的东西。前端习惯上会说"带Token的请求",后端看到Access-Control-Allow-Headers里要包含Authorization,这两类问题经常被混在一起排查,实际上分开看会清晰很多。

4.2 Origin: null的隐身请求

如果你用file://协议直接双击打开一个HTML页面,页面里发起跨域请求时,浏览器发送的Origin头会是一个字面量null。对后端来说,Origin: null是一个合法来源,但很多后端的Allow-Origin白名单里没写它,于是请求被拒。

这种情况常见于本地调试静态文件、在线代码编辑器导入HTML、或者某些iframe采用sandbox属性嵌套页面的时候。解决办法不是让后端在CORS里永远放行null,而是建议本地调试时用http-server或live-server起一个本地静态服务器,让页面处于正常的HTTP环境中,否则你会在调试阶段绕进一个和线上环境完全不一样的问题里。

4.3 改完后端配置,浏览器却还是那个报错

这个坑我至少见过十次:后端同事明确说"我加了响应头,你刷新看看",前端刷新后报错还在,后端一脸无辜,前端一脸崩溃。

多数情况是浏览器缓存了预检结果。当后端响应头里设置了Access-Control-Max-Age,比如86400秒,浏览器在一天之内不会再发OPTIONS请求,而是直接把之前那个"允许访问"的结果拿来用。如果你在这期间改了后端的允许来源或方法列表,浏览器用的还是旧缓存。所以排查跨域问题时,要记得在DevTools里勾选Disable cache,或者临时把Access-Control-Max-Age设置成0测试一下。

还有一种更隐蔽的情况:CDN或网关层把OPTIONS请求当作普通请求做了缓存。即使浏览器侧禁用了缓存,如果CDN节点上还存着之前那份没有CORS响应头的OPTIONS响应,前端拿到的依然是旧结果。遇到这种问题,要顺手检查一下代理层对OPTIONS的缓存策略,少走弯路。

4.4 五秒钟快速验证一个请求是不是CORS问题

排查跨域问题,我个人的习惯是先动手抓包,而不是看代码猜。最简单的方式是用curl模拟浏览器预检请求,直接观察响应头:

curl -i -X OPTIONS "http://api.demo.com/v1/user" \ -H "Origin: http://localhost:8080" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type, Authorization"

如果响应里看不到Access-Control-Allow-Origin,或者Allow-Headers里没有你请求里的自定义头,那问题就定位在后端配置上,和前端一行代码都无关。浏览器Network面板里,也能看到预检请求的状态,如果Optios请求状态码是403或505,基本就说明后端没有正确处理预检。

顺便说一句,"cors测试工具"在搜索引擎里搜索量一直很高,说明大家确实希望有一个能快速构造跨域请求、直接看完整响应头的工具。我平时用的方案是curl加一个小脚本,也可以直接用浏览器控制台fetch一个跨域地址,然后看控制台报错信息,再配合Network面板看具体是被哪个响应头卡住。

5. 一次预检失败报错的完整排查复盘

5.1 报错关键字先分流:是预检没过,还是响应头缺失

有一次我遇到一个线上问题,前端Vue项目往接口发POST请求,请求头带了Authorization和Content-Type: application/json,控制台报错是:

Access to XMLHttpRequest at 'http://api.demo.com/v1/user' from origin 'https://www.demo.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这句话里最关键的是Response to preflight request doesn't pass access control check,它直接告诉我们:预检阶段就没过。所谓"是不是CORS问题"从报错用词上就能做初步分流。如果报错只说No 'Access-Control-Allow-Origin' header,一般是简单请求阶段响应头缺失;如果带上了preflight字样,去看OPTIONS请求的响应。

5.2 从Network面板开始,一步步定位到Nginx和Spring

我打开Network面板,果然找到了一个OPTIONS请求,状态码是403。这里必须先解释一句:状态码403说明服务器确实响应了,但没给出允许跨域的头,于是浏览器在JS侧展示为"blocked"。很多人看到blocked就以为请求没发出去,其实服务器端日志里早就留下了记录。

接着我用curl复现了一遍OPTIONS请求,得到的响应头里没有任何Access-Control-*字段。于是问题范围缩小到两个点:Nginx层没把OPTIONS转发到后端,或者后端Spring Boot没配置CORS。我先查Nginx配置,发现server块里没有任何关于OPTIONS方法的特殊处理,直接proxy_pass到后端8080端口;再查Spring项目,全局配置里确实没有addCorsMappings。两端都没配置,预检当然过不了。

修复时我先在Spring里加了那套全局CORS配置,重启后OPTIONS请求返回200,也带上了Access-Control-Allow-Origin。我以为解决了,结果前端再一测,POST请求依然被拦,报错还是预检失败。这次打开预检响应头一看,发现Access-Control-Allow-Headers里只有默认值,没有Authorization。

因为前端请求头里带了Authorization,而Spring配置里allowedHeaders("*")虽然理论上可以匹配所有请求头,但当项目里同时配置了allowCredentials(true)时,某些版本下对通配符的处理并不如预期,所以我直接把allowedHeaders显式列成了"Authorization", "Content-Type"。改了之后,POST请求正常返回数据。

5.3 这次排查得出的判断方法

后来我把这次经历总结成了一个判断规律,在团队里也分享过:

  • 报错里出现preflight request doesn't pass access control check:第一反应是去看OPTIONS请求,不要去看POST请求的响应头。
  • 报错里只出现No 'Access-Control-Allow-Origin' header:直接去检查最终请求的响应头,通常是简单请求或后端漏配了Allow-Origin。
  • 如果OPTIONS请求本身返回401、403、405这类状态码:大多数时候不是CORS配置问题,而是Nginx、Spring Security或后端业务层拦截了OPTIONS请求,优先检查过滤器链。

这个方法帮我省掉了大量"翻后端代码看配置"的时间。因为CORS本质上是浏览器和服务器之间的握手,只要在握手这一步看到真实响应,问题基本就暴露了。

6. 开发与生产环境里绕开CORS的工程化方案:vue、uniapp、Nginx

6.1 vue-cli / Vite 项目的 devServer 代理

本地开发时,不少前端团队会直接用代理方案彻底绕开CORS。思路其实很简单:前端页面跑在http://localhost:8080,webpack或Vite的devServer也跑在http://localhost:8080,浏览器发起的请求目标是http://localhost:8080/api,属于同源请求,不触发CORS。devServer再把请求转发到真实的后端地址。

vue-cli项目在vue.config.js里这样配:

module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } };

Vite项目则在vite.config.js里配置server.proxy,字段基本相同。请求以/api开头时,devServer会把路径重写后转发给http://localhost:8080。这样前端代码里可以直接写相对路径/api/user,既绕开了跨域,也方便后续部署时用Nginx做同样路径的代理,代码不用改。

但我建议团队的接口基础层还是要对CORS保持"兼容"心态。因为不是所有请求都会走代理,比如在联调环境直接向后端接口调试、或者某些测试工具直连后端时,如果后端完全没有CORS响应头,又会回到报错状态。代理能解决开发阶段的体验,替代不了后端联调时需要的跨域支持。

6.2 uniapp的跨域配置:H5端代理和小程序端域名白名单

很多用uniapp的朋友第一次见到跨域报错,是在H5端运行项目时。uniapp在H5端本质是一个Vue工程,所以它的处理方式和vue-cli一样,也是通过manifest.json里的h5.devServer配置代理:

{ "h5": { "devServer": { "proxy": { "/api": { "target": "http://localhost:8080", "changeOrigin": true, "pathRewrite": { "^/api": "" } } } } } }

改完manifest.json记得重新运行项目,devServer配置变更通常需要重启才会生效。这是一个容易忽略的细节,我见过有人改了代理没有重启,然后在浏览器里反复报跨域,浪费了小半天。

但到了小程序端,情况完全不同。小程序不是浏览器环境,不存在同源策略,所以不会出现CORS报错;小程序平台有自己的校验体系,要求你在小程序后台配置合法请求域名。开发者工具打开"不校验合法域名"可以在开发阶段绕过,但上线前必须到小程序管理后台把接口域名加白名单,并且该域名必须完成备案和HTTPS配置。这里要提醒一句:很多人把小程序端的"域名白名单问题"也叫成跨域,其实它和CORS是两个完全不同的机制,排查方向不要混。

6.3 Nginx反向代理作为生产环境的兜底方案

如果后端确实不方便改CORS配置,生产环境还有一条路:Nginx做反向代理。前端请求的是与页面同源的/api路径,Nginx把请求转发到后端服务,浏览器从头到尾只和Nginx打交道,不涉及跨域:

location /api/ { proxy_pass http://backend-svc: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; }

这个方案的好处是后端不用动一行代码,但也要注意一个反向问题:如果后端服务本身已经返回了Access-Control-Allow-Origin响应头,Nginx层又通过add_header加了一遍,就会出现重复响应头。浏览器在解析时对重复头可能取第一个也可能取后一个,行为不稳定。所以要么后端配、Nginx不配,要么Nginx统一配、后端接口关闭CORS,两头都配绝对不是一个好习惯。

如果一定要在Nginx层直接处理预检请求,可以加一个if判断来拦截OPTIONS方法并直接返回允许头,相当于把CORS配置从应用层上移到网关层。这样集中管理的好处是多个后端服务都不需要各自配一遍跨域,但这种方案需要维护一套相对完整的Allow-Methods和Allow-Headers列表,后期调整请求头时很容易不同步,我个人的倾向是:能由后端框架配置的,就在应用层配;网关层只在统一接入的场景下使用。

最后说点个人体会。我在实际项目里踩过几次坑之后,形成了一个习惯:本地开发优先用devServer代理,但后端接口永远保持"该有的CORS响应头都在"的状态。这样即使哪天不经过代理直接打线上接口,也不会被跨域报错打个措手不及。真到了线上跨域问题排查那天,别急着翻代码,先把Network面板里预检请求和真实请求的响应头抓出来对照,哪一行缺了、哪一项不允许,一眼就能看清。多数时候让你头疼的并不是CORS多复杂,而是缺少一个快速定位它的方法。

返回列表