如何在Spring Boot+Spring Security OAuth2中提取Refresh Token值?
嘿,针对你使用Spring Boot 2.x、Spring Security 5.x + OAuth2 2.3.x对接第三方资源服务器的场景,我有几个轻量方案可以帮你快速拦截到Refresh Token的值,不用立刻搭建完整的Refresh Token Repository:
方案1:自定义OAuth2AccessTokenResponseClient(最直接推荐)
这个方案是在获取Token的核心环节直接拦截响应,因为OAuth2AccessTokenResponseClient负责和授权服务器交互并返回Token响应,我们可以重写它的方法来提取Refresh Token:
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; @Component public class CustomTokenResponseClient extends DefaultAuthorizationCodeTokenResponseClient { @Override public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) { // 先调用父类获取完整的Token响应 OAuth2AccessTokenResponse tokenResponse = super.getTokenResponse(authorizationCodeGrantRequest); // 提取Refresh Token String refreshToken = tokenResponse.getRefreshToken().getTokenValue(); // 这里可以做你需要的操作:比如临时存入内存、打印日志、或者预存到后续要做的Repository里 System.out.println("拦截到Refresh Token:" + refreshToken); return tokenResponse; } }
然后在你的Security配置里,把这个自定义的Client注入到OAuth2登录流程中:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomTokenResponseClient customTokenResponseClient; @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .tokenEndpoint() .accessTokenResponseClient(customTokenResponseClient); } }
这个方案的优势是:在Token获取的第一时间拿到Refresh Token,不会错过任何场景(比如首次登录、Token自动刷新),完全贴合Spring Security OAuth2的原生流程,后续迁移到正式Repository也很顺畅。
方案2:利用AuthenticationSuccessHandler拦截认证成功事件
当用户通过OAuth2认证成功后,AuthenticationSuccessHandler会被触发,我们可以从认证对象中提取已授权的客户端信息,进而拿到Refresh Token:
import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler { @Autowired private OAuth2AuthorizedClientService authorizedClientService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; // 获取已授权的客户端 OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient( oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName() ); // 提取Refresh Token if (authorizedClient.getRefreshToken() != null) { String refreshToken = authorizedClient.getRefreshToken().getTokenValue(); System.out.println("认证成功,拦截到Refresh Token:" + refreshToken); } } // 继续执行默认的成功跳转逻辑 response.sendRedirect("/home"); } }
然后在Security配置中配置这个SuccessHandler:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomAuthSuccessHandler customAuthSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { http .oauth2Login() .successHandler(customAuthSuccessHandler); } }
这个方案适合只需要在用户首次登录时获取Refresh Token的场景,不过如果是后台自动刷新Token的情况(比如用OAuth2AuthorizedClientManager自动刷新),这个Handler不会被触发,所以推荐结合方案1使用。
方案3:自定义Filter拦截Token响应(备选)
如果上面两种方案都不适合,你可以通过自定义Filter来拦截授权服务器返回的Token响应,解析响应体中的Refresh Token:
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingResponseWrapper; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class TokenResponseFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 只拦截Token端点的响应(根据你的授权服务器Token地址调整) if (request.getRequestURI().contains("/oauth2/token")) { // 包装响应以读取响应体 ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); filterChain.doFilter(request, wrappedResponse); // 读取响应体内容并解析Refresh Token String responseBody = new String(wrappedResponse.getContentAsByteArray(), wrappedResponse.getCharacterEncoding()); if (responseBody.contains("refresh_token")) { // 这里可以用Jackson/Gson做正规JSON解析,示例用简单字符串处理 String refreshToken = responseBody.split("\"refresh_token\":\"")[1].split("\"")[0]; System.out.println("Filter拦截到Refresh Token:" + refreshToken); } // 必须把缓存的内容写回原始响应 wrappedResponse.copyBodyToResponse(); } else { filterChain.doFilter(request, response); } } }
然后在Security配置中注册这个Filter:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .addFilterAfter(new TokenResponseFilter(), OAuth2AuthorizationCodeGrantFilter.class); } }
这个方案比较灵活,但需要自己处理响应体的解析,而且要注意授权服务器的Token响应格式,适合特殊场景下使用。
内容的提问来源于stack exchange,提问作者Trynkiewicz Mariusz

