Nginx解决跨域问题

Nginx解决跨域问题

一、跨域原理与解决思路

什么是跨域

跨域是浏览器的安全策略,当页面脚本试图访问不同源(协议、域名、端口任一不同)的资源时,浏览器会拦截请求。

关键点:跨域限制只存在于浏览器中 。使用 curlPostman 或服务端 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 重定向方案:

  1. 用户在外网 SSO 完成认证,获取 Token

  2. SSO 将用户重定向到内网中转页,URL 中携带 Token

  3. 中转页从 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 重定向作为补充方案。

相关推荐
虾壳云官方9 小时前
OpenClaw 2.7.9 Windows 一键部署教程:零基础也能搭建 AI 自动化助手
运维·人工智能·windows·自动化·openclaw·openclaw一键部署
江南风月9 小时前
WGCLOUD保姆级教程最新版整理
运维·zabbix·运维开发·prometheus·日志审计
志栋智能10 小时前
超自动化巡检:知识沉淀与团队协作的新载体
大数据·运维·网络·数据库·人工智能·自动化
vsropy11 小时前
Ubuntu网络图标消失问题/有网络问号
linux·运维·ubuntu
fofantasy11 小时前
NSK LH12AN 微型导轨技术手册
运维·网络·数据库·经验分享·规格说明书
coderwu11 小时前
Ubuntu 24.04 终端输入 openclaw config 提示未找到命令解决办法
linux·运维·ubuntu
dxxt_yy11 小时前
千兆光/电口+OTDR一体——成都鼎讯 SZT-1000A 千兆以太网测试仪助力风电能源光缆管理
运维·服务器·能源
AI帮小忙13 小时前
Debian系linux操作系统里安装OpenClaw
linux·运维·debian
极创信息13 小时前
Linux挖矿病毒深度清理实战教程,从进程隐藏、Rootkit驻留到彻底根除
java·大数据·linux·运维·安全·tomcat·健康医疗
志栋智能14 小时前
超自动化巡检剧本(Playbook):运维经验的数字化封装
运维·自动化