Nginx反代url中带#的过程

-
-
2025-08-11 16:30

外部访问地址

http://127.0.0.1:8081/user-center/#/?access_token=5bbafc23ae47465aaab8b5461c17f211

内部代理后的地址

http://127.0.0.1:9909/user-center/oauth2/login?token=5bbafc23ae47465aaab8b5461c17f211

 

Nginx 和任何其他后端服务器一样,是绝对获取不到 URL 中 # (哈希/片段) 后面的内容的。

这是由 Web 标准决定的:# 后面的内容是专门给浏览器使用的,用于页面内部的锚点定位或在单页面应用(SPA)中进行路由,它根本不会被浏览器发送到服务器。

因此,您无法直接用一条 Nginx proxy_passrewrite 规则来实现这个转换。

 

正确的解决方案:客户端重定向

 

要解决这个问题,我们需要“欺骗”一下浏览器,让它自己把 access_token# 后面挪到 ? 后面,再重新向 Nginx 发起一次请求。这通常通过返回一个包含一小段 JavaScript 的中间页面来实现。

整个流程如下:

  1. 第一次请求:浏览器访问 http://127.0.0.1:8081/user-center/#/?access_token=...
  2. Nginx 响应:Nginx 接收到对 /user-center/ 的请求(注意,没有 # 后面的部分)。您的配置应该让 Nginx 返回一个特殊的 redirect.html 页面。
  3. 浏览器执行JS:浏览器加载 redirect.html,页面里的 JavaScript 会读取地址栏中完整的 URL,从 # 后面提取出 access_token 的值。
  4. 浏览器重定向:JavaScript 构建一个新的 URL http://127.0.0.1:8081/user-center/oauth2/login?token=...,并命令浏览器跳转到这个新地址。
  5. 第二次请求:浏览器向 Nginx 发起第二次请求。这次的 URL 是 Nginx 可以完全理解的,因为它包含了标准的查询参数 ?token=...
  6. Nginx 代理:Nginx 接收到这个新请求,并根据配置将其完美地代理到后端的 http://127.0.0.1:9909/user-center/oauth2/login?token=...

 

Nginx配置

 

server {
    listen 8081;
    server_name 127.0.0.1;

    # 这里不再需要全局的 root 指令

    # 规则一:处理 SSO 登录重定向的静态 HTML 文件
    location = /user-center/ {
        # root 指令只对这个 location 生效
        root /var/www/sso-redirect; # <-- 指定 redirect.html 所在的目录
        try_files /redirect.html =404;
    }
    location = /user-center {
        root /var/www/sso-redirect;
        try_files /redirect.html =404;
    }

    # 规则二:处理登录请求的反向代理
    # 这个 location 负责代理,不需要 root 指令
    location /user-center/oauth2/login {
        proxy_pass http://127.0.0.1:9909; # 注意这里proxy_pass后面没有 /

        # proxy_pass http://...; 会将完整的URI (/user-center/oauth2/login) 传递给后端
        # 所以后端收到的请求是 http://127.0.0.1:9909/user-center/oauth2/login
        # 这正好符合您后端Java代码的 @RequestMapping

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 规则三:为您的另一个应用(博客)提供服务
    # 比如,访问 http://127.0.0.1:8081/blog/
    location /blog/ {
        # 这个 location 使用一个完全不同的根目录
        root /var/www/my-blog;
        index index.html index.htm;
        try_files $uri $uri/ /blog/index.html;
    }

    # 规则四:为通用静态资源(如图片、CSS)提供服务
    # 同样可以有独立的 root
    location ~ \.(css|js|png|jpg|gif)$ {
        root /var/www/common-assets;
        expires 30d; # 可以顺便加上缓存策略
    }
}

 

redirect.html

 

<!DOCTYPE html>
<html>
<head>
    <title>Redirecting...</title>
    <script type="text/javascript">
        try {
            // 1. 获取URL中 # 后面的内容,例如 "#/?access_token=xyz"
            var hash = window.location.hash.substring(1);

            // 2. 将其解析为 URLSearchParams 对象,方便获取参数
            // hash 可能是 /?access_token=xyz 或 ?access_token=xyz,我们去掉开头的 /
            if (hash.startsWith('/')) {
                hash = hash.substring(1);
            }
            var params = new URLSearchParams(hash);

            // 3. 获取 access_token 的值
            var accessToken = params.get('access_token');

            if (accessToken) {
                // 4. 构建新的目标 URL
                var newUrl = '/user-center/oauth2/login?token=' + encodeURIComponent(accessToken);

                // 5. 使用 replace 跳转,这样浏览器的历史记录中不会留下这个中间页
                window.location.replace(newUrl);
            } else {
                // 如果没有 access_token,可以显示错误信息
                document.write('Error: access_token not found.');
            }
        } catch (e) {
            document.write('An error occurred during redirection.');
            console.error(e);
        }
    </script>
</head>
<body>
    <p>Please wait, you are being redirected...</p>
</body>
</html>