Nginx中map模块实现CORS白名单的关键是显式设置default "",使未匹配的Origin映射为空字符串,从而add_header不发送Access-Control-Allow-Origin头,触发浏览器跨域拦截,达成默认安全。

Nginx 中用 map 模块实现 CORS 白名单,关键在于匹配失败时让 $cors_origin 为空字符串——这样 add_header Access-Control-Allow-Origin $cors_origin always 就不会输出该响应头,浏览器自然拒绝跨域请求,达到“默认安全”的效果。
map 指令如何确保匹配失败返回空值
map 的语法天然支持默认值控制。只要在规则开头显式写上 default "",所有未被正则匹配的 $http_origin 都会映射为空字符串:
map $http_origin $cors_origin {
default ""; # ← 所有不满足下面规则的 Origin 都落到这里,值为空
"~^https?://(app|admin)\.example\.com(:[0-9]+)?$" "$http_origin";
"~^https://staging\.example\.org$" "$http_origin";
}这个空值不是错误,而是主动设计的安全兜底:Nginx 不会发送 Access-Control-Allow-Origin 头,前端收到响应后因缺少该头而触发跨域拦截,整个过程无需额外逻辑干预。
白名单匹配失败后的实际行为
- 浏览器发起跨域请求(如
Origin: https://hacker.site) - Nginx 查
map表,没找到匹配项 →$cors_origin被设为"" -
add_header 'Access-Control-Allow-Origin' $cors_origin always实际等价于add_header 'Access-Control-Allow-Origin' "" always - Nginx 忽略空值响应头(不发送),响应中无
Access-Control-Allow-Origin - 浏览器判定跨域失败,控制台报错
CORS header ‘Access-Control-Allow-Origin’ missing
这就是“默认安全”:不匹配 = 不授权 = 不响应 = 被拦。
配合 credentials 时必须同步控制
如果启用了 Access-Control-Allow-Credentials: true,就绝对不能让 $cors_origin 为 *,也不能对不匹配源 fallback 到 *。正确做法是:
- 只在
$cors_origin非空时才加 credentials 头 - 或统一用
always,但确保default ""严格生效(Nginx 本身不会把空字符串当*发出去)
推荐写法:
add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Credentials' 'true' always;
因为 always 保证头存在,而 $cors_origin 为空时,Nginx 不发送 Access-Control-Allow-Origin,此时即使 Access-Control-Allow-Credentials 存在,浏览器也会因缺少 Allow-Origin 直接拒绝,不会产生安全隐患。
常见误配与规避
- ❌ 错误:漏写
default "",导致未匹配时继承上一条规则值或引发变量未定义 - ❌ 错误:正则末尾没加
$,比如~^https://app.example.com会误匹配https://app.example.com.evil.com - ❌ 错误:在
if块里用set $cors_origin ...替代map,性能低且易出错,无法用于add_header的always场景 - ✅ 正确:所有正则用
~*(忽略大小写)+^...$(全匹配)+ 显式default ""
不复杂但容易忽略


















