Spring Gateway OAuth2客户端AccessToken同步刷新问题及方案咨询
解决Spring Cloud Gateway OAuth2 Client并发令牌刷新问题
问题背景
基于Spring Boot 3.3.3的Gateway应用配置为OAuth2 Client,开启spring.security.oauth2.authorizationserver.client.gateway.token.reuse-refresh-tokens=false后,AccessToken过期时多请求并发触发令牌刷新:其中一个线程刷新成功并更新了刷新令牌,其他线程的刷新请求会被授权服务器拒绝,抛出ClientAuthorizationException。
现有方案分析
你尝试通过重写AbstractWebClientReactiveOAuth2AccessTokenResponseClient#getTokenResponse,用ConcurrentHashMap存储刷新中的请求、ReentrantLock控制并发的方式解决问题,最初因使用对象作为Map键导致判定失效,修正后虽可行,但在Reactor响应式环境中使用显式锁并非最优实践,可能引入阻塞风险。
更优解决方案
在响应式编程模型下,推荐利用Reactor的Mono#cache和Mono#switchIfEmpty结合的方式,实现同一刷新令牌的并发请求共享同一个刷新结果,避免重复发起刷新请求:
1. 自定义ReactiveOAuth2AccessTokenResponseClient
@Component public class ConcurrentRefreshTokenResponseClient extends DefaultRefreshTokenTokenResponseClient { private final ConcurrentHashMap<String, Mono<OAuth2AccessTokenResponse>> refreshRequests = new ConcurrentHashMap<>(); @Override public Mono<OAuth2AccessTokenResponse> getTokenResponse(OAuth2RefreshTokenGrantRequest grantRequest) { // 用刷新令牌的value作为缓存键,确保同一刷新令牌的请求共享结果 String refreshTokenValue = grantRequest.getRefreshToken().getTokenValue(); return refreshRequests.computeIfAbsent(refreshTokenValue, key -> super.getTokenResponse(grantRequest) // 刷新完成后移除缓存,无论成功或失败 .doFinally(signalType -> refreshRequests.remove(key)) // 缓存结果,直到刷新完成 .cache() ); } }
2. 配置替换默认客户端
在Gateway的配置类中,将自定义的客户端注入,替换默认的刷新令牌响应客户端:
@Configuration public class OAuth2ClientConfig { @Bean public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ReactiveOAuth2AuthorizedClientService authorizedClientService, ConcurrentRefreshTokenResponseClient refreshTokenResponseClient) { ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() .refreshToken(refresh -> refresh.accessTokenResponseClient(refreshTokenResponseClient)) .clientCredentials() .build(); DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; } }
方案优势
- 完全适配响应式模型:无需显式锁,利用Reactor的缓存机制实现并发请求的结果共享,避免阻塞。
- 自动清理缓存:通过
doFinally在请求完成(成功/失败)后自动移除缓存条目,避免内存泄漏。 - 精准控制:以刷新令牌的值作为键,确保只有针对同一刷新令牌的并发请求才会共享刷新操作,不同刷新令牌的请求互不干扰。
额外建议
- 确保授权服务器的刷新令牌过期时间合理:如果刷新令牌本身过期时间过短,可能导致频繁触发刷新,建议根据业务场景调整。
- 增加刷新失败的重试机制:可以在自定义客户端中添加
retry操作,处理临时网络故障等情况,但需注意避免重试导致的重复请求。
内容的提问来源于stack exchange,提问作者Elena
相关产品推荐
相关产品推荐

