nginx跨域安全需白名单+精准匹配+预检拦截:用map定义可信origin,credentials请求严格绑定非通配origin,options单独返回204并设vary: origin,禁用不必要方法及敏感头暴露。

在 Nginx 的 server 配置中实现严格的跨域安全校验策略,核心是放弃通配符、拒绝未经验证的来源、显式控制凭证与预检流程,并确保协议/域名/端口三者完全匹配。不能靠“加几个 header 就完事”,而要从源头过滤、运行时校验、响应精准声明三方面协同。
用 map 构建可信源白名单
避免硬编码或正则泛匹配,把允许的前端地址集中定义在 http 块顶部,再在 server 中引用:
- 在
http块开头添加:
map $http_origin $cors_origin {
default "";
"~^https://app\.example\.com(:[0-9]+)?$" $http_origin;
"~^https://admin\.example\.com$" $http_origin;
"~^https://localhost:3000$" $http_origin;
}
- 这样只有明确列出的 HTTPS(或本地开发 HTTP)源才被识别;不匹配的请求,
$cors_origin为空,后续不会注入任何 CORS 头,浏览器自然拦截 - 注意:正则中
https?不推荐用于生产 HTTPS 环境,必须写死https://,否则 http 源混入会破坏安全边界
带凭证请求必须绑定确切源
当前端使用 credentials: 'include'(如需传 Cookie 或 Authorization),Access-Control-Allow-Origin 绝不能为 *,且必须与 $cors_origin 严格一致:
- 在
location块中写:
if ($cors_origin) {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
}
-
always参数确保即使后端返回 4xx/5xx 错误,CORS 头仍存在,避免前端因无头而无法调试 - 若不需要传凭证(如公开接口),可省略
Allow-Credentials,但依然建议用白名单而非*,防止未来扩展引入风险
单独拦截并快速响应 OPTIONS 预检
浏览器对非简单请求会先发 OPTIONS,Nginx 必须自己处理,不能转发给后端:
- 在
location内添加:
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
add_header 'Access-Control-Max-Age' '86400' always;
add_header 'Vary' 'Origin' always;
return 204;
}
- 返回 204(No Content)而非 200,语义更准确,也避免某些客户端误解析响应体
-
Vary: Origin告诉缓存层(包括 CDN)该响应依赖于 Origin 头,防止不同源请求拿到错误的缓存结果 - 不要在 OPTIONS 响应中加
Access-Control-Allow-Credentials—— 浏览器不读它,反而可能引发兼容问题
补充安全加固细节
严格策略不止于基础头,还需收敛暴露面和防御探测:
- 显式声明可暴露的响应头:
add_header 'Access-Control-Expose-Headers' 'X-Request-ID, X-RateLimit-Limit' always;,避免泄露敏感头如Set-Cookie - 禁用不必要方法:如果 API 只用 GET/POST,就把
PUT、DELETE从Allow-Methods中移除 - 结合
geo或map拦截已知恶意 UA 或扫描路径(如.env、/phpmyadmin),防止攻击者利用跨域配置探测后端结构











