You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:37:40