Back

理解 CORS:为什么你的请求失败了

理解 CORS:为什么你的请求失败了

当你的 JavaScript 应用程序抛出 CORS 错误时,这并不是代码出了问题——而是浏览器在保护用户免受潜在的安全威胁。理解这些错误发生的原因,需要掌握浏览器处理跨域请求的基本安全模型。

核心要点

  • 当浏览器阻止缺少适当服务器权限的跨域请求时,就会发生 CORS 错误
  • 同源策略通过限制不同源之间的请求来保护用户
  • 简单请求直接进行,而复杂请求需要预检 OPTIONS 检查
  • 调试 CORS 需要检查浏览器网络选项卡并验证服务器响应头

同源策略基础

浏览器将同源策略作为其主要安全机制。一个源由三部分组成:协议(https://)、域名(example.com)和端口(:3000)。当这些完全匹配时,请求可以自由流动。当它们不同时——即使只是略有不同——你就在进行跨域请求。

考虑以下这些源:

  • https://app.example.comhttps://api.example.com(不同子域 = 不同源)
  • http://localhost:3000http://localhost:3001(不同端口 = 不同源)
  • http://example.comhttps://example.com(不同协议 = 不同源)

如果没有这个策略,任何网站都可以读取你的 Gmail、访问你的银行数据,或代表你发出请求。跨域资源共享(CORS)提供了对这些限制的受控放宽。

浏览器如何执行 CORS

浏览器充当执行者,而不是服务器。这个区别很重要,因为它解释了常见的误解:

  1. “在 Postman/curl 中可以工作” - 这些工具不是浏览器;它们不执行 CORS
  2. “添加 no-cors 模式可以修复它” - 这不会禁用 CORS;它会创建一个你无法读取的不透明响应
  3. “服务器正在阻止我的请求” - 服务器正常响应;浏览器阻止访问响应

当你发出跨域请求时,浏览器会自动添加一个 Origin 头。服务器必须响应一个与你的源匹配的 Access-Control-Allow-Origin 头(或对公共资源使用 *)。没有匹配的头意味着浏览器阻止访问响应——因此出现 CORS 错误。

简单请求与预检请求

并非所有请求都会触发相同的 CORS 行为。简单请求立即通过,而其他请求需要预检请求。

简单请求(无预检)

这些请求满足所有以下条件:

  • 方法:GETHEADPOST
  • 头部:仅简单头部(AcceptContent-LanguageContent-Type)
  • Content-Type:仅 application/x-www-form-urlencodedmultipart/form-datatext/plain

触发预检的请求

任何打破简单条件的请求都会触发 OPTIONS 预检:

  • PUTDELETEPATCH 这样的方法
  • 自定义头部(AuthorizationX-API-Key)
  • Content-Type: application/json(是的,JSON 会触发预检)

预检在发送实际请求之前请求权限。服务器必须响应适当的头部:

  • Access-Control-Allow-Methods(允许哪些方法)
  • Access-Control-Allow-Headers(允许哪些头部)
  • Access-Control-Max-Age(缓存此权限多长时间)

常见的 CORS 失败点

缺失或不正确的头部

服务器不返回 Access-Control-Allow-Origin 头,或者它与你的源不匹配。记住:http://localhost:3000http://localhost:3001 是不同的源。

凭证约束

包含凭证(credentials: 'include'withCredentials: true)会增加限制:

  • 服务器必须包含 Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin 不能是 *——它必须指定你的确切源
  • Cookie 必须满足 SameSite 要求

重定向复杂性

CORS 和重定向的交互很差。在 CORS 请求期间重定向到不同的源通常会失败,因为浏览器无法正确处理源的更改。避免在 CORS 请求中进行跨域重定向。

私有网络访问

当公共网站访问私有网络资源(localhost、192.168.x.x、10.x.x.x)时,现代浏览器会添加额外的保护。这些请求可能需要额外的头部,如 Access-Control-Allow-Private-Network,并且即使对于简单请求也会触发预检。这种保护防止外部网站扫描你的本地网络。

有效调试 CORS

面对 CORS 错误时:

  1. 检查浏览器的网络选项卡 - 查找预检 OPTIONS 请求并检查请求和响应头
  2. 验证确切的错误消息 - “CORS header ‘Access-Control-Allow-Origin’ missing”(缺少 CORS 头)与 “CORS header ‘Access-Control-Allow-Origin’ does not match”(CORS 头不匹配)指向不同的问题
  3. 首先在没有凭证的情况下测试 - 移除 credentials: 'include' 以隔离与凭证相关的问题
  4. 确认源匹配 - 确保协议、域名和端口都完全匹配

结论

CORS 错误不是任意的障碍——它们是浏览器保护用户免受恶意跨域请求的机制。服务器通过 CORS 头声明哪些源可以访问其资源,浏览器执行这些规则。理解这个模型可以将 CORS 从一个神秘的阻碍者转变为一个你可以有条不紊地调试的可预测系统。

当你的请求失败时,检查它是简单请求还是预检请求,验证服务器的 CORS 头是否与你的源完全匹配,并记住像 Postman 这样的工具完全绕过了这些检查。浏览器在做它的工作——你的任务是确保服务器提供正确的权限。

常见问题

Postman 和其他 API 测试工具不执行 CORS 策略。只有浏览器实施这些安全限制。你的服务器需要为浏览器请求发送适当的 Access-Control-Allow-Origin 头才能成功。

虽然浏览器提供了禁用安全功能的标志,但这是有风险的,并且会影响所有网站。相反,配置你的开发服务器发送适当的 CORS 头,或在开发期间使用代理服务器处理跨域请求。

CORS 规范仅将三种内容类型视为简单类型:application/x-www-form-urlencoded、multipart/form-data 和 text/plain。任何其他内容类型,包括 application/json,都会触发预检 OPTIONS 请求以验证权限。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay