Kubernetes 双层 Nginx 容器环境下的 CORS 问题及解决方案(极端情况)

在实际开发和运维中,CORS(跨源资源共享)是前端调用后端接口时最常遇到的安全策略问题之一。大多数情况下,只需在后端服务器上正确配置 Access-Control-Allow-Origin 即可。

但本人遇到了 双层 Nginx 代理极端 情况(K8s 上 Nginx 容器代理后,再由 Nginx 代理出来),常规修改方法往往失效,以下是原理分析以及解决方案。


一、背景

我所在的环境中,前端和后端均部署在 Kubernetes 上:

  • 前端 Nginx :Nginx 容器,把前端文件代理(第一次代理)出来,但用户无法直接访问(不允许)K8s 的容器服务,所以又通过 外部 Nginx(第二次代理)出来。
  • 后端 API :Python 容器,通过 python 启动一个 API 服务(占用一个端口),再通过 外部 Nginx 代理出来,由 前端 Nginx 访问。

访问流程大致如下:

复制代码
浏览器 → 前端 Nginx → 后端 API → Python 服务

在本地开发环境中,使用 前端 Nginx image,直接部署映射端口出来,且不调整 后端 API 的情况下,直接修改 后端 API Nginx 的 CORS 配置即可正常工作:

不修改会有跨域问题

nginx 复制代码
# Nginx 配置文件
server {
    listen 80;
    server_name api.example.com;

    location / {
        # 允许特定的域名 
        # 即:返回的包头添加了 Access-Control-Allow-Origin:http://www.example.com 信息
        # 当 http://www.example.com 与当前页面访问的域名(http)匹配上就正常
        add_header 'Access-Control-Allow-Origin' 'http://www.example.com';
        # 允许的 HTTP 方法
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
        # 允许的请求头
        add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
        # 允许 Cookie 传递
        add_header 'Access-Control-Allow-Credentials' 'true';

        # 处理复杂请求的预检 OPTIONS 请求
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'http://www.example.com';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
            add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
            add_header 'Access-Control-Allow-Credentials' 'true';
            return 204;
        }

        proxy_pass http://backend_server;
    }
}

http://www.example.com 部分改成 宿主机IP:映射端口 即可。

此部分参考,博文:
解决跨域请求的终极指南:从 CORS 到 Nginx 配置,掌握关键技巧!

但把 前端服务 重新部署到到生产环境 Kubernetes 上,经过两层 Nginx 代理后,浏览器报错:

复制代码
CORS 头 'Access-Control-Allow-Origin' 不匹配 'http://www.example.com, http://www.example.com'

明显看到 响应头重复,导致浏览器拒绝访问。


二、问题分析

1. CORS 的基本原理

CORS 是浏览器的一种安全策略,用来限制网页从一个源发起对另一个源的请求。浏览器检查响应头:

  • Access-Control-Allow-Origin 必须包含当前网页的 Origin。
  • 复杂请求会先发送 OPTIONS 预检请求。

⚠️ 关键点:浏览器只关心 最终返回给它的响应头

内层代理返回的头,如果被外层代理覆盖或拼接,就会失效。

2. 双层代理带来的特殊情况

前端服务两层 Nginx:

  1. 内层 Nginx(第一层)返回了 Access-Control-Allow-Origin: *
  2. 外层 Nginx(第二层)默认也可能添加或拼接了 CORS 头。

最终浏览器看到的响应头可能是:

复制代码
Access-Control-Allow-Origin: http://www.example.com, *

很明显这个 CORS 头就是不合法的,这样 API 后端无论写什么地址都匹配不上了。


三、解决方案

1. 原则

CORS 头必须由最外层 Nginx 返回,内层 Nginx 不再添加。

  • 内层 Nginx 只做纯粹的反向代理,不加 CORS。
  • 外层 Nginx 根据浏览器 Origin 动态返回 Access-Control-Allow-Origin
  • 支持 OPTIONS 预检请求返回 204。

2. 实现方案

前端服务内部 Nginx(第一层Nginx)原本就没加 CORS 头,只是单纯代理出来,就不修改了。

在外部的Nginx上(第二层)nginx.conf 配置文件下修改。

在 http 块定义 map(server 外部)

nginx 复制代码
map $http_origin $allow_origin {
    default "";
    "~^https?://www\.example\.com$" $http_origin;
}

