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

Spring Cloud Gateway配置OIDC/OAuth2刷新令牌(max-reuse=0)并发问题咨询

解决Spring Cloud Gateway授权码流程刷新令牌竞态问题

可行解决方案

1. 依赖Spring Security授权客户端服务的内置同步控制

Spring Security的OAuth2AuthorizedClientService(内存版InMemoryOAuth2AuthorizedClientService或JDBC版JdbcOAuth2AuthorizedClientService)在更新授权客户端信息时,已针对同一用户+客户端ID做了同步处理。只要网关通过ServerOAuth2AuthorizedClientExchangeFilterFunction处理令牌刷新,它会自动借助这个服务的锁机制,让同一用户的并发刷新请求串行执行,避免同时用旧刷新令牌发起请求。

2. 配置令牌提前刷新策略

在ReactiveOAuth2AuthorizedClientManager配置中设置提前刷新阈值,比如当访问令牌剩余有效期不足10%时主动刷新,而非等令牌过期后才触发。这能大幅减少并发请求同时触发刷新的概率,多数请求会使用未过期的令牌,降低竞态场景出现的可能。

示例配置代码:

@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
        ReactiveClientRegistrationRepository clientRegistrationRepository,
        ReactiveOAuth2AuthorizedClientService authorizedClientService) {

    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
            ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                    .authorizationCode()
                    .refreshToken(refreshToken -> refreshToken
                            .clockSkew(Duration.ofSeconds(60))
                            .refreshTokenValiditySeconds(3600))
                    .build();

    DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultReactiveOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    // 设置提前刷新逻辑:当令牌剩余有效期小于总有效期10%时触发刷新
    authorizedClientManager.setAuthorizationSuccessHandler((authorizedClient, principal, exchange) -> {
        Instant expiresAt = authorizedClient.getAccessToken().getExpiresAt();
        Duration totalValidity = Duration.between(authorizedClient.getAccessToken().getIssuedAt(), expiresAt);
        Duration remainingValidity = Duration.between(Instant.now(), expiresAt);
        if (remainingValidity.compareTo(totalValidity.dividedBy(10)) < 0) {
            // 触发提前刷新
            return authorizedClientProvider.authorize(authorizedClient, principal, exchange);
        }
        return Mono.empty();
    });

    return authorizedClientManager;
}

3. 网关层添加轻量会话锁

如果上述方案仍无法满足需求,可基于用户标识(如从请求的OAuth2认证信息中获取用户ID)在网关过滤器中实现本地锁(单实例部署)或分布式锁(集群部署),确保同一用户的刷新请求串行执行。

本地锁示例代码:

@Component
public class RefreshTokenLockFilter implements GatewayFilter {

    private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return exchange.getPrincipal()
                .map(principal -> principal.getName())
                .flatMap(userId -> {
                    ReentrantLock lock = lockMap.computeIfAbsent(userId, k -> new ReentrantLock());
                    try {
                        lock.lock();
                        return chain.filter(exchange);
                    } finally {
                        lock.unlock();
                        lockMap.remove(userId);
                    }
                })
                .switchIfEmpty(chain.filter(exchange));
    }
}

将该过滤器添加到需要令牌验证的路由过滤链中即可生效。

总结

优先采用Spring Security内置的同步控制机制,这是最省心可靠的方案;其次通过提前刷新策略降低竞态触发概率;最后再考虑自定义锁实现,无需从头搭建复杂的会话锁体系。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 23:42:31