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
相关产品推荐
相关产品推荐

