不喜欢广告? 无广告 今天

CORS 详解 为什么你的 fetch 在 curl 中可以正常工作,但在浏览器中却会出错

更新于

CORS 错误感觉像是服务器错误。其实并非如此。它们是浏览器强制执行的权限检查。理解同源策略、预检请求以及五个关键头部,并在五分钟内解决每一个 CORS 错误。

CORS 解释:为什么你的 fetch 在 curl 中可以正常工作,但在浏览器中却会崩溃 1
广告 移除?

你写了一个 fetch() 调用。它在 curl 中运行完美。你打开浏览器,访问该端点,却收到:

Access to fetch at 'https://api.example.com/data' from origin 'https://yourapp.com' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present 
on the requested resource.

你去谷歌搜索。Stack Overflow 建议你添加一个头部。你添加了头部,但错误变了。你又添加了另一个头部,现在变成了预检错误。花了两个小时,你终于在 Chrome 中通过一个 2016 年的答案找到的标志禁用了 CORS。你就这样把代码部署到生产环境,希望没人注意到。

本文存在的目的就是阻止这种情况发生。正确理解 CORS 大约需要十分钟,一旦理解了,每次修复只需两分钟。

这不是服务器的 bug,而是浏览器的规则。

关于 CORS 最重要的一点是: 它完全由浏览器强制执行。服务器不会阻止你的请求。浏览器在服务器已经响应之后,会自行拦截并阻止请求。

这就是为什么 curl 能正常工作。curl 不会强制执行 CORS。Postman 也不会。你的后端调用另一个后端也不会。只有当浏览器代表网页发起跨域请求时,CORS 才会被激活。

服务器的任务是决定是否允许 跨域访问,通过响应头来实现。浏览器的任务是检查这些响应头,并决定是否将响应交给你的 JavaScript。如果响应头不正确,浏览器会丢弃响应并抛出错误——即使请求已经完成,服务器也已返回数据。 浏览器为何这样做(同源策略)

同源策略(SOP)是浏览器的一项安全规则,它防止 JavaScript 在

上运行时读取来自 https://evil.com 的响应。如果没有这项策略,任何网站都可以在你不知情的情况下悄悄发起身份验证请求并读取结果。 https://yourbank.com两个 URL 具有相同来源,当它们的

协议、主机名和端口 完全匹配时: — 不同来源(不同的子域名)

  • https://app.example.comhttps://api.example.com — 不同来源(不同的协议)
  • http://example.comhttps://example.com — 不同来源(不同的端口)
  • https://example.comhttps://example.com:8080 — 相同来源(路径不计)
  • https://example.com/foohttps://example.com/bar CORS 是服务器通过响应头明确允许放松同源策略的一种方式。这不是绕过,而是一种明确的许可授权。

简单请求与预检请求

并非所有跨域请求都会触发预检。浏览器将请求分为两类。

直接通过(无需先进行 OPTIONS 检查)。当请求满足以下所有条件时,它被称为“简单请求”:

简单请求 方法是 GET、POST 或 HEAD

  • Content-Type 是
  • 没有超出 application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain
  • 的自定义头部 CORS 安全列表

预检请求 是所有其他情况——任何 PUT、PATCH、DELETE 方法,任何带有体的内容,任何自定义头部如 application/json 。在实际请求之前,浏览器会自动发送一个 OPTIONS 请求,询问“是否允许此请求?” AuthorizationX-API-Key预检请求实际上是什么样子

这里是一个真实的预检交互示例。你的前端调用

浏览器首先自动发送这个 OPTIONS 请求——你不需要编写这段代码: fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' } }).

服务器必须响应,确认允许此请求:

OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://yourapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

只有当预检成功时,浏览器才会发送实际的 POST 请求。如果任何预检头部缺失或错误,实际请求将永远不会离开浏览器。

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400

缓存预检结果 24 小时,以避免浏览器在每次请求时都重复这个握手过程。值得设置。

