map 指令比 if + set 更适合 CORS 白名单,因其在 server 初始化阶段编译、性能高、无副作用、支持正则匹配、默认值可控且变量可复用;基础配置需在 http 块中用 map 映射 $http_origin 为 $cors_origin,并配合 always 标志添加响应头。

直接用 add_header Access-Control-Allow-Origin 写死域名或设为 * 很容易踩坑——前者维护成本高,后者不支持带凭证的请求。Nginx 的 map 指令能根据请求头中的 Origin 动态匹配合法域名,实现安全、灵活、可维护的 CORS 白名单控制。
为什么 map 比 if + set 更适合做白名单
map 是 Nginx 的变量映射模块,在 server 初始化阶段就完成编译,性能高、无副作用;而 if 在每次请求时运行,嵌套逻辑易出错,且在 location 块中使用受限(比如不能直接 set header)。map 天然适合“输入 Origin → 输出是否允许 → 输出对应 Origin 值”这种单向映射关系。
- 支持正则匹配(如
~*^https?://(foo|bar)\.example\.com$) - 默认值可控(未匹配时返回空或预设值,避免意外暴露)
- 变量可复用于多个指令(
add_header、proxy_set_header等)
基础配置:定义白名单并注入响应头
在 http 块中声明 map,将原始 $http_origin 映射为可信的 $cors_origin:
map $http_origin $cors_origin {
default "";
"~*^https?://(localhost|127\.0\.0\.1):[0-9]+$" "$http_origin";
"~*^https?://(app\.example\.com|dashboard\.example\.org)$" "$http_origin";
"~*^https?://.*\.staging\.example\.com$" "$http_origin";
}
然后在 server 或 location 中启用 CORS 响应头:
add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
注意:必须加 always 标志,否则 301/404 等非 2xx 响应不会携带 CORS 头,导致前端预检失败后看不到错误原因。
处理预检请求(OPTIONS)
浏览器对复杂请求会先发 OPTIONS 预检。Nginx 需拦截并快速返回 204,不转发给后端:
location = / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Max-Age' 86400 always;
add_header 'Content-Length' 0 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
return 204;
}
}
更稳妥的做法是统一用 try_files 或独立 location ~ ^/api/ 匹配接口路径,并在其中处理 OPTIONS。
进阶技巧:按环境隔离白名单 & 支持子域通配
可通过 Nginx 变量区分部署环境,让开发、测试、生产共用一份配置:
map $env $cors_origin {
"dev" "~*^https?://(localhost|127\.0\.0\.1):[0-9]+$";
"test" "~*^https?://.*\.test\.example\.com$";
"prod" "~*^https?://(app\.example\.com|shop\.example\.com)$";
}
# 实际匹配仍需用嵌套 map 或 Lua,但可借助 env 控制主开关
若需支持子域通配(如所有 *.example.com),纯正则较难安全实现,推荐结合简单脚本生成白名单,或用 lua-resty-core 做运行时校验。不过对大多数场景,明确列出主域 + 常用子域(admin.example.com、api.example.com)已足够安全。










