CORS 的真相:它没能拦住你的请求,而后端正面临“裸奔”风险
在 Web 开发中,CORS(跨域资源共享)报错是每一位全栈/前端开发者的“老朋友”。每当我们看到控制台那行红色的 Access to XMLHttpRequest... has been blocked by CORS policy 时,第一反应往往是:“请求被浏览器拦截了,后端没收到。”
但真相往往比这更残酷: 在很多情况下,你的请求不仅发出去了,后端甚至已经执行完了业务逻辑,只是浏览器最后“翻脸不认人”,把响应数据扣下了。
这就引出了一个极易被忽视的安全黑洞。本文将深入剖析 CORS 的请求拦截机制,并揭示为什么**“仅靠 CORS 保护不了你的后端”**。
一、 误区:浏览器到底拦截了什么?
我们需要纠正一个核心观念:CORS 并不是一道绝对的防火墙。
根据请求类型的不同,浏览器的拦截行为分为两种完全不同的模式:“先斩后奏”和“先奏后斩”。
1. 简单请求:先斩后奏(最危险的误区)
当你的请求满足“简单请求”条件(通常是 GET 请求,或 Content-Type 为 application/x-www-form-urlencoded 的 POST 请求)时,浏览器的处理流程如下:
- 发送: 浏览器直接把请求发给后端(如果携带了 Cookie,也会一起带上)。
- 执行: 后端收到请求,执行业务逻辑(比如修改数据库、转账、删除用户)。
- 响应: 后端处理完毕,返回 HTTP 200 和数据。
- 拦截: 浏览器接到响应,检查响应头中是否包含允许跨域的字段(如
Access-Control-Allow-Origin)。- 如果没有,浏览器拦截响应数据,报错给前端 JS。
- 如果有,将数据交给 JS。
结论: 即使 CORS 报错,后端的副作用(Side Effect)已经发生了! 就像是你给隔壁班同学写情书,保安让你寄出去了,同学看完了(业务执行了),回信时保安才把信扣下销毁。你以为信没寄到,其实对方已经读完了。
2. 复杂请求:先奏后斩(安全的预检)
当请求包含特殊头部(如 Authorization)或 Content-Type 为 application/json 时,浏览器会启动预检机制(Preflight):
- 探路: 浏览器自动发送一个
OPTIONS请求,询问服务器:“我能发这个请求吗?” - 审核: 服务器检查 CORS 配置。
- 如果拒绝(或未配置),浏览器直接报错,真正的业务请求根本不会发出。
- 如果通过,浏览器才会发送真正的请求。
结论: 只有在复杂请求下,后端才是安全的,业务逻辑不会被恶意触发。
二、 安全隐患:CORS 挡不住的 CSRF 攻击
既然“简单请求”会直接穿透到后端执行,那么问题来了:如果后端完全依赖 CORS 来做安全防护,会发生什么?
答案是:CSRF(跨站请求伪造)攻击。
攻击场景还原
假设你登录了某网银网站 bank.com,浏览器存了你的登录 Cookie。此时,攻击者诱导你访问了恶意网站 evil.com。
evil.com 的页面里隐藏了这样一段代码:
<!-- 这是一个简单 POST 请求,浏览器会直接发送 -->
<form action="http://bank.com/api/transfer" method="POST">
<input type="hidden" name="to" value="HackerAccount" />
<input type="hidden" name="amount" value="10000" />
</form>
<script>document.forms[0].submit();</script>
发生了什么?
- 这是一个 Form 表单提交,属于简单请求。
- 浏览器发现你要访问
bank.com,于是贴心地带上了你在bank.com的 Cookie。 - 请求直达后端,后端验证 Cookie 有效,执行转账操作。
- 后端返回“转账成功”。浏览器发现
evil.com不在 CORS 许可名单里,拦截了响应内容,报错。
攻击者的心态: “哈哈!虽然浏览器不让我看‘转账成功’的 JSON 数据(被 CORS 拦住了),但我根本不在乎响应内容,只要钱转出去就行了!”
核心结论:
- CORS 的本质是“隐私保护”:它防止恶意网站读取你的敏感数据。
- CSRF 的本质是“操作伪造”:它利用你的身份去写入/修改数据。
- CORS 防不住简单请求的写操作。
三、 后端自保指南:如何封堵漏洞?
既然 CORS 在简单请求下是“马后炮”,后端必须构建第二道防线。
1. 严格遵守 HTTP 语义
GET 请求必须是只读的。
千万不要设计 GET /deleteUser?id=1 这种接口。因为攻击者只需要在页面放一个 <img src="..."> 就能触发 GET 请求,连 Form 表单都不用。
所有修改数据的操作(增删改),必须使用 POST、PUT 或 DELETE。
2. 使用 CSRF Token(标准防线)
这是防御 CSRF 的终极手段。
- 原理: 后端生成一个随机的 Token(不同于 Cookie),前端在提交请求时(Body 或 Header)必须带上这个 Token。
- 效果: 攻击者无法通过跨域读取到你页面里的 Token(这正是 CORS 发挥作用的地方:阻止读取)。因此,攻击者伪造的请求里只有 Cookie,没有 Token,后端直接拒绝。
3. 配置 SameSite Cookie(现代防线)
在后端设置 Cookie 时,添加 SameSite 属性:
Set-Cookie: session_id=xyz; SameSite=Strict;
- Strict: 只有当前页面 URL 和 API 域名完全一致时,浏览器才发送 Cookie。
- Lax: 允许部分导航行为发送 Cookie,但跨域的 POST/AJAX 请求不会携带 Cookie。
- 效果: 攻击者在
evil.com发起请求时,浏览器发现跨站了,根本不带 Cookie。后端认为未登录,直接拦截。
四、 总结
- CORS 不是万能盾牌: 对于简单请求,CORS 只能阻止前端“读取”结果,不能阻止请求“发送”和后端“执行”。
- 区分请求类型: 调试时请注意,报错红字并不代表后端没处理,可能你的数据库已经被改脏了。
- 防御纵深:
- 用 CORS 防止数据泄露(读)。
- 用 CSRF Token / SameSite 防止恶意操作(写)。
做 Web 开发,既要懂“怎么通”(解决 CORS 报错),更要懂“怎么堵”(防止 CSRF 漏洞)。希望这篇文章能让你对浏览器安全机制有更立体的认知。