CORS

6 阅读5分钟

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 请求)时,浏览器的处理流程如下:

  1. 发送: 浏览器直接把请求发给后端(如果携带了 Cookie,也会一起带上)。
  2. 执行: 后端收到请求,执行业务逻辑(比如修改数据库、转账、删除用户)。
  3. 响应: 后端处理完毕,返回 HTTP 200 和数据。
  4. 拦截: 浏览器接到响应,检查响应头中是否包含允许跨域的字段(如 Access-Control-Allow-Origin)。
    • 如果没有,浏览器拦截响应数据,报错给前端 JS。
    • 如果有,将数据交给 JS。

结论: 即使 CORS 报错,后端的副作用(Side Effect)已经发生了! 就像是你给隔壁班同学写情书,保安让你寄出去了,同学看完了(业务执行了),回信时保安才把信扣下销毁。你以为信没寄到,其实对方已经读完了。

2. 复杂请求:先奏后斩(安全的预检)

当请求包含特殊头部(如 Authorization)或 Content-Type 为 application/json 时,浏览器会启动预检机制(Preflight)

  1. 探路: 浏览器自动发送一个 OPTIONS 请求,询问服务器:“我能发这个请求吗?”
  2. 审核: 服务器检查 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>

发生了什么?

  1. 这是一个 Form 表单提交,属于简单请求
  2. 浏览器发现你要访问 bank.com,于是贴心地带上了你在 bank.com 的 Cookie。
  3. 请求直达后端,后端验证 Cookie 有效,执行转账操作
  4. 后端返回“转账成功”。浏览器发现 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。后端认为未登录,直接拦截。

四、 总结

  1. CORS 不是万能盾牌: 对于简单请求,CORS 只能阻止前端“读取”结果,不能阻止请求“发送”和后端“执行”。
  2. 区分请求类型: 调试时请注意,报错红字并不代表后端没处理,可能你的数据库已经被改脏了。
  3. 防御纵深:
    • CORS 防止数据泄露(读)。
    • CSRF Token / SameSite 防止恶意操作(写)。

做 Web 开发,既要懂“怎么通”(解决 CORS 报错),更要懂“怎么堵”(防止 CSRF 漏洞)。希望这篇文章能让你对浏览器安全机制有更立体的认知。