CORS 配置有哪些安全风险?为什么 CORS 不能防 CSRF?
简化版
CORS 是浏览器根据服务端响应头决定“跨源脚本能不能读取响应”的机制,不是服务端访问控制,也不能防 CSRF。安全配置要避免随意反射 Origin,带凭证请求不能使用 *,需要精确白名单、正确处理预检、配合 CSRF 防护和 Vary: Origin。
详细版
同源策略限制的是跨源脚本读取响应,CORS 是服务端声明允许哪些 Origin 读取资源。危险配置包括 Access-Control-Allow-Origin 动态反射任意 Origin、同时允许凭证、忘记区分开发和生产域名、缓存层未加 Vary: Origin。
CSRF 关注的是“跨站请求是否能借用户 Cookie 改变状态”。很多跨站请求即使读不到响应也能发出去,所以 CORS 不能替代 CSRF Token、SameSite Cookie、Origin/Referer 校验。
完整版教学
一、CORS 管的是“读响应”
浏览器允许页面发出一些跨源请求,但会根据 CORS 响应头决定是否把响应交给 JavaScript。服务器可能已经收到了请求并执行了逻辑,只是浏览器不让脚本读取结果。
evil.com JS -> bank.com/transfer
服务器可能收到请求
浏览器再判断响应能否交给 JS
所以不能把“前端报 CORS 错”理解为“请求一定没到服务端”。面试里这一点非常容易被追问。
记忆钩子:CORS 管“脚本能不能读到响应”,不是管“请求能不能发出去”,也不是服务端鉴权。
二、危险的 Origin 反射
有些服务会把请求里的 Origin 原样写回 Access-Control-Allow-Origin,再配上 Access-Control-Allow-Credentials: true。这等于允许任意站点读取带用户凭证的响应。
Origin: https://evil.example
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Credentials: true
正确做法是维护明确白名单,只允许可信域名。白名单匹配也要精确,不要用字符串包含判断。
三、带凭证请求的规则
跨源请求如果要带 Cookie 或 HTTP 认证,前端要设置 credentials,服务端要返回明确 Origin 和 Access-Control-Allow-Credentials: true。此时 Access-Control-Allow-Origin: * 不能和凭证一起使用。
| 场景 | Allow-Origin | Credentials | 结果 |
|---|---|---|---|
| 公共资源 | * | false | 可读 |
| 带 Cookie | 明确 Origin | true | 可读 |
| 带 Cookie | * | true | 浏览器阻止 |
这个规则保护的是响应读取权限,不代表接口本身完成了权限校验。
四、预检请求有什么意义
非简单请求会先发 OPTIONS 预检,询问服务端允许哪些方法和请求头。预检通过后,浏览器才发送真实请求。
OPTIONS /api
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization
预检能减少一些意外跨源写操作,但它不是认证机制。真正的接口仍要做登录态、权限、CSRF 或签名校验。
五、为什么 CORS 不能防 CSRF
CSRF 的核心是攻击者让用户浏览器发出带 Cookie 的状态变更请求。攻击者不一定需要读取响应,只要请求成功执行就够了。
<form action="https://bank.example/transfer" method="POST">
<input name="amount" value="1000">
</form>
这类请求是否读得到响应,不影响转账是否被服务端执行。防 CSRF 要靠 SameSite、CSRF Token、Origin/Referer 校验和关键操作二次确认。
六、缓存和 Vary: Origin
当服务端按 Origin 动态返回不同的 CORS 头时,CDN 或代理缓存必须知道响应会随 Origin 变化。否则 A Origin 的允许头可能被缓存后错误给到 B Origin。
Vary: Origin
如果没有 Vary: Origin,缓存层可能制造跨租户或跨站读取风险。这个点在工程面试里很加分,因为它连接了安全和缓存。
七、常见配置清单
生产 CORS 配置可以按清单检查。只允许明确域名,方法只开放必要项,请求头只开放必要项,凭证请求只给可信前端,预检缓存时间不要无限长。
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
Vary: Origin
开发环境的 localhost:* 不应直接带到生产。多租户系统还要防止租户 A 的 Origin 被错误授权访问租户 B 的数据。
八、排查 CORS 报错
排查时要分别看预检请求和真实请求。浏览器控制台提示通常比 Network 面板状态码更重要,因为状态码 200 也可能因为响应头不满足规则而被浏览器拦截。
OPTIONS 是否成功
Allow-Origin 是否精确匹配
Allow-Headers 是否包含自定义头
credentials 是否两端一致
是否发生 301/302 重定向
很多 CORS 问题来自重定向后丢头、网关只给真实请求加头、预检没有走鉴权放行。
九、常见误区与追问
- 误区:配置 CORS 就能防 CSRF。 CORS 管响应读取,CSRF 管跨站状态变更请求。
- 误区:浏览器报 CORS,服务端一定没处理。 简单请求可能已经到达服务端,只是响应未暴露给 JS。
- 误区:反射 Origin 很方便也安全。 任意 Origin 加凭证会泄露用户数据。
- 追问:为什么带凭证不能用
*? 浏览器要求明确信任来源,避免任意站点读取凭证响应。 - 追问:预检请求是否等于鉴权? 不是,预检只是跨源能力协商。
- 追问:动态 CORS 为什么要加 Vary? 告诉缓存响应随 Origin 变化,避免复用错误头。
- 追问:生产跨域应该怎么配? 精确白名单、最小方法和头、分环境配置、配合 CSRF 防护。
十、加强记忆
CORS 的记忆主线是“谁能读响应”,CSRF 的主线是“谁能借身份改状态”。CORS 配错会造成跨站读取敏感数据,但它不是服务端鉴权,也不是 CSRF 防线。生产配置要白名单、凭证请求明确 Origin、预检最小授权、动态响应加 Vary: Origin。