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
相关产品推荐
相关产品推荐

