CORS 预检请求
理解 HTTP 跨域时的 Options 预检请求机制
问题
HTTP 跨域请求时,浏览器为什么会发送 Options 预检请求?什么情况下会触发?如何处理?
解答
什么是预检请求
预检请求(Preflight Request)是浏览器在发送”非简单请求”前,自动发送的一个 OPTIONS 请求,用于询问服务器是否允许该跨域请求。
简单请求 vs 非简单请求
简单请求需同时满足:
- 方法为 GET、HEAD、POST 之一
- 只包含安全的请求头:Accept、Accept-Language、Content-Language、Content-Type
- Content-Type 仅限:text/plain、multipart/form-data、application/x-www-form-urlencoded
非简单请求(会触发预检):
// 触发预检:使用了 PUT 方法
fetch('https://api.example.com/data', {
method: 'PUT',
body: JSON.stringify({ name: 'test' })
});
// 触发预检:自定义请求头
fetch('https://api.example.com/data', {
headers: {
'X-Custom-Header': 'value'
}
});
// 触发预检:Content-Type 为 application/json
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'test' })
});
预检请求流程
浏览器 服务器
| |
| 1. OPTIONS /api/data |
| Origin: https://example.com |
| Access-Control-Request-Method: PUT|
| Access-Control-Request-Headers: X-Custom-Header
| ---------------------------------> |
| |
| 2. 200 OK |
| Access-Control-Allow-Origin: * |
| Access-Control-Allow-Methods: PUT |
| Access-Control-Allow-Headers: X-Custom-Header
| Access-Control-Max-Age: 86400 |
| <--------------------------------- |
| |
| 3. PUT /api/data (实际请求) |
| ---------------------------------> |
服务器端配置
Node.js + Express:
const express = require('express');
const app = express();
// CORS 中间件
app.use((req, res, next) => {
// 允许的源
res.header('Access-Control-Allow-Origin', 'https://example.com');
// 允许的方法
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// 允许的请求头
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Custom-Header');
// 预检请求缓存时间(秒)
res.header('Access-Control-Max-Age', '86400');
// 允许携带凭证
res.header('Access-Control-Allow-Credentials', 'true');
// 直接响应预检请求
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
app.put('/api/data', (req, res) => {
res.json({ success: true });
});
app.listen(3000);
Nginx 配置:
location /api/ {
# 预检请求处理
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' '86400';
return 204;
}
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
proxy_pass http://backend;
}
预检请求的响应头
| 响应头 | 说明 |
|---|---|
| Access-Control-Allow-Origin | 允许访问的源 |
| Access-Control-Allow-Methods | 允许的 HTTP 方法 |
| Access-Control-Allow-Headers | 允许的请求头 |
| Access-Control-Max-Age | 预检结果缓存时间 |
| Access-Control-Allow-Credentials | 是否允许携带 Cookie |
关键点
- 预检请求是浏览器自动发送的 OPTIONS 请求,用于检查服务器是否允许跨域
- 简单请求(GET/HEAD/POST + 安全头 + 限定 Content-Type)不触发预检
- 使用自定义头、PUT/DELETE 方法、application/json 等会触发预检
- Access-Control-Max-Age 可缓存预检结果,减少 OPTIONS 请求次数
- 服务器需正确响应 OPTIONS 请求并返回相应的 CORS 头
目录