CORS 跨域根本性学习文档

1. 一句话理解 CORS

CORS,全称 Cross-Origin Resource Sharing,中文常叫 跨源资源共享
它不是“网络不通”,也不是“接口坏了”,而是:
浏览器出于安全考虑,不允许一个网页随便读取另一个源的数据。
如果服务端愿意被某个前端页面读取,就必须通过一组 HTTP Header 明确告诉浏览器:“我允许这个来源访问我。”
所以跨域问题的核心不是前端,也不是后端单独的问题,而是:
浏览器、前端请求、服务端响应 Header 三者之间的协商失败。

2. 什么叫“同源”?

一个 URL 的源由三部分组成:
例如:
它的源是:
下面这些都不是同源:
端口不同,跨域。
协议不同,跨域。
主机不同,跨域。即使它们可能指向同一台机器,浏览器也认为不同源。
子域名不同,跨域。
路径不影响同源判断:
它们同源,因为协议、域名、端口相同。

3. 为什么浏览器要限制跨域?

假设没有同源策略,会出现严重安全问题。
比如你已经登录了银行网站:
然后你打开了一个恶意网站:
如果浏览器允许 evil.com 的 JavaScript 随便请求并读取 bank.com 的数据,那么恶意网站就可能读取你的账户信息。
所以浏览器默认限制:
A 网站里的 JavaScript 不能随便读取 B 网站的响应内容。
注意,这里重点是 读取响应内容
有些跨源请求本身可能会发出去,比如图片、表单、script 标签等。但 JavaScript 想读取另一个源的接口响应时,就会受到限制。

4. CORS 解决的是什么问题?

CORS 不是关闭安全限制,而是提供一个标准流程,让服务端可以明确授权。
例如前端页面在:
后端 API 在:
浏览器会在请求里带上:
服务端如果允许它访问,就应该返回:
浏览器看到服务端明确允许当前 Origin,就把响应交给前端 JavaScript。
如果服务端没返回,或者返回的不匹配,浏览器就会拦截响应,并在控制台报 CORS 错误。

5. 最重要的几个 Header

5.1 Origin

这是浏览器自动加的请求头。
它表示:这个请求是从哪里发起的。
一般情况下,前端不需要也不能随便手动设置它。

5.2 Access-Control-Allow-Origin

这是服务端返回的响应头。
表示允许哪个源访问当前响应。
如果接口是公开资源,也可以:
但是注意:
只要涉及 Cookie、登录态、Authorization 凭证,就不要用 *

5.3 Access-Control-Allow-Methods

预检请求时,服务端告诉浏览器允许哪些 HTTP 方法:
如果前端真实请求是 PUT,但这里没有 PUT,浏览器会拦截。

5.4 Access-Control-Allow-Headers

预检请求时,服务端告诉浏览器允许哪些自定义请求头:
如果前端请求带了:
那么服务端预检响应里必须允许 Authorization

5.5 Access-Control-Allow-Credentials

如果要允许跨域携带 Cookie、HTTP 认证信息、客户端证书等凭证,需要服务端返回:
前端也要配合:
Axios 中通常是:
注意:
这是错误组合。
带凭证时必须返回具体 Origin:

5.6 Access-Control-Expose-Headers

默认情况下,前端不能读取所有响应头。
如果你想让前端读取某些自定义响应头,比如:
服务端需要加:
否则请求虽然成功了,但前端通过 JS 读不到这些 Header。

5.7 Access-Control-Max-Age

预检请求可以缓存。
表示这类预检结果可以缓存一段时间,减少频繁发 OPTIONS 请求。

6. 简单请求和预检请求

CORS 请求大致分两类:

7. 什么是简单请求?

简单请求不会先发 OPTIONS 预检。
常见条件包括:
方法是:
并且请求头比较简单。
例如:
浏览器可能直接发真实请求。
但服务端仍然要在响应里返回:
否则浏览器还是会拦截响应。

8. 什么是预检请求?

当请求比较“危险”或“复杂”时,浏览器会先自动发一个 OPTIONS 请求,问服务端:
我等下想发一个真正的请求,方法是 PUT,还带 Authorization 请求头,你允许吗?
例如前端请求:
浏览器可能先发:
服务端应该返回:
浏览器确认允许后,才会继续发真实的 PUT 请求。

9. 为什么 Postman、curl 能成功,浏览器却失败?

因为 CORS 是浏览器的安全策略。
下面这些一般不受 CORS 限制:
受 CORS 限制的是:
所以你看到:
这并不矛盾。
说明网络和接口可能是通的,但服务端没有给浏览器需要的 CORS 响应头。

10. 最常见的跨域错误

10.1 后端没有返回 Access-Control-Allow-Origin

报错类似:
原因:
解决:

10.2 允许的 Origin 不匹配

前端来源是:
后端返回:
浏览器会认为不匹配。
解决:
或者后端根据请求里的 Origin 动态判断并回显。

10.3 带 Cookie 时用了通配符

错误配置:
解决:
前端:

10.4 OPTIONS 请求没处理

浏览器先发了 OPTIONS,但后端没有处理,返回 404、405、500。
解决:
后端必须正确响应 OPTIONS 请求。
一般返回 200 或 204 即可。

10.5 没允许 Authorization

前端请求:
但后端预检响应没有:
解决:

10.6 网关或 Nginx 没放行

