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 配置文件
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:
- 内层 Nginx(第一层)返回了
Access-Control-Allow-Origin: * - 外层 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 外部):
map $http_origin $allow_origin {default "";"~^https?://www\.example\.com$" $http_origin;
}
外层 Nginx(前端)配置:
server {listen 80;server_name www.example.com;location / {# 处理预检请求 OPTIONSif ($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 后端)配置:
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保证只返回一个合法 Originalways确保即使 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 有更清晰的认知。
