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

SAML认证后向React传递JWT并存储至localStorage的方案

解决方案

后端修改:返回中转HTML页面替代JSON响应

SAML 2.0的SSO回调是浏览器直接发起的跳转请求,而非前端AJAX请求,因此无法通过axios获取响应内容。我们需要后端返回一个中转HTML页面,通过页面内的JavaScript完成JWT存储和页面跳转。

方案1:使用模板引擎(如Thymeleaf)

修改后端回调接口代码:

@PostMapping("/login/saml2/sso/okta") // 路径需匹配SAML配置的回调地址
public String handleSAMLLogin(Authentication authentication, Model model) {
    Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();

    Map<String, Object> claims = new HashMap<>();
    principal.getAttributes().forEach((key, value) -> {
        claims.put(key, value.get(0));
    });

    JwtResponse jwtResponse = new JwtResponse(
            samlAuthenticationSuccessHandler.generateSamlJWTToken(principal, claims)
    );

    // 将JWT数据传入模板
    model.addAttribute("accessToken", jwtResponse.getAccessToken());
    model.addAttribute("tokenType", jwtResponse.getTokenType());
    model.addAttribute("expiresIn", jwtResponse.getExpiresIn());
    model.addAttribute("redirectUrl", "/home");

    // 返回中转页面视图
    return "saml-callback";
}

创建saml-callback.html模板文件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>处理认证中...</title>
</head>
<body>
    <script>
        // 从模板注入的数据中提取JWT并存储
        const accessToken = [[${accessToken}]];
        const tokenType = [[${tokenType}]];
        const expiresIn = [[${expiresIn}]];
        
        if (accessToken) {
            localStorage.setItem("token", `${tokenType} ${accessToken}`);
            localStorage.setItem("expiresIn", expiresIn);
        }
        
        // 跳转到目标页面
        window.location.href = [[${redirectUrl}]];
    </script>
</body>
</html>

方案2:直接拼接HTML字符串返回(无需模板引擎)

如果不想引入模板引擎,可直接在后端拼接HTML响应:

@PostMapping("/login/saml2/sso/okta")
public ResponseEntity<String> handleSAMLLogin(Authentication authentication) {
    // 生成JwtResponse的逻辑保持不变
    Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();
    Map<String, Object> claims = new HashMap<>();
    principal.getAttributes().forEach((key, value) -> claims.put(key, value.get(0)));
    JwtResponse jwtResponse = new JwtResponse(
            samlAuthenticationSuccessHandler.generateSamlJWTToken(principal, claims)
    );

    // 拼接中转HTML
    String html = String.format("""
        <!DOCTYPE html>
        <html>
        <body>
            <script>
                localStorage.setItem('token', '%s %s');
                localStorage.setItem('expiresIn', '%s');
                window.location.href = '/home';
            </script>
        </body>
        </html>
        """, jwtResponse.getTokenType(), jwtResponse.getAccessToken(), jwtResponse.getExpiresIn());

    return ResponseEntity.ok()
            .contentType(MediaType.TEXT_HTML)
            .body(html);
}

前端修改:移除无效逻辑

原handleAuthCallback方法是通过axios请求回调地址,但SAML回调是浏览器直接跳转,该方法不会被触发,可直接删除。保留其余认证状态相关方法即可:

import axios from "axios";

export const initiateAuth = () => {
  window.location.href = "http://localhost:8080/saml2/authenticate/okta";
};

export const isAuthenticated = () => {
  const token = localStorage.getItem("token");
  return !!token;
};

export const getToken = () => {
   return localStorage.getItem("token");
};

export const logout = () => {
  localStorage.removeItem("token");
  localStorage.removeItem("expiresIn");
   delete axios.defaults.headers.common["Authorization"];
};

axios.interceptors.request.use(
   (config) => {
    const token = getToken();
    if (token) {
      config.headers.Authorization = token; 
        }
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );

核心逻辑说明

  • SAML 2.0的SSO流程是浏览器端跳转,后端无法直接向前端AJAX请求返回数据,中转HTML页面的脚本在浏览器环境中执行,可直接操作localStorage。
  • 中转过程对用户几乎透明,完成存储后立即跳转到目标页面,满足需求同时避免JWT暴露在URL或Cookie中。

内容的提问来源于stack exchange,提问作者Sanjana MV

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 10:09:54