指南

如何修复 CORS 错误(以及它为什么会出现)

CORS 错误是指你的网页尝试读取来自另一个源(不同的域名、端口或协议)的响应,而该服务器没有发送允许访问的响应头。浏览器会阻止这个响应,以保护用户。解决方法是在服务器上为你网站的源返回 Access-Control-Allow-Origin 响应头,并处理 OPTIONS 预检请求;或者通过你自己的同源后端发送请求,从而避免这个问题。

这是 Web 开发中最令人困惑的错误之一,因为请求通常确实已经到达了服务器。本指南将解释其中的原理,以及如何修复每一种情况。

什么是“源”

源由协议、域名和端口组合而成:

URL源
https://myapp.com/dashboardhttps://myapp.com
https://api.myapp.com/usershttps://api.myapp.com(子域名不同,因此源也不同)
http://localhost:5173http://localhost:5173
http://localhost:3000http://localhost:3000(端口不同,因此源也不同)

浏览器遵循同源策略:页面上的 JavaScript 可以自由读取来自自身源的响应,但不能读取来自其他源的响应。CORS(跨域资源共享)是一套机制,让服务器可以主动选择开放:“来自这些源的页面可以读取我的响应。”

有两个结果会让人感到意外:

  • 请求通常确实到达了服务器。 CORS 通常阻止的是你的代码读取响应,而不是服务器接收请求。即使你看到了错误,一次 POST 请求可能已经被保存了。
  • 只有浏览器会执行它。 curl、Postman 和服务端代码完全忽略 CORS。因此,“在 Postman 中能用”并不能说明任何关于 CORS 的问题。

第一步:阅读确切的错误信息

打开开发者工具(右键 → 检查),切换到 控制台,阅读完整的消息。Chrome 的措辞会告诉你是哪种问题:

消息包含问题所在
No 'Access-Control-Allow-Origin' header is present服务器根本没有发送该响应头,或者请求在到达你的 CORS 代码之前就已经失败(例如返回 404 或 500)
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin服务器允许的是另一个源,而不是你的源
Response to preflight request doesn't pass access control checkOPTIONS 预检请求失败
...must not be the wildcard '*' when the request's credentials mode is 'include'你发送了 Cookie,而服务器回复的是 *
Request header field authorization is not allowed服务器没有在 Access-Control-Allow-Headers 中列出该请求头

然后打开 网络 标签页,找到失败的请求(以及在它之前的任何 OPTIONS 请求),查看它的状态码和响应头。

先检查状态码。 许多“CORS 错误”实际上是伪装的服务器错误。如果服务器崩溃或返回 404,它的错误响应通常不包含 CORS 响应头,因此浏览器会报告 CORS 错误,而不是真正的问题。修复 500 或 404 之后,CORS 错误可能就会消失。

第二步:先决定是否应当从浏览器直接调用该 API

在添加响应头之前,先问自己:浏览器应该直接调用这个 API 吗?

  • 需要密钥的第三方 API(OpenAI、Stripe 的私钥、大多数邮件服务)绝不应当从浏览器调用。密钥会暴露给每一位访客。请改由你的后端调用,CORS 问题也随之消失,因为服务器到服务器的请求不受 CORS 限制。参见如何安全地保管 API 密钥。
  • 你无法控制、且不发送 CORS 响应头的第三方 API,无法从你这边修复。请由你的后端调用它。
  • 你自己的 API 可以通过正确配置来修复。请继续阅读下文。

第三步:在服务器上修复

服务器必须在其响应中添加正确的响应头,包括错误响应。

对于实际请求,需要发送:

  • Access-Control-Allow-Origin: https://myapp.com(前端的确切源,末尾不要加斜杠)
  • 如果该值取决于请求来自哪个源,则需要 Vary: Origin

对于预检 OPTIONS 请求,以成功状态码(200 或 204)响应,并包含:

  • Access-Control-Allow-Origin: https://myapp.com
  • Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
  • Access-Control-Allow-Headers: Content-Type, Authorization(列出你发送的每一个自定义请求头)
  • 可选的 Access-Control-Max-Age: 600,让浏览器在一段时间内缓存预检结果

大多数框架都有相应的软件包或设置可以完成这些工作。在 Express 中使用 cors 包;在 FastAPI 中使用 CORSMiddleware;在 ASP.NET Core 中使用 AddCors;在 Spring 中使用 @CrossOrigin 或 CORS 配置。一个典型的 Express 配置如下:

import cors from "cors";

app.use(cors({
  origin: ["https://myapp.com", "http://localhost:5173"],
  credentials: true,
}));

请把 CORS 处理放在路由和身份验证之前,这样预检请求(不携带登录信息)就不会因为 401 而被拒绝。

如果使用 AI 搭建工具,请精确描述设置:

位于 https://myapp.com 的前端调用 https://api.myapp.com 的 API,并收到“Response to preflight request doesn't pass access control check”。请在 API 上配置 CORS,只允许 https://myapp.com 和预览源访问;允许带 Content-Type 和 Authorization 请求头的 GET、POST、PATCH、DELETE;在身份验证之前处理 OPTIONS;并且也要在错误响应中包含 CORS 响应头。

第四步:处理 Cookie 和凭证

如果你的前端需要向另一个源发送 Cookie(例如登录会话),双方必须达成一致:

  • 前端在 fetch 中设置 credentials: "include",或在 axios 中设置 withCredentials: true。
  • 服务器发送 Access-Control-Allow-Credentials: true。
  • 服务器的 Access-Control-Allow-Origin 必须是确切的源,而不能是 *。

