You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

前后端分离架构下Azure AD SAML SSO前端如何获取验证完成通知?

前后端分离下Azure AD SAML SSO的Token传递方案

针对React+NestJS分离架构下的SAML集成问题,以下是几种成熟的实现方案,按推荐程度排序:

方案一:弹出窗口跨页面通信(最常用)

利用浏览器postMessage API实现登录窗口与主应用窗口的直接通信,步骤如下:

  1. 前端登录逻辑:

    • 用户点击登录按钮时,打开新窗口指向Azure AD登录地址(可由NestJS生成SAML请求后返回跳转URL,前端直接调用)。
    • 主窗口监听message事件,严格验证消息来源合法性:
      // React主应用代码
      useEffect(() => {
        const handleMessage = (event) => {
          // 仅接收信任来源的消息,防止恶意攻击
          if (event.origin !== process.env.REACT_APP_BASE_URL) return;
          
          if (event.data.type === 'SAML_LOGIN_SUCCESS') {
            const { token } = event.data;
            // 存储Token到localStorage或全局状态
            localStorage.setItem('authToken', token);
            // 刷新页面或跳转到首页
            window.location.reload();
          }
        };
        window.addEventListener('message', handleMessage);
        return () => window.removeEventListener('message', handleMessage);
      }, []);
      
  2. NestJS处理SAML响应:

    • 接收Azure AD POST的SAML响应,完成签名验证、断言解析等逻辑,生成业务Token(如JWT)。
    • 返回内嵌JavaScript的HTML页面,通过window.opener.postMessage将Token发送给主窗口后关闭当前窗口:
      // NestJS的SAML回调控制器
      @Post('/saml')
      async samlCallback(@Body() samlResponse: any, @Res() res: Response) {
        // 验证SAML响应并生成Token
        const token = await this.authService.generateJwt();
        
        // 返回用于跨窗口通信的HTML页面
        const html = `
          <!DOCTYPE html>
          <html>
          <body>
            <script>
              window.opener.postMessage(
                { type: 'SAML_LOGIN_SUCCESS', token: '${token}' },
                '${process.env.FRONTEND_BASE_URL}'
              );
              window.close();
            </script>
          </body>
          </html>
        `;
        res.send(html);
      }
      

优点:实现简单、实时性好,无额外依赖;缺点:需确保浏览器允许弹出窗口,且postMessage的来源验证必须严格。

通过HttpOnly Cookie传递Refresh Token,结合前端轮询状态接口完成登录流程:

  1. NestJS处理逻辑:

    • 验证SAML响应后,生成Access Token和Refresh Token,将Refresh Token设置为HttpOnly、Secure、SameSite属性的Cookie(跨域场景需配置CORS允许携带Cookie)。
    • 返回提示登录成功的HTML页面,自动关闭窗口或引导用户返回主应用:
      @Post('/saml')
      async samlCallback(@Body() samlResponse: any, @Res() res: Response) {
        const { accessToken, refreshToken } = await this.authService.generateTokens();
        
        // 设置HttpOnly Cookie,防止XSS窃取
        res.cookie('refreshToken', refreshToken, {
          httpOnly: true,
          secure: process.env.NODE_ENV === 'production',
          sameSite: 'none', // 跨域场景需设置为none,同时必须开启secure
          maxAge: 7 * 24 * 60 * 60 * 1000 // 有效期7天
        });
        
        // 返回自动关闭的提示页面
        const html = `
          <!DOCTYPE html>
          <html>
          <body>
            <p>登录成功,即将关闭窗口...</p>
            <script>setTimeout(() => window.close(), 1000)</script>
          </body>
          </html>
        `;
        res.send(html);
      }
      
  2. 前端轮询逻辑:

    • 打开登录窗口后,前端定时调用后端状态接口(如/auth/saml/status),该接口检查Cookie中的Refresh Token,返回有效的Access Token:
      // React主应用代码
      const handleLogin = () => {
        window.open('/api/saml/login', '_blank', 'width=600,height=600');
        
        // 轮询状态接口,检查登录是否完成
        const interval = setInterval(async () => {
          try {
            const res = await fetch('/api/auth/saml/status', {
              credentials: 'include' // 携带Cookie
            });
            if (res.ok) {
              const { accessToken } = await res.json();
              localStorage.setItem('authToken', accessToken);
              window.location.reload();
              clearInterval(interval);
            }
          } catch (err) {
            // 未登录成功,继续轮询
          }
        }, 1000);
      };
      

优点:安全性高(HttpOnly Cookie避免XSS窃取);缺点:轮询会产生额外请求,跨域Cookie配置需注意浏览器兼容性。

方案三:WebSocket实时推送(你考虑的方案)

通过WebSocket连接实现后端主动向前端推送Token,适合对实时性要求高的场景:

  1. 前端WebSocket连接:

    • 打开登录窗口前,建立WebSocket连接到NestJS,生成临时会话ID(如UUID)并传递给登录窗口:
      // React主应用代码
      const handleLogin = () => {
        const sessionId = crypto.randomUUID();
        const ws = new WebSocket(`ws://localhost:3000/ws/saml/${sessionId}`);
        
        ws.onmessage = (event) => {
          const { token } = JSON.parse(event.data);
          localStorage.setItem('authToken', token);
          window.location.reload();
          ws.close();
        };
        
        // 打开登录窗口并携带sessionId
        window.open(`/api/saml/login?sessionId=${sessionId}`, '_blank', 'width=600,height=600');
      };
      
  2. NestJS WebSocket与SAML处理:

    • 实现WebSocket网关,维护sessionId与WebSocket连接的映射:
      // NestJS WebSocket网关
      @WebSocketGateway()
      export class SamlWsGateway {
        private connections = new Map<string, WebSocket>();
        
        @SubscribeMessage('connect')
        handleConnect(client: WebSocket, payload: { sessionId: string }) {
          this.connections.set(payload.sessionId, client);
        }
        
        // 供SAML控制器调用的Token推送方法
        sendToken(sessionId: string, token: string) {
          const client = this.connections.get(sessionId);
          if (client) {
            client.send(JSON.stringify({ token }));
            this.connections.delete(sessionId);
          }
        }
      }
      
    • SAML回调控制器中调用网关推送Token:
      @Post('/saml')
      async samlCallback(@Body() samlResponse: any, @Query('sessionId') sessionId: string, @Res() res: Response) {
        const token = await this.authService.generateJwt();
        this.samlWsGateway.sendToken(sessionId, token);
        
        // 返回关闭窗口的页面
        res.send('<script>window.close()</script>');
      }
      

优点:实时性强,无需轮询;缺点:实现复杂度高,需处理WebSocket连接断开、重连等异常,服务器需维护连接映射。


内容的提问来源于stack exchange,提问作者prof chaos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 04:42:55