核心是“先清后立”:用proxy_hide_header拦截后端CORS头,再由Nginx统一注入合规头,避免浏览器因重复头或Credentials-Origin冲突而拒绝响应。
核心是“先清后立”:用
把后端返回的 CORS 头全部拦下,再由 Nginx 统一、干净地注入,避免浏览器收到重复头而直接拒绝响应。
为什么必须隐藏后端 CORS 头
当 Java、Go 或 Node.js 后端已自行设置了
等头,而 Nginx 又在 location 里用
再加一遍,就会导致响应中出现两个同名头。浏览器(尤其是 Chrome)对这类重复头处理严格:
预检请求(OPTIONS)返回非 200(如被静默截断),报错 “Response to preflight request doesn’t pass access control check”
实际请求即使状态码是 200,也会被拦截,控制台提示 “has been blocked by CORS policy”
若
是通配符
,但同时设了
,浏览器会直接拒收——这不是配置错误,而是规范强制要求
关键配置步骤(location 块内顺序不能错)
所有操作必须写在
所在的
块中,且按以下顺序执行:
先屏蔽后端返回的冲突头(每行一条,不可合并):
proxy_hide_header Access-Control-Allow-Origin;proxy_hide_header Access-Control-Allow-Methods;proxy_hide_header Access-Control-Allow-Headers;proxy_hide_header Access-Control-Allow-Credentials;proxy_hide_header Access-Control-Expose-Headers;
再统一注入合规头(务必加
,否则对 3xx/4xx 响应不生效):
add_header 'Access-Control-Allow-Origin' 'https://your-frontend.com' always;add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;add_header 'Access-Control-Allow-Credentials' 'true' always;add_header 'Access-Control-Expose-Headers' 'X-Request-ID, Content-Length' always;
单独处理 OPTIONS 预检请求(不转发给后端,直接响应):
if ($request_method = 'OPTIONS') {add_header 'Access-Control-Max-Age' 1728000;add_header 'Content-Type' 'text/plain; charset=utf-8';add_header 'Content-Length' 0;return 204;}
容易忽略的两个安全细节
这两点不满足,整个 CORS 配置形同虚设:
Credentials 和 Origin 必须匹配
:如果需要携带 Cookie(即
),则
不能是
,必须写具体协议+域名,例如
确保没有其他 location 或 server 块重复注入相同头
:检查全局配置,避免多个
叠加;安全头如
、
也建议用
屏蔽后端输出,统一由 Nginx 控制
验证是否生效
用 curl 检查真实响应头,确认无残留、无重复:
正确结果应满足:
只出现一次
,值为你在 Nginx 中指定的那个
没有
、
等头来自后端(可通过对比关掉 Nginx 代理直连后端来比对)
OPTIONS 请求返回 204,且含
和
proxy_hide_headerAccess-Control-Allow-Originadd_headerAccess-Control-Allow-Origin*Access-Control-Allow-Credentials: trueproxy_passlocationalwaysAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Origin*https://app.example.comadd_headerStrict-Transport-SecurityX-Frame-Optionsproxy_hide_headercurl -I https://your-domain/api/testAccess-Control-Allow-OriginAccess-Control-Allow-OriginAccess-Control-Allow-CredentialsAccess-Control-Max-AgeContent-Length: 0