你在浏览器里调一个 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)是服务端"授权"跨域访问的机制。而预检请求是浏览器在发送"可能有副作用"的请求之前,先问一嘴服务端:"我能不能这样请求你?"
简单请求不会触发预检,必须同时满足:
- 方法是
GET、HEAD或POST - 没有自定义请求头(只有
Accept、Accept-Language、Content-Language、Content-Type等安全头) Content-Type只能是text/plain、multipart/form-data、application/x-www-form-urlencoded
以下任何一种情况都会触发预检:
- 使用
PUT、DELETE、PATCH等非简单方法 - 添加自定义请求头,如
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 配置,开发阶段可以:
- 前端代理:在
vite.config.js或webpack devServer中配置 proxy,让开发服务器代为转发请求 - 浏览器插件:临时禁用 CORS 检查(仅限开发环境,切勿用于日常浏览)
- 本地 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 困扰,也不用担心请求数据经过第三方服务器。