Nginx解决跨域问题
Nginx解决跨域问题
一、跨域原理与解决思路
什么是跨域
跨域是浏览器的安全策略,当页面脚本试图访问不同源(协议、域名、端口任一不同)的资源时,浏览器会拦截请求。
关键点:跨域限制只存在于浏览器中。使用curl、Postman或服务端HttpClient发起请求完全不受影响,因为它们没有同源策略。
核心解决思路
既然跨域是浏览器基于地址判断的,解决思路就是:
让浏览器认为所有请求都来自同一个源——保持浏览器地址栏中的协议、域名、端口一致即可。
具体做法是通过反向代理(Nginx),将前端页面和后端服务统一代理到同一个地址下,浏览器看到的始终是同一个 origin。
二、常见问题:iframe 跨域无法操作登录表单
问题场景
在 SSO 集成中,一种常见方案是用 iframe 内嵌第三方登录页,然后通过 JS 自动填充用户名密码并提交登录:
<iframesrc="http://sso-server:8080/login"id="loginFrame"></iframe><script>constiframe=document.getElementById('loginFrame');iframe.onload=function(){// 尝试获取 iframe 内部 DOM 并填充表单constiframeDoc=iframe.contentDocument;constusernameInput=iframeDoc.querySelector('input[type="text"]');constpasswordInput=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 配置示例
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 统一设置:
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 重定向作为补充方案。
