你看到的 CORS error,不是“防病毒软件”在拦截,也不是后端坏了。它是浏览器的一套安全规则,核心目标是:
防止一个网站偷偷读取另一个网站里的用户数据。
严格说,大家口头说的“跨域保护”主要包括两层:
- 同源策略:Same-Origin Policy,简称 SOP。默认不让网页脚本随便读取其他来源的数据。
- CORS:Cross-Origin Resource Sharing。它不是“更严格的拦截”,而是“在安全前提下允许跨域访问”的标准机制。
MDN 对同源策略的定义是:浏览器限制一个来源加载的文档或脚本,如何与另一个来源的资源交互。所谓“同源”,要求协议、域名、端口都一样。比如 http://localhost:5176 和 http://localhost:8000 端口不同,所以不是同源。来源:MDN Same-origin policy、MDN CORS 文档。
一、如果没有跨域保护,会发生什么?
想象你已经登录了银行网站https://bank.example,浏览器里有银行网站的登录 Cookie。然后你又打开了一个恶意网站https://evil.example
如果浏览器没有同源限制,恶意网站可以在网页里写 JavaScript:
fetch("https://bank.example/api/account")
.then(res => res.json())
.then(data => sendToAttacker(data));
问题在于:浏览器请求 https://bank.example 时,可能会自动带上你在银行网站的 Cookie。银行后端看到 Cookie,以为这是你本人正常访问,于是返回账户数据。恶意网站如果能读取响应,就能把你的账户信息发走。
这就是跨域保护要解决的核心问题:浏览器既是用户访问网页的工具,又保存着用户在很多网站的登录状态。如果任意网页都能读取任意网站的数据,整个 Web 就没有隐私边界。
二、人们以前尝试过什么?为什么不够好?
Web 发展早期,大家既需要安全,又需要跨站协作。比如:
- 前端页面在
example.com - API 在
api.example.com - 图片、字体、脚本在 CDN
- 第三方支付、地图、登录服务都来自别的域名
如果完全禁止跨域,现代 Web 很多功能都做不了。所以人们尝试过一些办法。
1. 完全不限制:安全灾难
这是最直观但最危险的方案。任何网页都能读取其他网站的数据,恶意网站就可以利用用户已有登录状态窃取信息。
所以浏览器最终必须有一个默认安全边界。
2. 完全禁止跨域:功能不够用
如果浏览器严格禁止所有跨域行为,那么前端就不能直接请求第三方 API,CDN、公共接口、前后端分离开发都会很麻烦。
这也不符合 Web 的开放性。
3. JSONP:能用,但很危险
CORS 普及前,前端常用 JSONP 跨域取数据。它利用了一个历史规则:
<script src="https://api.example.com/data?callback=handleData"></script>
浏览器允许跨域加载 <script>,于是服务器返回:
handleData({ "name": "Alice" });
这样前端就拿到了数据。
但 JSONP 的本质是:把远程返回内容当 JavaScript 执行。这很危险,因为对方服务器如果返回恶意脚本,它就直接在你的页面里运行。JSONP 还主要适合 GET 请求,不适合复杂 API 权限控制。W3C CORS 标准的安全说明里也提到,CORS 是为了替代 JSONP、服务端代理、跨文档消息等旧式跨域共享办法。
4. 后端代理:能控制,但成本更高
还有一种办法是让自己的后端去请求第三方 API:
浏览器 -> 自己后端 -> 第三方 API
这样浏览器没有直接跨域,安全边界更清楚。但它会增加后端负担,也要求服务端正确处理鉴权、缓存、限流和错误。
后端代理不是失败方案,只是它不适合作为浏览器跨域访问的通用标准。
三、人们如何想到“跨域保护”这套设计?
关键想法是:给网页划分“来源”。
一个来源由三部分组成:
协议 + 域名 + 端口
例如http://localhost:5176,来源是:
协议:http
域名:localhost
端口:5176
下面这些都不是同源:
https://localhost:5176 # 协议不同
http://127.0.0.1:5176 # 域名不同
http://localhost:8000 # 端口不同
同源策略的设计思路是:默认情况下,一个来源的脚本不能读取另一个来源的敏感响应。
但 Web 又确实需要跨域访问,所以后来有了 CORS。CORS 的设计思路是:默认禁止,由资源拥有者明确声明哪些来源可以访问。
比如后端返回:
Access-Control-Allow-Origin: http://localhost:5176
我这个 API 允许 http://localhost:5176 的网页读取响应。如果浏览器发现请求来源是 http://localhost:5176,响应里也允许它,就放行。否则就报 CORS 错误。
对于复杂请求,比如带自定义 header、PUT、DELETE、某些 POST,浏览器还会先发一个预检请求:
OPTIONS /api/user
Origin: http://localhost:5176
Access-Control-Request-Method: DELETE
后端如果允许,再返回:
Access-Control-Allow-Origin: http://localhost:5176
Access-Control-Allow-Methods: DELETE
然后浏览器才会发真正的请求。
四、CORS 到底保护了什么?不保护什么?
它主要保护:
- 恶意网页读取你在其他网站的私密数据
- 未被授权的前端读取 API 响应
- 某些复杂跨域请求在真正发送前被浏览器拦下
但它不等于万能安全机制。
CORS 不负责防病毒
病毒通常是操作系统或文件层面的恶意程序。CORS 是浏览器里的 Web 安全边界。
CORS 不等于登录鉴权
就算你配置了 CORS,后端仍然必须检查:
- 用户是否登录
- token 是否有效
- 用户是否有权限访问这条数据
CORS 不完全等于 CSRF 防护
有些跨站请求仍可能被发送,只是浏览器不让恶意网页读取响应。比如传统 HTML 表单提交在历史上就是允许跨站发送的。
所以重要操作仍然需要:
- CSRF token
- SameSite Cookie
- 检查
Origin/Referer - 服务端权限判断
五、如何正确使用跨域保护?
例子 1:本地开发只允许本地前端
如果你的前端跑在:
http://localhost:5176
后端跑在:
http://localhost:8000
后端可以配置:
CORS_ORIGINS=http://localhost:5176
这表示只允许这个前端来源访问后端 API。
例子 2:生产环境只允许真实前端域名
如果线上前端是:
https://app.example.com
后端 API 是:
https://api.example.com
后端应该返回:
Access-Control-Allow-Origin: https://app.example.com
而不是:
Access-Control-Allow-Origin: *
尤其是涉及登录态、Cookie、用户数据的 API,更不能随便开放给所有来源。
例子 3:公共资源可以用 *
如果资源本来就是公开的,比如公共字体、公开 JS 库、公开图片资源,可以允许任意来源读取:
Access-Control-Allow-Origin: *
例子 4:带 Cookie 的请求要更谨慎
如果前端请求需要带 Cookie:
fetch("https://api.example.com/me", {
credentials: "include"
});
后端不能这样:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
正确做法是明确指定可信来源,并且后端仍然要检查用户登录状态和权限。
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
例子 5:不要“反射”所有 Origin
有些后端会这样写逻辑:
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
这相当于来谁都信谁。恶意网站请求时带:
Origin: https://evil.example
后端就返回:
Access-Control-Allow-Origin: https://evil.example
这样 CORS 就失去意义了。
正确做法是维护白名单:
const allowedOrigins = [
"https://app.example.com",
"http://localhost:5176"
];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
}
例子 6:不要信任不安全的 HTTP 来源
如果你的站点是 HTTPS,却允许:
Access-Control-Allow-Origin: http://app.example.com
那中间人攻击者可能篡改 HTTP 页面,再利用这个被信任的来源访问你的 HTTPS API。本地开发可以用 http://localhost,但线上生产环境应尽量使用 HTTPS。
参考资料
- MDN: Same-origin policy
- MDN: Cross-Origin Resource Sharing CORS
- MDN: CORS configuration
- RFC 6454: The Web Origin Concept
- W3C: Cross-Origin Resource Sharing Recommendation, 2014
- W3C: CORS is a W3C Recommendation
- WHATWG Fetch Standard
- OWASP: Cross-Site Request Forgery Prevention Cheat Sheet
- OWASP Top 10:2021 A01 Broken Access Control
- PortSwigger Web Security Academy: Same-origin policy
- PortSwigger: Cross-origin resource sharing, unencrypted origin trusted
- Mind the CORS: Large-scale analysis of CORS misconfigurations