外层 Nginx(前端)配置

nginx 复制代码
server {
    listen 80;
    server_name www.example.com;

    location / {
        # 处理预检请求 OPTIONS
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin $allow_origin always;
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
            add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" always;
            add_header Access-Control-Max-Age 86400 always;
            return 204;
        }

        # 正常请求加 CORS 头
        add_header Access-Control-Allow-Origin $allow_origin always;
        add_header Access-Control-Allow-Credentials true always;

        proxy_pass http://example.swtxt.svc.cluster.local:8088;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

内层 Nginx(API 后端)配置

nginx 复制代码
server {
    listen 80;
    server_name www.exampleapi.com;

    location / {
        proxy_pass http://example.swtxt.svc.cluster.local:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

3. 关键点

  • map 必须在 server 外部定义
  • $allow_origin 保证只返回一个合法 Origin
  • always 确保即使 4xx/5xx 响应也带头
  • 内层 Nginx 不要重复添加 CORS

然后浏览器访问测试,是OK的,没有跨域问题了。


四、总结

做到这里,虽然都解决跨域问题了,但还会有个疑问:

为什么"前端只一层 nginx 时 CORS 要配置在后端",

而"前端两层 nginx 时 CORS 要配置最外层前端 nginx"?

浏览器不是都只看前端域名吗??

前端只 一层 Nginx

复制代码
浏览器 (域名:www.example.com)
   ↓ 反向代理
前端 Nginx
   ↓
后端 API 服务

浏览器 来说:

网络流向:浏览器 → 后端 API

若前后端不分离(不跨域),浏览器 → 前端 Nginx → 后端 API(浏览器对后端不可见)

前端和后端都是"公开的独立域名"

所以 Cross-Domain = 前端域名 访问后端域名

因此 后端必须允许前端域名 CORS

复制代码
Access-Control-Allow-Origin: https://www.example.com
配置位置 原因
后端 Nginx 添加 CORS 因为前端域名直接请求到 API 域名 → 后端必须允许

前端有 两层 Nginx(外层一层,内层一层)

复制代码
浏览器 (域名:www.example.com)
   ↓
外层前端 Nginx (公网)
   ↓
内层前端/后端 Nginx (集群内)
   ↓
后端 API 服务

注意这里:

➡ 浏览器 再也访问不到内部 Nginx 和后端 API

➡ 请求永远到不了内部那一层就已被外层拦截

浏览器看到的世界:

复制代码
浏览器 → www.example.com (只经过外层 Nginx)

浏览器不知道还有反向代理、内网服务

所有跨域判断都发生在和浏览器直接通信的那一层

也就是:外层前端 Nginx

📌 所以:

配置位置 原因
外层前端 Nginx 添加 CORS 因为它是浏览器的唯一交互对象,必须允许请求通过

内部 Nginx 和 API 不需要设置 CORS

因为浏览器永远看不到 + 不会触发跨域检查

用一句话总结

跨域检查(CORS)只有在浏览器环境中才会触发。

浏览器只会对它"看得到的那一跳"做跨域检查。
看不到的代理层不需要设置 CORS。

哪一层返回响应给浏览器,CORS 就必须在哪一层配置。

当然,跨域 情况遇到本来就比较少了,现在双 Nginx跨域 极端情况下,就更少见了,这么搞一次,可以对前后端网络流量、CORS 有更清晰的认知。

相关推荐
IT逆夜14 小时前
linux系统安全及应用
linux·运维
苹果醋314 小时前
VueX(Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式)
java·运维·spring boot·mysql·nginx
q***37515 小时前
Nginx如何实现 TCP和UDP代理?
tcp/ip·nginx·udp
q***965815 小时前
Nginx作用以及应用场景
运维·nginx
小肖爱笑不爱笑15 小时前
2025/11/19 网络编程
java·运维·服务器·开发语言·计算机网络
J***Q29216 小时前
Docker镜像多平台构建
运维·docker·容器
c***979816 小时前
Docker音频处理案例
运维·docker·容器
大锦终16 小时前
【Linux】Reactor
linux·运维·服务器·c++
佐杰16 小时前
Jenkins备份管理
java·运维·jenkins
杨云龙UP17 小时前
【MySQL逻辑备份】基于mysqldump的MySQL 8.0全量逻辑备份脚本
linux·运维·数据库·sql·mysql·mssql