You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot SAML SSO集成场景下自定义JWT令牌过期后的刷新机制问询

解决JWT过期后通过SAML SSO会话刷新令牌的方案

首先明确:不需要直接调用/saml/login端点,直接调用会触发完整的重定向流程,用户体验不好(即使IDP会话存在,也可能有不必要的页面跳转)。我们可以利用SAML的静默认证机制,在不打扰用户的前提下验证现有SSO会话有效性,进而生成新的JWT。

下面是具体的实现思路和步骤:

核心逻辑:依赖IDP的活跃会话进行静默认证

因为你的应用是无状态的,后端不存储用户会话,所以刷新JWT的关键是验证用户在IDP(比如Azure AD)的会话是否仍处于活跃状态。只要IDP会话有效,就能静默获取新的SAML断言,进而生成新的JWT。

具体实现步骤

1. 前端监听JWT过期,发起静默刷新请求

在Angular前端,你可以:

  • 用拦截器监听所有API请求,检查JWT的过期时间(比如提前5分钟触发刷新)
  • 当令牌快过期时,发起一个静默请求到后端的刷新接口(比如/api/saml/refresh-jwt
  • 这个请求可以用iframe发起,避免页面跳转,实现无感知刷新

示例Angular代码片段(伪代码):

// JWT拦截器中检测过期逻辑
const isTokenExpiringSoon = (token: string): boolean => {
  const payload = JSON.parse(atob(token.split('.')[1]));
  const expTime = payload.exp * 1000;
  return expTime - Date.now() < 5 * 60 * 1000; // 提前5分钟刷新
};

// 发起静默刷新
if (isTokenExpiringSoon(currentToken)) {
  const refreshIframe = document.createElement('iframe');
  refreshIframe.src = '/api/saml/refresh-jwt?refresh=true';
  refreshIframe.style.display = 'none';
  document.body.appendChild(refreshIframe);
  
  // 监听iframe加载完成后移除
  refreshIframe.onload = () => {
    document.body.removeChild(refreshIframe);
  };
}

2. 后端新增静默刷新接口,触发SAML静默认证

在Spring Boot后端,新增一个接口,利用Spring Security SAML的SAMLEntryPoint构造不带强制认证的AuthnRequest:

  • 设置ForceAuthn=false,告诉IDP如果用户已有活跃会话,直接返回断言,不需要重新登录
  • 让这个请求走你现有的SAML认证流程,最终进入onSSOSuccessHandler处理

示例后端代码片段:

@GetMapping("/api/saml/refresh-jwt")
public void refreshJwt(HttpServletRequest request, HttpServletResponse response) throws Exception {
  SAMLEntryPoint samlEntryPoint = applicationContext.getBean(SAMLEntryPoint.class);
  Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  
  // 构造AuthnRequest,禁用强制认证,允许IDP复用现有会话
  AuthnRequest authnRequest = samlEntryPoint.getAuthnRequest(request, response, authentication);
  authnRequest.setForceAuthn(Boolean.FALSE);
  
  // 重定向到IDP进行静默认证
  samlEntryPoint.commence(request, response, authentication);
}

3. 修改onSSOSuccessHandler,区分正常登录和刷新请求

在你现有的onSSOSuccessHandler中,添加逻辑判断请求来源:

  • 如果是刷新请求(通过请求参数标记),直接生成新的JWT并设置到Cookie中,返回200状态码
  • 如果是正常登录请求,继续执行原有的跳转逻辑

示例代码片段:

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  // 生成自定义JWT令牌
  String jwtToken = generateJwtToken(authentication);
  
  // 判断是否为刷新请求
  boolean isRefreshRequest = "true".equals(request.getParameter("refresh"));
  
  if (isRefreshRequest) {
    // 设置JWT到HttpOnly Cookie,有效期1小时
    Cookie jwtCookie = new Cookie("JWT_TOKEN", jwtToken);
    jwtCookie.setHttpOnly(true);
    jwtCookie.setSecure(true); // 生产环境开启
    jwtCookie.setMaxAge(3600);
    jwtCookie.setPath("/");
    response.addCookie(jwtCookie);
    
    // 返回成功状态,不跳转页面
    response.setStatus(HttpServletResponse.SC_OK);
  } else {
    // 正常登录流程,跳转到前端首页
    response.sendRedirect("/frontend/home");
  }
}

关键注意事项

  • IDP会话有效期:确保IDP的会话有效期长于JWT的有效期(比如设置为24小时),否则SAML会话过期后,必须重新登录
  • iframe限制:部分IDP(比如Azure AD)默认不允许在iframe中处理认证,需要在IDP的应用配置中添加你的前端域名到允许的frame列表
  • 无状态保障:后端不要存储任何会话信息,所有认证状态都依赖IDP的断言和JWT,保持应用的无状态特性
  • 安全防护:刷新接口要添加CSRF防护,避免跨站请求伪造攻击

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

火山引擎 最新活动