实际项目里 CORS 可能不是业务服务直接处理,而是在:
只要链路上有一层吞掉了 OPTIONS 或响应头,就会出问题。
排查时不要只看业务代码,要看完整链路。

11. 本地开发为什么经常遇到跨域?

因为前后端开发端口通常不同:
端口不同就是跨域。
常见解决方案有两种。

12. 方案一:后端配置 CORS

适合真实环境、联调环境、生产环境。
核心思想:
谁提供 API,谁负责告诉浏览器允许哪些前端访问。
例如允许:
不建议生产环境无脑允许:
尤其是带登录态的接口。

13. 方案二:前端开发代理

适合本地开发。
例如前端请求:
开发服务器代理到:
浏览器看到的仍然是:
因为浏览器认为它请求的是同源地址,所以不会触发跨域。
Vite 示例:
前端代码:
不要写成:
否则还是跨域。

14. Nginx 示例

简单公开 API:
带 Cookie 的 API:
如果需要多个 Origin,不建议直接写多个 Access-Control-Allow-Origin
正确做法是:

15. Express 示例


16. Go 示例


17. Spring Boot 思路

可以在 Controller、全局配置、网关层配置 CORS。
核心仍然是这几件事:
不要只在单个接口上加,结果登录接口好了,上传接口又坏了。
最好统一在全局中间件或网关层处理。

18. Cookie、登录态和 CORS

跨域登录最容易踩坑。
你需要同时满足:
服务端响应:
前端请求:
Cookie 自身也要正确设置。
如果是跨站 Cookie,通常需要:
注意:
这两个问题经常混在一起,但不是同一个东西。

19. CORS 和 CSRF 的区别

CORS 主要限制:
CSRF 主要问题是:
CORS 不能完全防 CSRF。
如果接口依赖 Cookie 登录,还应该考虑:
不要以为配了 CORS 就等于安全。

20. CORS 和反向代理的区别

CORS 是浏览器安全机制。
反向代理是一种架构方案。
例如:
浏览器看到的只有:
所以前端页面和 API 是同源的,自然不需要 CORS。
这就是为什么生产环境常见做法是:

21. 看到 CORS 报错时怎么排查?

按这个顺序查。

第一步:看请求 URL

前端页面地址是什么?
接口地址是什么?
协议、域名、端口是否完全一致?

第二步:看 Network 里有没有 OPTIONS

如果有 OPTIONS,说明触发了预检。
点开 OPTIONS 请求,看响应状态码。
正常应该是:
如果是:
优先处理这个问题。

第三步:看 OPTIONS 响应头

至少应该有:
如果真实请求带 Cookie,还需要:

第四步:看真实请求响应头

很多人只给 OPTIONS 加了 CORS Header,但真实请求响应没有加。
注意:
否则仍然失败。

第五步:看是否带 Credentials

如果前端用了:
或者 Axios:
那么后端不能返回:
必须返回具体 Origin。

第六步:看是否有重定向

预检或真实请求如果被重定向到登录页、HTTPS、其他域名,可能导致 CORS 失败。
比如:
浏览器最后看到的响应可能不是你以为的 API 响应。

第七步:看网关、CDN、Nginx

确认 CORS Header 有没有被覆盖、删除、重复添加。
特别注意:

22. 推荐的生产配置原则

22.1 不要无脑允许所有来源

不推荐:
尤其是后台管理系统、用户接口、支付接口、私有 API。

22.2 使用白名单

推荐:
后端读取请求的 Origin,判断是否在白名单中。
如果在,就回显:

22.3 带 Cookie 时显式配置

同时前端:
并保证 Cookie 的 SameSiteSecure 设置正确。

22.4 OPTIONS 请求要优先处理

中间件、网关、Nginx 应该能直接响应 OPTIONS
不要让预检请求进入复杂鉴权逻辑。
常见问题:

22.5 错误响应也要加 CORS Header

如果接口报 401、403、500,但响应里没有 CORS Header,前端看到的可能只是 CORS 错误,而不是实际错误。
所以 CORS Header 应尽量在统一中间件或网关层加,覆盖正常和异常响应。

23. 常见误区

误区一:前端加 Header 就能解决 CORS

错误。
CORS 主要靠服务端响应 Header 决定。
前端不能自己说“允许我跨域”。

误区二:后端接口能访问,所以不是后端问题

不一定。
Postman 能访问,只能说明接口可用。
浏览器能否访问,还要看服务端是否返回正确 CORS Header。

误区三:localhost127.0.0.1 一样

对操作系统来说可能指向同一台机器。
对浏览器同源策略来说,它们不是一个源。

误区四:只要加 Access-Control-Allow-Origin: * 就行

不一定。
公开接口可以考虑。
带登录态、Cookie、后台管理接口不应该这么配。

误区五:CORS 是为了保护服务端

更准确地说,CORS 是浏览器保护用户数据的机制。
服务端仍然必须做:
不能把安全寄托在 CORS 上。

24. 一套万能排查模板

以后遇到 CORS,可以按这个模板问自己:

25. 最小正确心智模型

记住这句话:
跨域不是请求发不出去,而是浏览器不把响应交给你的 JavaScript。
再记住这张流程:

26. 最佳实践总结

开发环境:
测试环境:
生产环境:

27. 最终记忆版

CORS 就看四件事:
带登录态再加两件事:
只要你把这几个点看懂,绝大多数跨域问题都能自己解决。
GitHub