为什么自定义Header会触发CORS预检请求
当前端在AJAX请求中设置自定义Header(如X-Requested-With、X-Auth-Token、X-Client-Version)时,浏览器会自动触发CORS预检请求(Preflight Request)。这是因为自定义Header不属于CORS安全列表(Safe List),浏览器必须先询问服务器是否允许该请求。
预检请求使用HTTP OPTIONS方法,携带Access-Control-Request-Headers告诉服务器:实际请求会带上哪些自定义Header。服务器必须在响应中通过Access-Control-Allow-Headers明确批准这些Header,否则浏览器会直接拦截实际请求,控制台报CORS错误。
Nginx处理OPTIONS预检请求的完整配置
预检请求不携带业务数据,服务器只需返回正确的CORS响应头,不需要转发给后端。Nginx可以在location块中直接处理OPTIONS请求:
location /api/ {
# 处理OPTIONS预检请求
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 'X-Requested-With, X-Auth-Token, Content-Type, Authorization';
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Max-Age 86400;
add_header Content-Type 'text/plain; charset=utf-8';
return 204;
}
# 正常请求的CORS头
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Credentials 'true';
# 转发给后端
proxy_pass http://backend;
}
Access-Control-Max-Age 86400表示预检结果缓存86400秒(24小时),避免每个请求都触发预检,对性能有明显提升。
Access-Control-Allow-Headers配置自定义Header白名单
Access-Control-Allow-Headers的值是一个逗号分隔的Header名称列表,必须与实际请求中Access-Control-Request-Headers的值匹配。常见做法有两种:
方式一:精确列出允许的Header(推荐,安全性更好)
add_header Access-Control-Allow-Headers 'X-Requested-With, X-Auth-Token, X-Client-Version, Content-Type' always;
方式二:使用通配符(方便但安全性较低,且携带credentials时不生效)
add_header Access-Control-Allow-Headers '*' always;
注意:当请求携带Cookie(credentials模式)时,Access-Control-Allow-Headers: *无效,必须明确列出Header名称,且Access-Control-Allow-Origin也不能用*。
携带自定义Header时credentials模式的注意事项
当请求需要携带Cookie或HTTP认证信息时,前端需要设置withCredentials: true,此时Nginx配置有三个硬性要求:
Access-Control-Allow-Origin不能是*,必须明确指定origin(可用变量$http_origin动态获取,但需自行校验安全性)Access-Control-Allow-Credentials必须设为trueAccess-Control-Allow-Headers和Access-Control-Allow-Methods不能用通配符*
# credentials模式的正确配置示例
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 'X-Auth-Token, Content-Type';
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Max-Age 86400;
return 204;
}
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials 'true' always;
proxy_pass http://backend;
}
Nginx add_header的always参数与上下文覆盖问题
Nginx的add_header指令有一个容易踩的坑:当某个上下文(如if块或location块)中出现了add_header,它只会生效该上下文中定义的header,上层定义的同名header会被覆盖消失。
解决方法一:在每个add_header后面加always参数,确保无论HTTP状态码是什么都输出该header:
add_header Access-Control-Allow-Origin $http_origin always;
解决方法二:把CORS相关配置抽到独立的include文件中,每个location统一引入,避免重复书写导致覆盖。
常见错误与排查方法
错误1:自定义Header被浏览器拦截,控制台报"not allowed by Access-Control-Allow-Headers"
原因:Access-Control-Allow-Headers未包含该Header名称。解决:在允许列表中追加该Header名称。
错误2:OPTIONS请求返回405 Method Not Allowed
原因:Nginx未单独处理OPTIONS,请求被转发给后端而后端不支持OPTIONS。解决:在location中优先用if ($request_method = 'OPTIONS')拦截返回204。
错误3:credentials模式下仍然用通配符*,浏览器报"credentials mode only supports ..."
原因:credentials模式禁止通配符。解决:将*改为明确的origin和明确的Header列表。
错误4:add_header配置看起来正确,但实际响应中没有CORS头
原因:if块中的add_header覆盖了外层的add_header。解决:统一加always参数,或用include文件统一管理。
使用map指令实现多域名动态CORS白名单
当API需要允许多个指定域名跨域访问时,可以用Nginx的map指令实现动态origin校验,避免直接在配置中硬编码:
http {
map $http_origin $cors_origin {
default '';
'~^https?://localhost(:\d+)?$' $http_origin;
'~^https?://example\.com$' $http_origin;
'~^https?://app\.example\.com$' $http_origin;
}
server {
location /api/ {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $cors_origin;
add_header Access-Control-Allow-Headers 'X-Auth-Token, Content-Type';
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Max-Age 86400;
return 204;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials 'true' always;
proxy_pass http://backend;
}
}
}
这种方法既支持多域名,又避免了将Access-Control-Allow-Origin设为*的安全风险。
相关文章推荐:
Nginx CORS跨域配置详解:从原理到实战的完整指南
Nginx CORS预检请求OPTIONS处理配置:解决跨域预检失败的完整实战
Nginx CORS与Cookie携带配置教程:解决跨域请求Cookie丢失的完整实战
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论