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 有更清晰的认知。

相关推荐
kaoa0004 小时前
Linux入门攻坚——52、drbd - Distribute Replicated Block Device,分布式复制块设备-1
linux·运维·服务器
林九生4 小时前
【Debian】离线 Debian 系统如何正确设置东八区(Asia/Shanghai)时间
运维·debian
cqwuliu4 小时前
通过nginx+openssl自签名证书部署https应用并解决不安全问题
nginx·安全·https
啊略略wxx4 小时前
嵌入式Linux面试题目
linux·运维·服务器
半桔4 小时前
【IO多路转接】深入解析 poll:从接口到服务器实现
linux·运维·服务器·php
熙客4 小时前
Docker核心文件:DockerCompose文件
docker·容器
荣光波比4 小时前
CI/CD(三)—— 【保姆级实操】Jenkins+Docker GitLab+Tomcat 实现微服务CI/CD全流程部署
ci/cd·docker·jenkins
斯普信专业组4 小时前
rabbitmq-k8s下双架构镜像+手动sts部署完全文档(下)
架构·kubernetes·rabbitmq
xx.ii4 小时前
k8s:service资源详解
运维·网络·容器·kubernetes