CORS 跨域问题:自定义请求头导致的预检请求失败
在前后端分离的项目中,前端应用(运行在 `http://localhost:8174`)向后端API(`https://modal.sum.ait/api/`)发起请求时,浏览器控制台出现以下CORS错误:
一、问题描述
在前后端分离的项目中,前端应用(运行在 http://localhost:8174)向后端API(https://modal.sum.ait/api/)发起请求时,浏览器控制台出现以下CORS错误:
Access to XMLHttpRequest at 'https://modal.sum.ait/api/multiCreate/text2image?prompt=asf&style=&resolution='
from origin 'http://localhost:8174' has been blocked by CORS policy:
Request header field x-request-id is not allowed by Access-Control-Allow-Headers in preflight response.
关键错误信息:Request header field x-request-id is not allowed by Access-Control-Allow-Headers in preflight response
二、问题原因分析
2.1 CORS 机制概述
CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种基于HTTP头的机制,它允许服务器声明哪些源站有权限访问服务器的资源。当浏览器检测到跨域请求时,会自动触发CORS机制。
2.2 预检请求(Preflight Request)
对于某些跨域请求,浏览器会先发送一个 OPTIONS 预检请求,以确认服务器是否允许该跨域请求。触发预检请求的条件包括:
- 使用了非简单请求方法:如
PUT、DELETE等(GET、POST、HEAD为简单方法) - 使用了非简单请求头:除了浏览器自动设置的头部和以下简单头部外的其他头部
AcceptAccept-LanguageContent-LanguageContent-Type(仅限于application/x-www-form-urlencoded、multipait/form-data、text/plain)
2.3 本案例的问题根源
通过错误信息分析,问题的核心在于:
- 前端请求包含自定义头部:前端代码在发送请求时添加了
x-request-id这个自定义请求头 - 服务器CORS配置不匹配:Nginx 的 CORS 配置中
Access-Control-Allow-Headers字段没有包含x-request-id - 预检请求被拒绝:浏览器发送
OPTIONS预检请求时,服务器返回的允许头列表中没有x-request-id,导致预检失败,真正的请求无法发送
三、详细排查过程
3.1 网络架构分析
根据提供的配置,整体架构如下:
前端(localhost:8174)
↓
反向代理服务器(45.248.133.102) - modal.sum.ait:443
↓
后端应用服务器(45.248.133.103:8080)
↓
后端服务(localhost:8082)
3.2 请求流程追踪
-
前端发起请求:
// 请求包含自定义头 x-request-id axios.get('/api/multiCreate/text2image', { headers: { 'x-request-id': 'some-uuid' } }) -
浏览器发送 OPTIONS 预检请求:
OPTIONS /api/multiCreate/text2image HTTP/1.1 Host: modal.sum.ait Origin: http://localhost:8174 Access-Control-Request-Method: GET Access-Control-Request-Headers: x-request-id -
Nginx 返回 CORS 响应头(45.248.133.103 服务器配置):
Access-Control-Allow-Origin: http://localhost:8174 Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With -
问题发现:
- 浏览器请求的头部:
x-request-id - 服务器允许的头部:
Content-Type, Authorization, X-Requested-With x-request-id不在允许列表中 → 预检失败 → 真实请求被浏览器阻止
- 浏览器请求的头部:
3.3 配置对比分析
当前配置(45.248.133.103 服务器 /api/ location):
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
问题:缺少 x-request-id 头部的允许声明
四、解决方案
4.1 修改 Nginx 配置
在 45.248.133.103 服务器的 Nginx 配置文件中,找到多模态后端的 location /api/ 块,修改 Access-Control-Allow-Headers 配置。
方案一:添加 x-request-id(最小修改)
location /api/ {
set $cors_origin "";
if ($http_origin ~* "^http://localhost:[0-9]+$|^https://modal\.sum\.ait$|^https?://124\.71\.115\.96(:[0-9]+)?$|^https?://124\.71\.115\.99(:[0-9]+)?$") {
set $cors_origin $http_origin;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
# 添加 x-request-id 到允许的请求头列表
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
# OPTIONS 预检请求也需要添加
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id';
add_header 'Access-Control-Max-Age' '3600';
add_header 'Content-Length' '0';
add_header 'Content-Type' 'text/plain';
return 204;
}
proxy_pass http://localhost:8082/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
方案二:添加更多常见请求头(推荐)
考虑到未来可能添加其他自定义头部,建议添加更全面的头部支持:
location /api/ {
set $cors_origin "";
if ($http_origin ~* "^http://localhost:[0-9]+$|^https://modal\.sum\.ait$|^https?://124\.71\.115\.96(:[0-9]+)?$|^https?://124\.71\.115\.99(:[0-9]+)?$") {
set $cors_origin $http_origin;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH' always;
# 添加更全面的请求头支持
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id, Accept, Origin, Cache-Control, X-File-Name' always;
# 允许浏览器读取的响应头
add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range, X-Request-Id' always;
# 允许携带认证信息(如需要)
# add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, x-request-id, Accept, Origin, Cache-Control, X-File-Name';
add_header 'Access-Control-Max-Age' '86400'; # 预检请求缓存24小时
add_header 'Content-Length' '0';
add_header 'Content-Type' 'text/plain charset=UTF-8';
return 204;
}
proxy_pass http://localhost:8082/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
4.2 配置变更步骤
-
备份当前配置:
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup.$(date +%Y%m%d_%H%M%S) -
编辑配置文件:
sudo vim /etc/nginx/nginx.conf # 或使用其他编辑器 sudo nano /etc/nginx/nginx.conf -
验证配置语法:
sudo nginx -t期望输出:
nginx: configuration file /etc/nginx/nginx.conf test is successful -
重载 Nginx 配置:
sudo systemctl reload nginx # 或 sudo nginx -s reload -
验证服务状态:
sudo systemctl status nginx
4.3 验证修复效果
修改配置并重载 Nginx 后,可以通过以下方式验证:
-
浏览器开发者工具:
- 打开 Network 标签
- 查找 OPTIONS 预检请求
- 检查响应头中的
Access-Control-Allow-Headers是否包含x-request-id
-
curl 命令测试:
curl -X OPTIONS https://modal.sum.ait/api/multiCreate/text2image \ -H "Origin: http://localhost:8174" \ -H "Access-Control-Request-Method: GET" \ -H "Access-Control-Request-Headers: x-request-id" \ -v检查响应头中是否包含:
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, x-request-id, ... -
前端应用测试:
- 清除浏览器缓存
- 重新发起请求
- 确认请求成功,无 CORS 错误
五、最佳实践建议
5.1 CORS 配置原则
- 最小权限原则:只允许必要的源、方法和头部
- 显式声明:明确列出允许的头部,避免使用
*通配符(除非开发环境) - 安全考虑:
- 生产环境不要使用
Access-Control-Allow-Origin: * - 如需凭证(Cookie),必须设置具体的 Origin,不能使用
* - 合理设置
Access-Control-Max-Age减少预检请求
- 生产环境不要使用
5.2 调试技巧
- 查看完整的请求和响应头:使用浏览器 DevTools 的 Network 面板
- 区分简单请求和预检请求:
- 简单请求直接发送,只检查响应头
- 复杂请求先发 OPTIONS,再发实际请求
- 检查 Nginx 日志:
sudo tail -f /var/log/nginx/error.log sudo tail -f /var/log/nginx/access.log
5.3 前端配置建议
如果可以修改前端代码,建议统一管理自定义请求头:
// axios 配置示例
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';
const apiClient = axios.create({
baseURL: 'https://modal.sum.ait/api',
timeout: 30000,
});
// 请求拦截器:统一添加自定义头部
apiClient.interceptors.request.use(config => {
config.headers['x-request-id'] = uuidv4();
return config;
});
export default apiClient;
5.4 多层代理的注意事项
本案例中存在两层 Nginx 代理:
- 45.248.133.102(外层代理)→ 45.248.133.103(内层代理 + 应用服务器)
关键点:
- CORS 配置应该在最终响应的服务器上设置(本例中是 45.248.133.103)
- 外层代理不应再添加 CORS 头,避免重复头部导致的问题
- 使用
proxy_pass时,确保Host头正确传递
六、总结
6.1 问题核心
CORS 错误的本质是浏览器的安全机制阻止了不符合服务器 CORS 策略的跨域请求。本案例中,前端请求包含的 x-request-id 自定义头部未在服务器的 Access-Control-Allow-Headers 白名单中,导致预检请求失败。
6.2 解决关键
在 Nginx 的 CORS 配置中添加 x-request-id 到 Access-Control-Allow-Headers 列表,同时确保 OPTIONS 预检请求的响应头配置一致。
6.3 举一反三
遇到类似 CORS 错误时,排查步骤:
- 查看错误信息:确定是哪个头部、方法或源被拒绝
- 检查预检请求:在 Network 中找到 OPTIONS 请求,对比请求头和响应头
- 定位配置位置:找到处理该路径的服务器配置
- 修改并验证:添加缺失的配置项,重载服务,测试验证
更多推荐


所有评论(0)