Spring Boot OpenID Connect中Refresh Token续期AccessToken的实现咨询
嘿,作为刚上手Spring Boot和OIDC的新手,你碰到的这俩问题其实特别典型——好在Spring Security已经内置了不少OIDC相关的能力,不用我们从头写复杂逻辑。我来一步步给你拆解解决方案:
一、用Refresh Token续期AccessToken:不用自己写核心逻辑
Spring Security的OIDC客户端模块已经集成了Refresh Token的自动处理,你只需要确保几个配置到位就行:
1. 先让客户端请求offline_access权限
OIDC规范里,只有当你请求offline_access这个scope时,身份提供商才会返回Refresh Token。所以在你的配置文件(比如application.yml)里要加上这个scope:
spring: security: oauth2: client: registration: my-oidc-client: # 替换成你的客户端ID client-id: your-client-id-here client-secret: your-client-secret-here scope: openid, profile, email, offline_access # 必须包含offline_access # 其他登录相关配置(比如redirect-uri) provider: my-oidc-provider: # 替换成你的提供商ID issuer-uri: https://your-oidc-provider.com/issuer # 比如Keycloak、Auth0的地址
2. 直接用Spring Security的服务获取有效令牌
当AccessToken过期时,OAuth2AuthorizedClientService会自动帮你用Refresh Token去刷新。如果你需要手动获取或检查令牌有效性,可以这么写:
@Autowired private OAuth2AuthorizedClientService authorizedClientService; public String getValidAccessToken(Authentication authentication) { // 加载当前用户的授权客户端信息 OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient( "my-oidc-client", authentication.getName() ); // 可以提前检查令牌是否即将过期(比如剩余时间不足60秒),手动触发刷新 if (client.getAccessToken().expiresIn() < 60) { OAuth2AuthorizedClient refreshedClient = authorizedClientService.refreshAuthorizedClient( "my-oidc-client", authentication ); return refreshedClient.getAccessToken().getTokenValue(); } // 没过期的话直接返回当前令牌 return client.getAccessToken().getTokenValue(); }
二、是否需要自定义过滤器?怎么替换会话里的Authentication?
先说结论:大部分场景下不需要自定义过滤器——Spring Security会在每次请求时自动检查AccessToken有效性,过期且有有效Refresh Token的话会自动刷新。但如果你的业务需要主动定期检查(比如后台任务),或者要把新令牌同步到你自定义的UsernamePasswordAuthenticationToken里(正常OIDC认证后会话里是OAuth2AuthenticationToken,你应该是做了自定义转换?),可以按下面的步骤来:
1. 自定义令牌刷新过滤器(如果需要主动检查)
写一个继承OncePerRequestFilter的过滤器,放到Spring Security过滤器链里,在OIDC认证过滤器之后执行:
@Component public class TokenRefreshFilter extends OncePerRequestFilter { @Autowired private OAuth2AuthorizedClientService authorizedClientService; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // 只处理OIDC类型的认证对象 if (auth instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthAuth = (OAuth2AuthenticationToken) auth; String clientId = oauthAuth.getAuthorizedClientRegistrationId(); String username = oauthAuth.getName(); OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(clientId, username); // 提前5分钟检查令牌是否即将过期 if (client != null && isTokenAboutToExpire(client.getAccessToken())) { // 触发令牌刷新 OAuth2AuthorizedClient refreshedClient = authorizedClientService.refreshAuthorizedClient(clientId, auth); // 如果需要替换会话里的UsernamePasswordAuthenticationToken UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken newAuthToken = new UsernamePasswordAuthenticationToken( userDetails, null, // OIDC认证不需要凭证,填null就行 userDetails.getAuthorities() ); // 复制原认证对象的细节(比如请求信息) newAuthToken.setDetails(oauthAuth.getDetails()); // 更新SecurityContext和会话,确保后续请求用新的认证对象 SecurityContextHolder.getContext().setAuthentication(newAuthToken); request.getSession().setAttribute( HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext() ); } } // 继续执行过滤器链 filterChain.doFilter(request, response); } // 判断令牌是否即将过期 private boolean isTokenAboutToExpire(OAuth2AccessToken accessToken) { Instant now = Instant.now(); // 提前5分钟触发刷新,避免刚好在请求过程中过期 return accessToken.getExpiresAt().minus(Duration.ofMinutes(5)).isBefore(now); } }
2. 把过滤器加到Spring Security配置里
在你的SecurityFilterChain配置中,将这个过滤器放在OAuth2AuthenticationProcessingFilter之后:
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private TokenRefreshFilter tokenRefreshFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 // 你的OIDC登录页面、回调地址等配置 ) .oauth2Client(withDefaults()) // 添加自定义过滤器,放在OIDC认证过滤器之后 .addFilterAfter(tokenRefreshFilter, OAuth2AuthenticationProcessingFilter.class); return http.build(); } }
几个关键提醒
- Refresh Token有效期:这个是由你的OIDC提供商决定的,比如Keycloak默认Refresh Token有效期是30天。如果Refresh Token也过期了,那就只能让用户重新登录了。
- 别过度自定义:Spring Security已经帮我们处理了大部分令牌刷新的场景,只有当你有特殊业务需求(比如主动后台刷新)时才需要写自定义过滤器。
- 会话同步要到位:替换Authentication对象后,一定要同时更新SecurityContext和HttpSession,不然后续请求可能还是用旧的令牌信息。
内容的提问来源于stack exchange,提问作者Rak2018

