0

Nginx CORS多域名动态匹配:if与map两种方案深度对比与实战选择

2026.05.23 | youres | 19次围观

在前后端分离项目中,跨域请求是绕不开的问题。Nginx作为反向代理服务器,处理CORS配置是基本功。但当业务需要支持多个可信域名时,怎么配就成了一个值得探讨的问题。

常见做法有两种:用if指令做动态判断,或者用map指令做静态映射。表面上看都能实现,但在性能、配置可维护性上差距挺大。今天就把两种方案掰开了讲。

为什么多域名CORS配置要特殊对待

标准的CORS响应头需要返回Access-Control-Allow-Origin,它的值可以是具体的域名(必须是完整URI,不能带路径),也可以是通配符*

但通配符有个限制:当浏览器请求携带认证信息(如Cookie)时,服务端不能返回*,必须指定具体域名。这种场景在SSO登录、iframe子系统里很常见,所以多域名白名单几乎是每个中后台项目的标配。

更麻烦的是,不同环境域名不同——开发、测试、预发、生产,各有各的地址。而且业务扩张时还会新增域名,这时候配置维护就成了问题。

方案一:if指令动态判断

这是最直观的方式。Nginx配置里写一串if判断,每个域名对应一种处理逻辑:

server {
    listen 80;
    server_name api.example.com;

    # 用if判断域名
    if ($http_origin = 'https://www.example.com') {
        set $cors_origin 'https://www.example.com';
    }
    if ($http_origin = 'https://admin.example.com') {
        set $cors_origin 'https://admin.example.com';
    }

    add_header 'Access-Control-Allow-Origin' $cors_origin always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
}

这种方式优点是逻辑直观——谁请求就给谁批。改配置也简单,加一个if就行。但问题也在这里:每加一个域名就要改配置,而且if是rewrite阶段的指令,每次请求都要走一遍判断逻辑。域名少还好,一旦上了七八个,维护成本就上来了。

方案二:map指令静态映射

map是ngx_http_map_module提供的指令,本质是一个键值查找表。它在Nginx启动时加载到内存,请求进来直接查表,O(1)复杂度。

首先在http块(或included文件)里定义map表:

# /etc/nginx/conf.d/cors-map.conf
map $http_origin $cors_origin {
    default '';
    ~^https://www\.example\.com$ https://www.example.com;
    ~^https://admin\.example\.com$ https://admin.example.com;
    ~^https://m\.example\.com$ https://m.example.com;
}

然后在server块里引用:

server {
    listen 80;
    server_name api.example.com;

    if ($cors_origin = '') {
        return 204;
    }

    add_header 'Access-Control-Allow-Origin' $cors_origin always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
}

map里的正则匹配可以写得比较灵活,支持泛匹配(用正则~),也支持精确匹配。域名变更时只需要改map文件,不用动server块配置。

两种方案核心对比

先看性能。if方案每个请求都会执行一遍条件判断链,请求量大了之后对CPU的消耗不可忽视。map方案查表操作在内存里完成,复杂度是固定的,Nginx官方文档也明确建议用map处理这类场景。

再看维护性。if方案写死在server块里,域名多了之后配置臃肿,而且没有统一管理入口。map方案把域名列表集中在一处,新加域名、改域名都很清晰,配合Git管理也很方便。

然后看安全性。if方案如果忘记处理默认情况,可能把$http_origin直接回写,意外放行未知域名。map方案有default兜底,未知域名映射到空值,这时候直接return 204,更安全。

生产环境推荐配置

综合上面几点,生产环境我推荐用map方案。下面是一套可以直接上线的配置:

# /etc/nginx/conf.d/cors-map.conf(nginx.conf里include)
map $http_origin $cors_allow_origin {
    default 0;
    ~^https://(www|admin|m)\.example\.com$ $http_origin;
    ~^https://[a-z0-9-]+\.example\.com$ $http_origin;
}

这里用了两个技巧:一是把默认行为设为0(非真),在server块里判断;二是用正则泛匹配覆盖同一主域名下的多个子域名,减少重复条目。

对应的server配置:

location / {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $cors_allow_origin always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 86400 always;
        return 204;
    }
    proxy_pass http://backend;
}

Access-Control-Max-Age 86400设置预检结果缓存一天,客户端在有效期内重复跨域请求不需要再发OPTIONS预检,能有效降低请求延迟。

常见的坑

if里set变量不生效

Nginx的if和set配合有时会出现变量拿不到的情况,尤其是if在location块里时。这是因为if执行的阶段和变量求值阶段可能有冲突。解法是把变量定义放在server块级别,或者改用map彻底绕过这个问题。

origin和referer混淆

CORS判断用的是Origin请求头,不是Referer。Referer来源更复杂,可能带路径、带参数,不适合做跨域判断。

正则匹配顺序

map的正则按从上到下匹配,第一个命中的就返回了。写泛匹配时要注意,宽松的正则如果写在前面,会把后面更具体的规则吃掉,导致后面的域名永远匹配不到。

总结

两种方案都能实现多域名CORS动态配置,但综合考虑性能、可维护性和安全性,生产环境更推荐map方案。如果项目域名少、变化也不频繁,if方案足够用,改起来快。如果要支撑一个持续迭代的系统,map方案虽然多了个配置文件,但长期收益明显。

实际选型时,建议按这个标准判断:域名少于5个且基本不变,用if;超过5个或需要频繁调整,用map。


相关阅读:

版权声明

本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论
883文章数 0评论数
作者其它文章