跨站 Cookie 还需要在 Cookie 上设置 SameSite=None; Secure,而且有些浏览器还会进一步限制第三方 Cookie。这也是为什么应当优先采用第五步的方法。

第五步:通过同源配置避免 CORS

通常最干净的解决方法是根本不发起跨源请求。让 API 与页面处于同一个源下提供服务,例如 https://myapp.com/api/...:

  • 在开发环境中,大多数开发服务器都支持代理。Vite 的 server.proxy 设置可以把 /api 转发到另一端口上的后端,这样浏览器只与一个源通信。
  • 在生产环境中,通过托管平台的重写或代理设置,将 /api 路由到后端;或者让前端和 API 运行在同一台服务器上。

这样,前端只需调用相对路径,例如 fetch("/api/orders")。不存在跨源请求,不需要跨源预检,也不会有 Cookie 方面的麻烦。这还意味着你不必在代码中硬编码一个在预览环境和生产环境之间不同的 API 域名。

将前端改为使用相对的 /api 路径调用后端,而不是完整的 URL,并设置开发代理,使 /api 转发到 API 服务器。然后删除我们不再需要的所有 CORS 设置。

不要这样做

  • 在 fetch 中使用 mode: "no-cors"。 它并不会绕过任何限制,只会给你的代码一个空的、无法读取的响应。
  • 禁用 CORS 的浏览器扩展。 它们只改变你自己的浏览器。每一位访客仍然会看到错误。
  • 在真实应用中使用公共的“CORS 代理”服务。 它们可以读取并修改经过它们的所有内容,而且随时可能宕机。
  • 对使用登录的 API 设置 Access-Control-Allow-Origin: *。 无论如何它都无法与凭证配合使用,而且对于私有数据来说,这是错误的默认设置。
  • 在允许凭证的情况下,把任意 Origin 请求头原样返回。 这会让任何网站代表你的用户发出已登录的请求。请使用白名单。

AI 搭建应用中的常见原因

情况修复方法
前端使用密钥调用第三方 API把调用移到后端
在预览中正常,在已发布的域名上失败把已发布域名和自定义域名的源加入白名单,或使用相对的 /api 路径
只在 POST、PUT 或 DELETE 请求时出错未处理预检请求;添加 OPTIONS 处理和允许的请求头
添加登录功能之后出现错误凭证需要确切的源以及 Allow-Credentials
出现“CORS 错误”,但网络标签页显示 500先修复服务器错误
API 地址硬编码为 localhost使用相对路径,或为每个环境设置单独的配置

Mythex 中的 CORS

当一个 Mythex 项目同时包含网站和 API 服务器时,两者都会作为服务在同一个项目中运行,你可以在预览中在它们之间切换。请让智能体把前端通过相对的 /api 路径调用后端,并在开发环境中进行代理,这样可以避免大多数 CORS 问题。如果你确实需要跨源调用,请把控制台中的确切错误以及涉及的源告诉智能体。由于已发布应用的地址与预览不同,也请在已发布的应用上检查结果。相关内容请参阅文档中的多服务应用和API 后端,以及我们关于调试 AI 搭建应用和前端与后端的指南。

CORS 检查清单

  • 已阅读完整的控制台错误以及失败请求的状态码
  • 已排除隐藏的 404 或 500 错误
  • 需要密钥的 API 由后端调用,而不是由浏览器调用
  • 服务器以确切的白名单源发送 Access-Control-Allow-Origin
  • OPTIONS 预检在身份验证之前,以允许的方法和请求头响应
  • 错误响应中也包含 CORS 响应头
  • 凭证:确切的源、Allow-Credentials: true,以及正确的 Cookie 设置
  • 预览、已发布域名和自定义域名的源均已覆盖,或使用了相对的 /api 路径
  • 没有使用 no-cors、公共代理或“反射任意源”之类的捷径

常见问题

CORS 错误是什么意思?

你的页面从一个源加载,却尝试读取另一个源的响应,而该服务器的响应中没有包含允许这样做的响应头。浏览器会阻止你的代码读取这个响应。解决方法在服务器端,或在请求的路由方式上,而不是在前端代码中。

我能在前端修复 CORS 错误吗?

无法真正地修复。像 mode: 'no-cors' 这样的设置只会让你的代码看不到响应内容。正确的做法是让服务器发送正确的 Access-Control-Allow-Origin 响应头,或者通过你自己的后端或同源代理来调用 API。

Access-Control-Allow-Origin: * 安全吗?

对于任何人都可以读取的公开数据是可以的,例如不需要登录的公共 API。但它不能与 Cookie 或其他凭证一起使用;对于私有 API,你应当列出允许访问的确切来源。

为什么我的请求在 Postman 或 curl 中能用,在浏览器中却不行?

CORS 只由浏览器强制执行。Postman 和 curl 等工具不会应用它,因此请求在这些工具中可以成功,但当页面的 JavaScript 发起同样的请求时,仍可能被拦截。

什么是预检请求(preflight)?

对于非简单请求,例如 JSON 格式的 POST 请求,或带有 Authorization 请求头的请求,浏览器会先发送一个 OPTIONS 请求,询问服务器允许哪些方法和请求头。如果这个 OPTIONS 请求失败或缺少正确的响应头,真正的请求就根本不会发出。

Mythex · Pricing · 用 AI 构建任何东西 · 网站和应用模板 · Mythex 对比 · AI 应用构建术语表 · 免费的 AI 应用构建工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 繁體中文(香港) 繁體中文(台灣)