Nginx解决跨域问题
一、跨域原理与解决思路
什么是跨域
跨域是浏览器的安全策略,当页面脚本试图访问不同源(协议、域名、端口任一不同)的资源时,浏览器会拦截请求。
关键点:跨域限制只存在于浏览器中 。使用 curl、Postman 或服务端 HttpClient 发起请求完全不受影响,因为它们没有同源策略。
核心解决思路
既然跨域是浏览器基于地址判断的,解决思路就是:
让浏览器认为所有请求都来自同一个源------保持浏览器地址栏中的协议、域名、端口一致即可。
具体做法是通过反向代理(Nginx),将前端页面和后端服务统一代理到同一个地址下,浏览器看到的始终是同一个 origin。
二、常见问题:iframe 跨域无法操作登录表单
问题场景
在 SSO 集成中,一种常见方案是用 iframe 内嵌第三方登录页,然后通过 JS 自动填充用户名密码并提交登录:
html
<iframe src="http://sso-server:8080/login" id="loginFrame"></iframe>
<script>
const iframe = document.getElementById('loginFrame');
iframe.onload = function() {
// 尝试获取 iframe 内部 DOM 并填充表单
const iframeDoc = iframe.contentDocument;
const usernameInput = iframeDoc.querySelector('input[type="text"]');
const passwordInput = iframeDoc.querySelector('input[type="password"]');
usernameInput.value = 'admin'; // 报错!
passwordInput.value = 'password'; // 报错!
};
</script>
报错信息
Uncaught DOMException: Blocked a frame with origin "http://my-app:3000"
from accessing a cross-origin frame.
原因分析
浏览器禁止跨域访问 iframe 内部的 DOM。当父页面(http://my-app:3000)和 iframe 页面(http://sso-server:8080)不同源时,父页面的 JS 无法:
- 读取或修改 iframe 内的 DOM 元素
- 获取 iframe 内的 Cookie 或 LocalStorage
- 监听 iframe 内的事件
这意味着通过 iframe 内嵌登录页 + JS 自动填充的方案在跨域场景下行不通。
三、使用中转页 + Nginx 解决跨域
解决方案架构
核心思路:将中转页地址 和登录服务后端代理到同一个域名和端口下,让浏览器认为它们是同源的。
用户浏览器
│
▼
Nginx (统一入口 http://proxy-server:80)
├── /sso-proxy/ → 中转页服务 (http://127.0.0.1:8081)
├── /app/ → 目标系统前端 (http://127.0.0.1:3000)
└── /app-api/ → 目标系统后端 API (http://127.0.0.1:8080)
浏览器始终访问 http://proxy-server,所有请求同源,跨域问题消失。
Nginx 配置示例
nginx
server {
listen 80;
server_name proxy-server;
# 中转页(SSO 登录中转服务)
location /sso-proxy/ {
proxy_pass http://127.0.0.1:8081/sso-proxy/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 目标系统前端
location /app/ {
proxy_pass http://127.0.0.1:3000/app/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 目标系统后端 API
location /app-api/ {
proxy_pass http://127.0.0.1:8080/app-api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 设置 CORS 头部,允许中转页访问
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials true;
# 处理预检请求
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With";
add_header Access-Control-Allow-Credentials true;
add_header Content-Length 0;
return 204;
}
}
}
CORS 头部注意事项:只能设置一次
Access-Control-Allow-Origin 响应头只能出现一次。如果 Nginx 和后端应用同时设置了该头部,浏览器会收到重复的头部导致请求失败:
Access to XMLHttpRequest has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header contains multiple values
'http://my-app, http://my-app', but only one is allowed.
解决办法:只在一处设置 CORS 头部。
- 方案 A:只在 Nginx 设置,后端不设置
- 方案 B:只在后端设置,Nginx 不加
add_header
如果后端已经设置了 CORS 头,Nginx 中可以用 proxy_hide_header 去掉后端返回的头,再由 Nginx 统一设置:
nginx
location /app-api/ {
proxy_pass http://127.0.0.1:8080/app-api/;
# 去掉后端返回的 CORS 头,避免重复
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Allow-Credentials;
# 由 Nginx 统一设置
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Allow-Credentials true;
}
中转页工作流程
完整的 SSO 中转登录流程如下:
1. 用户访问中转页
http://proxy-server/sso-proxy/transit.html?token=xxx
2. 中转页通过同源的 API 获取用户信息
GET http://proxy-server/sso-proxy/getUserInfo
(同源,无跨域问题)
3. 中转页内嵌 iframe 加载目标系统
iframe src = http://proxy-server/app/
(同源,可以操作 iframe DOM)
4. 中转页填充用户名密码并调用登录接口
POST http://proxy-server/app-api/user/login
(同源,无跨域问题)
5. 登录成功,跳转到目标系统
redirect → http://proxy-server/app/#/home
四、其他补充方案:Token 重定向
当内外网环境无法通过 Nginx 统一代理时(如内网系统需要从外网 SSO 获取认证),可以使用Token 重定向方案:
-
用户在外网 SSO 完成认证,获取 Token
-
SSO 将用户重定向到内网中转页,URL 中携带 Token
-
中转页从 URL 提取 Token,再重定向到目标系统
外网 SSO 认证
│
▼ 重定向(携带 token)
http://内网地址/sso-proxy/transit.html?redirect=目标地址&token=xxx
│
▼ 中转页提取 token,拼接后跳转
http://目标系统地址?token=xxx
这种方式避免了跨域 Ajax 请求,通过浏览器地址栏跳转传递 Token,不受同源策略限制。
五、总结
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Nginx 反向代理 | 所有服务可统一入口 | 彻底解决跨域,对代码无侵入 | 需要 Nginx 配置 |
| CORS 头部 | 服务端可控 | 配置灵活 | 注意不能重复设置 |
| Token 重定向 | 内外网隔离场景 | 绕过跨域限制 | 流程较复杂 |
| iframe + 同源代理 | 需要操作第三方页面 DOM | 可自动填充表单 | 必须保证同源 |
最佳实践: 优先使用 Nginx 反向代理将所有服务统一到同一域下,从根本上消除跨域问题。在无法统一代理的场景下,使用 Token 重定向作为补充方案。