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 重定向作为补充方案。

相关推荐
c++逐梦人9 小时前
epoll ET服务器(Reactor模式)
运维·服务器·php
牛奔10 小时前
codebuddy 桌面版 如何配置自己的模型
运维·服务器·开发语言·php
SilentSamsara10 小时前
日志与可观测性:logging 进阶配置与结构化日志实战
运维·开发语言·python·青少年编程
学困昇10 小时前
Linux IPC 详解:匿名管道、命名管道、共享内存与信号量
linux·运维·服务器·c语言·c++·人工智能
TYKJ02311 小时前
服务器带宽的"独享"和"共享"到底差在哪?从原理到实测讲清楚
运维·服务器·后端
%KT%11 小时前
Windows安装wsl2和docker desktop,部署qdrant向量数据库
运维·docker·容器
vortex511 小时前
Kali Linux 安装与使用 GitHub CLI (gh)教程
linux·运维·github
腾讯蓝鲸智云12 小时前
嘉为蓝鲸WeOps:47天周期常态化管理,全生命周期智能方案筑牢安全防线
运维·服务器·人工智能·自动化·云计算
dingxingdi12 小时前
Linux 清理过程
linux·运维·服务器