欢迎关注微信公众号:FSA全栈行动 👋
一、前言
说实话,我以前遇到 CORS 报错时的反应非常典型:直接把那串红色的报错信息复制,扔进 Google。然后,在成千上万个 Stack Overflow 回答里找一个点赞最高的,直接把 Access-Control-Allow-Origin: * 复制到我的 Express 服务里,就搞定了,收工。
但是,空闲之后复盘,却发现对此头信息的底层逻辑一问三不知,为什么这个接口之前没加此头信息时,在 Postman 里能调通,在浏览器里就报 CORS 错误?
所以,我决定静下心来把这件事彻底拆解清楚。如果你也还在通过"复制粘贴"来解决 CORS 问题,建议你花几分钟看完这篇,这能让你少走很多弯路。
二、核心原理:它到底在保护谁?
很多人初学时会有一种误区,觉得 CORS 是为了保护服务器的安全(比如防止非法请求)。
其实不然。CORS 的核心逻辑是:它不是为了给服务器筑起防火墙,而是为了给"浏览器里的用户"当保镖。
1、CORS 的本质:一个"保镖"机制
你可以把 CORS 想象成一个专门为"访客"服务的保安。
你的浏览器里存着非常敏感的信息,比如 Cookies、Session、或者某个站点的 Auth Token。当一个网页(比如 myapp.com)尝试向另一个域名的服务器(比如 api.other.com)发送请求时,浏览器会非常谨慎。
浏览器会问服务器一个问题:"这个来自 myapp.com 的网页,你真的允许它代表用户来访问你吗?"
默认情况下,浏览器的回答是:"除非你明确说允许,否则统统不行。"这就是所谓的"同源策略"(Same-Origin Policy)。而 CORS 则是服务器用来表达"我允许这个页面访问"的一种手段。
2、什么是"源" (Origin)?
要搞懂 CORS,首先得知道什么是"同源"。一个"源"由三个部分组成:
-
协议 (
Protocol) -
域名 (
Domain) -
端口 (
Port)
只要这三者中任何一个不同,它们就是不同的源。
|----------|-------------------------|-------------------------|---------|----------------------|
| 场景 | 原始 URL | 目标 URL | 结论 | 原因 |
| 协议不同 | https://example.com | http://example.com | 不同源 | https vs http |
| 域名不同 | https://a.com | https://b.com | 不同源 | 域名不一致 |
| 端口不同 | http://localhost:3000 | http://localhost:8080 | 不同源 | 端口不一致 |
| IP vs 域名 | http://127.0.0.1 | http://localhost | 不同源 | 虽然指向同一设备,但在浏览器眼中它们不同 |
三、实战避坑:为什么你的请求总是"莫名其错"?
在实际开发中,CORS 的坑通常藏在那些你看不见的地方。
1、Postman 与浏览器的差异
这是最让新手困惑的地方:为什么 Postman 调用接口顺畅无比,浏览器却报 CORS 错误?
原因非常简单:Postman 和 curl 这类工具不是浏览器。
它们不遵循浏览器的安全限制,也不会去执行什么"同源策略"。它们直接发请求,服务器也直接回响应。但浏览器不同,它在收到响应后会先"过一遍"安全检查,如果发现没有 CORS 相关的头部信息,它会直接把结果拦下来,不让你在 JavaScript 里读到。
2、深坑:预检请求 Preflight
很多时候,你的 Backend 日志里根本看不到报错的那个请求。这是因为浏览器在发送真正的请求前,会先悄悄发一个"探测请求",这就是 Preflight(预检请求),使用的 HTTP 方法是 OPTIONS。
当你使用了以下内容时,浏览器会自动触发预检请求:
-
使用了非简单方法(如
PUT、DELETE)。 -
设置了自定义的
Header(如Authorization)。 -
Content-Type不是简单的application/x-www-form-urlencoded、multipart/form-data或text/plain(比如现在最常用的application/json)。
如果你的服务器没有正确响应这个 OPTIONS 请求(必须返回 Access-Control-Allow-Origin 等头部),那么真实的业务请求压根就不会发出去。你以为是业务逻辑崩了,其实是预检都没过。
3、那些高阶的"神坑"
在生产环境,我见过无数团队在这些地方折腾一整天:
-
"万能"但危险的
Wildcard(*): 很多人喜欢用Access-Control-Allow-Origin: *。这在纯公开的 API(不需要Cookie或Authorization)中没问题,但如果你需要携带凭证(Credentials),浏览器会直接拒绝 这种组合。你必须明确指定具体的Origin。 -
错误响应也需要
CORS头部: 这是一个极大的隐蔽坑。如果你的后端逻辑出错抛出了500 Error,而你的错误处理中间件在返回错误时忘记加上CORS头部,浏览器会因为拿不到CORS头部而报出一个"CORS error"。这会让你误以为是权限问题,其实是你的业务代码崩了。 -
重定向陷阱: 如果你的
OPTIONS请求遇到了301或302重定向(比如从http跳到https),预检请求通常会直接失败。 -
代理与 Nginx 的覆盖: 有时候你在
Express里配得好好的,但依然报错。这时候要检查一下,是不是前端前面的Nginx、API Gateway或者CDN把你的CORS响应头给拦截或者覆盖掉了。
四、总结
处理 CORS 的最高境界不是学会如何"绕过"它,而是学会如何"正确地配置"它。
一旦你明白 CORS 本质上是一个关于"信任"的机制,你的调试思路就会发生变化:
-
不要再盲目复制
*。 -
重点关注
Network标签页里的OPTIONS请求。 -
确保所有的响应(包括错误响应)都能带上正确的
CORS头部。
如果你的项目涉及到多域名访问,建议在后端维护一个 Allowlist(允许列表),根据请求的 Origin 动态返回匹配的 Access-Control-Allow-Origin,而不是简单地回一个 *。
最后,如果你在折腾 CORS 时遇到了什么让你抓狂的奇葩问题,欢迎在评论区交流。
如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~