nginx 必须在 options 预检响应中显式声明 access-control-allow-headers 包含 x-token,且需加 always 参数、大小写严格匹配、避免与后端重复,同时配合动态 origin 白名单和独立 location 处理预检。

当浏览器发起带 X-Token 这类自定义请求头的跨域请求时,会触发 OPTIONS 预检;若 Nginx 未在预检响应中明确允许该头部,浏览器就会拒绝后续真实请求。关键不是“放行 X-Token”,而是让预检响应里正确声明它。
确保 Access-Control-Allow-Headers 包含 X-Token
预检响应必须显式列出所有前端将发送的非标准头,X-Token 就是典型之一。Nginx 不能靠后端返回,必须自己写死或动态注入:
- 在处理 OPTIONS 的 location 块中,用
add_header Access-Control-Allow-Headers明确包含X-Token,例如:add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,Content-Type,Authorization,X-Token' always; - 不要只写
*—— 浏览器不认这个值用于Allow-Headers,它只接受逗号分隔的具体字段名 - 大小写敏感:前端发的是
X-Token,响应头里也必须写成X-Token,不能写成x-token或Xtoken
避免与后端重复声明导致冲突
如果后端(如 Spring Boot、Express)也配置了 CORS 并返回了 Access-Control-Allow-Headers,而 Nginx 又叠加一次,可能出现两个同名响应头,浏览器会直接报错拒绝请求:
- 关闭后端的 CORS 自动配置,或至少禁用对 OPTIONS 请求的响应
- 确认最终响应中
Access-Control-Allow-Headers只出现一次,且内容完整包含X-Token - 用
curl -I -X OPTIONS -H "Origin: https://a.com" -H "Access-Control-Request-Headers: X-Token" https://api.example.com/xxx直接验证响应头
配合 Origin 动态校验,兼顾安全与灵活性
若前端携带 X-Token 同时又启用了 credentials(如 cookie 或 auth),Access-Control-Allow-Origin 就不能用 *,否则浏览器拒绝:
- 用
map指令建立白名单,例如:map $http_origin $cors_origin {<br> ~^https?://(localhost|admin\.example\.com|app\.mycompany\.io)(:[0-9]+)?$ $http_origin;<br> default "";<br>} - 在 OPTIONS 块中写:
add_header 'Access-Control-Allow-Origin' $cors_origin always;
匹配失败时变量为空,不设该头,浏览器自然拦截——比硬写*更安全 - 同时加:
add_header 'Access-Control-Allow-Credentials' 'true' always;
检查预检是否真被 Nginx 拦截,而非被静默丢弃
很多问题其实不是头没写对,而是 OPTIONS 根本没走到你写的配置块里:
- 确认
if ($request_method = 'OPTIONS') { ... }放在正确的location内,且该 location 能精确匹配到接口路径(比如/api/) - 避免把
if和proxy_pass写在同一 location 里——Nginx 官方不推荐,旧版本可能丢失 header 或出 502 - 更稳妥的做法是拆成两个 location:
一个专配location ^~ /api/ { if (OPTIONS) { return 204; } }
另一个配location ^~ /api/ { proxy_pass ... } - 查 access_log,看是否有
OPTIONS /api/xxx 204日志行,没有就说明没命中配置











