CORS 报错?我是这么解决的
部署完代码,满心欢喜打开页面,结果控制台一抹红——
熟悉吗?这玩意儿几乎每个前端工程师都遇到过。我也不例外。刚入行那会儿,每次看到这个报错就头皮发麻,来回改配置、重启服务,折腾半天。后来踩的坑多了,才摸索出一套"一看报错就知道哪儿的毛病"的思路。今天就把这些经验摊开说说,不讲废话,直接实战。
一、先说真实报错场景,你属于哪种?
CORS 报错长这样,但不完全一样。浏览器控制台里最常看到的几种:
1. 没有任何 CORS 头,直接拒绝访问
这是最典型的——后端压根没配任何 CORS 响应头。
2. 用了通配符 *,但带了 credentials
后端配了 Access-Control-Allow-Origin: *,但前端 fetch() 里带着 credentials: 'include',浏览器不干。这俩是互斥的,不能一起用。
3. 预检请求过了,但实际请求还是失败
说明后端给 OPTIONS 请求回了响应头,但实际响应里缺少某些头,比如 Access-Control-Allow-Credentials: true。
你是哪种?先认清楚报错信息,再往下走,能省很多弯路。
二、CORS 到底是个什么东西?
2.1 同源策略(SOP)——根本原因
浏览器有个铁规则,叫同源策略(Same-Origin Policy)。简单说:一个网页只能"顺理成章"地访问同一个源的资源。这里的"源"指的是:
- 协议(http / https)
- 域名(example.com / www.example.com 是两个不同的域名)
- 端口(:80 / :443 / :3000 都算不同端口)
三个都一样,叫同源;有任何一个不一样,叫跨域。跨域请求会被浏览器直接拦下来——注意是浏览器拦,不是后端拦。也就是说:后端其实收到了请求、也返回了数据,但浏览器一看响应头里没有 CORS 相关字段,直接把响应废弃了,你的 JS 代码拿不到数据。
这就是为什么:本地开发没问题,部署到服务器就报错了。因为本地开发时前端和后端往往跑在同一台机器的同一个端口下,是同源的。
2.2 预检请求(Preflight Request)——很多人翻车的地方
浏览器把跨域请求分两类:简单请求和非简单请求。
简单请求必须同时满足:
- 方法是
GET、HEAD或POST - 请求头只能是 Accept、Accept-Language、Content-Language、Content-Type(且只能是
application/x-www-form-urlencoded、multipart/form-data、text/plain)
简单请求直接发出去,服务端正常处理就行。
非简单请求:任何自定义头、PUT/DELETE 方法、JSON 格式的 POST(Content-Type: application/json)等,都属于非简单请求。浏览器会先发一个 OPTIONS 预检请求,等后端确认"行,我允许你访问",才发真正的请求。
OPTIONS 请求这回事。后端没配 OPTIONS 的处理逻辑,预检直接失败,后面所有请求都发不出去。所以后端配置 CORS 时,必须同时处理 GET 和 OPTIONS 两种方法,很多踩坑就踩在这儿。2.3 核心响应头,一个一个说
CORS 的机制全靠几个响应头撑起来:
Access-Control-Allow-Origin:允许哪个源访问。*表示任意源(但不能和 credentials 共用)。最好配具体域名。Access-Control-Allow-Methods:允许哪些 HTTP 方法。GET, POST, PUT, DELETE, OPTIONSAccess-Control-Allow-Headers:允许哪些自定义请求头。Access-Control-Allow-Credentials:是否允许携带 Cookie。true时,Access-Control-Allow-Origin不能是*,必须指定具体域名。Access-Control-Max-Age:预检结果缓存多少秒。设置大一点可以减少预检次数,提高性能。Access-Control-Expose-Headers:允许 JS 访问哪些响应头。默认 JS 只能读到少数几个基本头。
三、后端怎么配?三大主流语言全覆盖
3.1 Node.js + Express
这是前端圈最常见的组合。推荐用 cors 中间件,别自己手写:
需要手写中间件的话(没装 cors 包),这样写:
3.2 Python + Flask
Flask 推荐用 flask-cors 扩展,一行搞定:
不想用扩展,手写中间件:
3.3 Java + Spring Boot
Spring Boot 有两种配法。第一种,全局配置类:
第二种,用注解,更精细控制:
四、前端怎么调试?工具和方法
4.1 先判断是简单请求还是非简单请求
打开浏览器 Network 面板,找这个请求:
- 如果 Method 是
GET或POST,没有OPTIONS,那就是简单请求 - 如果先看到一个灰色的
OPTIONS,然后才是你的POST,那就是非简单请求
灰色 OPTIONS 状态码不是 200/204,而是 4xx——问题就在这儿,说明后端没处理预检。
4.2 检查响应头
在 Network 面板点开任意一个请求,看 Response Headers 里有没有:
Access-Control-Allow-Origin: https://your-site.comAccess-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONSAccess-Control-Allow-Credentials: true(如果你前端带着 credentials)
没有?说明后端没配或者配置没生效。
4.3 前端带 credentials 时怎么写
注意:后端必须把 Access-Control-Allow-Origin 设置为具体域名,不能是 *,否则浏览器会拒绝 credentials 请求。
4.4 开发环境用代理,服务环境用后端配置
本地开发时,前端跑在 localhost:3000,后端跑在 localhost:4000,天然跨域。最舒服的做法是用代理,让本地 dev server 替你转发:
4.5 用在线 CORS 测试工具快速验证
不确定自己的后端 CORS 配得对不对?用工具直接测:CORS 测试工具,输入 URL 看返回的响应头对不对,比改代码再重启服务快多了。
五、JSONP vs CORS——别再用 JSONP 了
有些老项目还在用 JSONP 绕 CORS,这儿说一句:JSONP 是用 <script> 标签绕过同源限制,本质上是个漏洞——它要求后端返回一个 JS 函数调用,而 JS 函数调用的参数是数据。这玩意儿:
- 只能发 GET 请求,不能 POST
- 没有错误处理机制,404 和 500 长得一模一样
- 有安全风险(XSS),已经被现代浏览器放弃
- 无法携带 Cookie 或自定义头
所以,别用 JSONP。老老实实配 CORS。
六、开发环境和生产环境的区别
很多人问:本地开发好好的,怎么上线就挂了?
原因很简单:本地开发时前后端同源(都是 localhost),根本不存在跨域问题。上线后,前端在 https://your-site.com,后端在 https://api.example.com,跨域了,CORS 才开始生效。
所以:
- 本地开发用代理,别靠 CORS(方便也快)
- 测试环境和生产环境必须用真实的域名/子域名配置 CORS
- 别在生产环境用
Access-Control-Allow-Origin: *(除非是纯公开 API)
七、常见误区,逐条说清楚
误区1:后端配了 CORS,浏览器还是报跨域错误
检查是不是只配了 GET,没配 OPTIONS。预检请求如果没被正确处理,后续请求发不出去。另外,确认你的请求是从浏览器发出来的——命令行 curl 不受同源策略限制,看起来"通了",但浏览器里还是报错。
误区2:把 CORS 配置写到 Nginx 反向代理里就行了
Nginx 只是个代理,它加的响应头是在转发过程中加的,不会影响浏览器对跨域的判断。确认你的后端服务本身正确处理了 CORS,Nginx 只做它该做的事。
误区3:credentials 和通配符可以一起用
不行。浏览器的规定:带 credentials 的请求,Access-Control-Allow-Origin 必须是具体域名,不能是 *。两边都配了的话,浏览器会直接拒绝请求。
误区4:CORS 配置可以临时生效,上线再改
不要这样。开发阶段就配好 CORS,特别是预检请求的处理。上线临时改很容易漏掉 OPTIONS 的处理,导致部分请求失败。
误区5:用 JSONP "解决" CORS 问题
JSONP 是老技术,有安全风险,现代项目不要再用。直接配 CORS,一个中间件搞定。
总结
CORS 这事儿,搞清楚了原理,其实不复杂:
- 同源策略是浏览器的安全铁律,跨域请求需要后端授权
- 预检请求是关键,容易被忽略
- credentials和通配符 *是互斥的,别一起用
- 后端配置 CORS 很简单,一个中间件/注解就能搞定
- 本地开发用代理,生产环境配 CORS,别混着用
遇到 CORS 报错,先看控制台报错信息,再打开 Network 面板看响应头,按图索骥定位到具体问题。90% 的情况都是后端没配或漏了 OPTIONS 的处理,剩下的 10% 是 credentials 和通配符冲突。知道这两条,基本能解决所有 CORS 问题。
常见问题
A: 这类工具一般有明确的输入框和输出框,按提示输入内容,点击对应按钮即可得到结果。建议先用简单示例测试功能是否正常,再处理实际数据。
A: 根据具体工具类型决定。格式转换工具适合处理第三方数据,编码工具适合加密传输,压缩工具适合文件上传前处理。多积累工具使用经验,遇到问题时能快速判断用哪个工具解决。
A: 不同工具有不同侧重,重点是理解原理。可以同时安装多个类似工具,实际使用中对比效果,选择最顺手的一个。随着使用经验增加,你也能判断工具的好坏。