Skip to content
api2026-07-254 分钟阅读

你在浏览器里调一个 API,明明 URL 没问题、参数也对,控制台却甩出一段红色报错:

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass
access control check: No 'Access-Control-Allow-Origin' header is present.

更让人困惑的是,打开 Network 面板一看,请求列表里多了一个你从未主动发起的 OPTIONS 请求。这个 OPTIONS 请求就是所谓的 CORS 预检请求(Preflight Request)

什么情况下会触发预检请求?

浏览器的同源策略规定,网页只能请求同协议、同域名、同端口的资源。CORS(Cross-Origin Resource Sharing)是服务端"授权"跨域访问的机制。而预检请求是浏览器在发送"可能有副作用"的请求之前,先问一嘴服务端:"我能不能这样请求你?"

简单请求不会触发预检,必须同时满足:

  • 方法是 GETHEADPOST
  • 没有自定义请求头(只有 AcceptAccept-LanguageContent-LanguageContent-Type 等安全头)
  • Content-Type 只能是 text/plainmultipart/form-dataapplication/x-www-form-urlencoded

以下任何一种情况都会触发预检

  • 使用 PUTDELETEPATCH 等非简单方法
  • 添加自定义请求头,如 Authorization: Bearer xxx
  • Content-Type 设为 application/json(这是前端最常踩的坑)
// 这个请求会触发预检,因为 Content-Type 是 application/json
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'test' })
});

// 这个也会,因为用了 DELETE 方法
fetch('https://api.example.com/users/1', {
  method: 'DELETE'
});

// 这个也会,因为有自定义头
fetch('https://api.example.com/data', {
  headers: { 'X-API-Key': 'abc123' }
});

预检请求到底发了什么?怎么看响应?

当触发预检时,浏览器会自动发送一个 OPTIONS 请求,携带以下关键头:

OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
  • Origin:发起请求的源(你的前端地址)
  • Access-Control-Request-Method:实际请求要用什么方法
  • Access-Control-Request-Headers:实际请求要带什么自定义头

服务端需要在响应中"批准"这些条件:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

各响应头的含义

| 响应头 | 作用 | |--------|------| | Access-Control-Allow-Origin | 允许哪些源访问。* 表示所有源(但不能与 credentials 共用) | | Access-Control-Allow-Methods | 允许的 HTTP 方法列表 | | Access-Control-Allow-Headers | 允许的自定义请求头 | | Access-Control-Max-Age | 预检结果缓存时间(秒),避免每次请求都发 OPTIONS | | Access-Control-Allow-Credentials | 是否允许携带 Cookie(设为 true 时 Origin 不能用 *) |

如果服务端返回的响应头不满足浏览器的检查条件,实际请求就不会发出,你看到的就是那个 CORS 报错。

常见 CORS 报错及解决方法

报错 1:No 'Access-Control-Allow-Origin' header

服务端完全没有配置 CORS。需要在服务端添加响应头:

// Express 示例
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
  res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');
  next();
});

// 或者直接用 cors 中间件
const cors = require('cors');
app.use(cors({ origin: 'http://localhost:3000' }));

报错 2:Method not allowed in preflight

服务端 CORS 配置中没有包含你使用的 HTTP 方法。比如你发 PUT,但 Access-Control-Allow-Methods 里只有 GET, POST

报错 3:Request header field X is not allowed

自定义请求头没有被 Access-Control-Allow-Headers 批准。检查你发送的所有非标准头是否都在服务端的允许列表中。

报错 4:credentials mode is 'include' but Allow-Origin is '*'

fetch 设置了 credentials: 'include'(携带 Cookie),服务端不能用 Access-Control-Allow-Origin: *,必须指定具体的 Origin,并且加上 Access-Control-Allow-Credentials: true

为什么本地 API 测试工具不受 CORS 限制?

这里有一个关键认知:CORS 是浏览器的安全机制,不是 HTTP 协议的限制

  • 浏览器中的 fetch / XMLHttpRequest 受同源策略约束
  • 命令行工具(curl、httpie)、桌面应用、后端服务发起的请求完全不受 CORS 限制

这就是为什么用 Postman 或 curl 调接口一切正常,换到浏览器里就报 CORS 错误。

对于前端开发者来说,在开发阶段频繁遇到 CORS 问题时,一个实用的做法是使用不经过浏览器的 API 测试工具。API 测试工具 直接在本地发起 HTTP 请求,不经过浏览器沙箱,因此完全不会触发预检请求或 CORS 检查。你可以专注验证接口本身的逻辑是否正确,而不用被跨域配置干扰。

当然,生产环境中你仍然需要正确配置 CORS——因为最终用户是通过浏览器访问的。但在开发和调试阶段,把"接口逻辑问题"和"CORS 配置问题"分开排查,效率会高很多。

开发阶段的临时解决方案

如果你没有权限修改服务端 CORS 配置,开发阶段可以:

  1. 前端代理:在 vite.config.jswebpack devServer 中配置 proxy,让开发服务器代为转发请求
  2. 浏览器插件:临时禁用 CORS 检查(仅限开发环境,切勿用于日常浏览)
  3. 本地 API 工具:用 API 测试工具 直接验证接口,绕开浏览器限制
// Vite 代理配置示例
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
}

理解 CORS 预检机制的核心就一句话:浏览器在发送"非简单请求"之前,会先发一个 OPTIONS 请求征求服务端同意,服务端通过 Access-Control-* 系列响应头来表态。搞清楚触发条件和响应头的对应关系,绝大多数 CORS 问题都能在几分钟内定位并解决。

下次遇到 CORS 报错,先打开 Network 面板找到那个 OPTIONS 请求,对比 Access-Control-Request-*Access-Control-Allow-* 的值,答案通常就在那里。如果你只是想先确认接口本身能不能通,直接用 API 测试工具 发请求即可——本地处理,无 CORS 困扰,也不用担心请求数据经过第三方服务器。


ad