Access-Control-Max-Age: 86400 — 唯一必需的头部。可以是特定来源(

真正重要的响应头

Access-Control-Allow-Origin 适用于任何来源。当你发送凭证(如 Cookie、Authorization 头)时,不能使用https://yourapp.com)或 * Access-Control-Allow-Methods * — 对于预检请求是必需的。必须列出你希望允许的所有方法。始终包含 OPTIONS,否则某些服务器会在预检中返回 405 错误。

Access-Control-Allow-Headers — 当请求包含自定义头部时是必需的。必须明确列出前端发送的每一个非安全列表头部。缺少

会导致大约 40% 个 CORS 支持工单。 Access-Control-Allow-Credentials: true Authorization — 仅当发送 Cookie 或 HTTP 认证时才需要。必须在 fetch 调用中设置

。当此值为 true 时, 必须是明确的来源—— credentials: 'include' 否则将无法工作,你将收到第二个独立错误。 Allow-Origin Access-Control-Expose-Headers * — 响应头部,前端可以读取。默认情况下,JavaScript 只能访问一小部分安全列表头部(如 Content-Type、Cache-Control 等)。如果你需要读取响应中的自定义头部如

,请在此处添加。 五个导致数小时损失的常见错误 X-Request-Id 1. 使用通配符配合凭证。

然后困惑为什么带有凭证的请求仍然失败。规范明确禁止在涉及凭证时使用通配符。你必须反映具体的来源:读取请求头,并在响应中回显它。

2. 完全不处理 OPTIONS 请求。 环境 Access-Control-Allow-Origin: * Express、FastAPI 和大多数框架不会自动对 OPTIONS 请求返回 CORS 头部。如果你的 CORS 中间件只在实际端点上运行,而不在预检路由上运行,那么所有带有凭证或非简单请求都将失败。解决方法:确保你的 CORS 中间件在路由之前运行,或显式处理 OPTIONS 请求。 Origin 3. 仅在成功时返回 CORS 头部。

如果服务器返回 4xx 或 5xx 错误但没有 CORS 头部,浏览器会隐藏真实的 HTTP 状态码,JavaScript 只能收到一个通用的网络错误,且无状态码。必须在每个响应中返回 ,而不仅仅是在 200 响应中返回。

4. 忘记端口。 端口不同即为不同来源。在本地开发时,你经常会从 3000 端口的前端访问 8080 端口的后端。这两个端口都必须包含在允许的来源列表中,否则需要使用开发代理。 Access-Control-Allow-Origin 5. 缓存了错误的预检结果。

缓存了预检结果。如果你将其设置为 86400,然后更新了允许的头部,浏览器会因缓存的错误预检结果而持续失败长达 24 小时。在开发过程中,要么将其设置为 0,要么通过强制刷新并禁用 DevTools 缓存来清除缓存。 https://app.example.comhttps://app.example.com:3000 根据场景的快速修复方案

前端与后端在不同域名,无 Cookie: Access-Control-Max-Age (或具体的前端来源) 到你的服务器。完成。

前端与后端在不同域名,带有 Cookie 或身份验证:

你需要在服务器上设置 添加 Access-Control-Allow-Origin: * (明确指定,不能使用通配符) 和

,并在 fetch 调用中设置 。Cookie 也需要 Access-Control-Allow-Origin: https://yourfrontend.com 才能跨域。 Access-Control-Allow-Credentials: true 本地开发访问你无法控制的远程 API: credentials: 'include' 在构建工具中添加一个开发代理。Vite: SameSite=None; Secure 。webpack-dev-server:

。Next.js: 。代理使浏览器认为所有请求都是同源的;CORS 从此不会触发。 server.proxyAPI 网关 / CDN 位于前端: devServer.proxy确保 OPTIONS 预检请求不会在到达源服务器前被吞掉。AWS API Gateway 和 CloudFront 都有 CORS 设置,可能与你的应用级头部冲突——如果你在两个地方都设置了头部,你可能会遇到重复的头部值,从而导致失败。 rewrites如果你需要验证服务器实际返回的头部,

在 IO Tools 中可以让你无需编写任何代码即可构造并检查完整的头部集合。这对于审计后端实际返回的内容与你预期的内容非常有用。 CORS 错误看起来像服务器 bug,因为错误信息提到了服务器。它们实际上是浏览器强制执行的权限检查——一旦你了解缺少哪个头部及其原因,每个 CORS 错误都能在几分钟内解决。使用

CORS 头部生成器 CORS Headers Builder & Validator 或使用

来检查客户端错误,避免在修改服务器配置前触及问题。 CORS 解释:为什么你的 fetch 在 curl 中可以正常工作,但在浏览器中却会出错 2 CORS 解释:为什么你的 fetch 在 curl 中可以正常工作,但在浏览器中却会出错 1 cURL 命令生成器 在修改服务器配置之前,先排除客户端错误。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?