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

Webflux+Spring Security下OAuth2客户端为何频繁触发身份提供商登录?

Spring Security Client Credentials 模式下Token复用异常问题

我们正在监控身份提供商的客户端登录情况,想明确Spring Security在此场景下的预期表现。

配置信息

@Configuration(proxyBeanMethods = false)
class SecurityConfiguration {

    @Bean
    fun authorizedClientManager(
        clientRegistrationRepository: ReactiveClientRegistrationRepository,
        authorizedClientService: ReactiveOAuth2AuthorizedClientService,
    ): ReactiveOAuth2AuthorizedClientManager =
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository,
            authorizedClientService,
        )
            .apply {
                val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider =
                    ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials()
                        .build()

                setAuthorizedClientProvider(authorizedClientProvider)
            }

    @Bean
    fun customizer(
        authorizedClientManager: ReactiveOAuth2AuthorizedClientManager,
    ): WebClientCustomizer = WebClientCustomizer {
        it.filter(ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager))
    }
}

业务逻辑

每15分钟执行一次以下逻辑:

client.doInitialRequest()
    .flatMap { client.doEvenMoreRequests() } // 约10k次请求

当前token有效期为5分钟(实际请求1分钟即可完成),采用client_credentials授权流。目前代码会导致身份提供商产生200至700次客户端登录。按预期,客户端完成初始请求后应复用已有的授权token,但实际并非如此。我们尝试将token有效期延长至1小时,发现首次请求周期后恢复正常,但1小时后又会出现200至700次登录。


问题原因分析

  1. 竞态条件导致重复获取token
    默认的AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager没有针对token获取/刷新的并发控制。当token过期后,大量并发请求会同时检测到token失效,进而各自发起token获取请求,导致身份提供商收到大量登录请求。

    • 当token有效期为5分钟时,每15分钟的请求批次启动时,token已过期,所有请求都会触发新token获取,直接引发大量登录。
    • 当token有效期为1小时时,前几个15分钟的请求批次还在token有效期内,所以能正常复用;但1小时后token过期,下一批次的并发请求又会同时触发token获取,再次出现大量登录。
  2. 多实例部署的额外影响(若存在)
    如果你的服务是多实例部署,默认的InMemoryReactiveOAuth2AuthorizedClientService会让每个实例维护独立的token缓存。当token过期时,所有实例都会独立发起token请求,进一步放大登录次数。


解决方案

1. 添加并发控制,避免同一客户端重复发起token请求

通过包装ReactiveOAuth2AuthorizedClientManager,针对客户端ID添加锁机制,确保同一时间只有一个请求去获取/刷新token:

@Bean
fun authorizedClientManager(
    clientRegistrationRepository: ReactiveClientRegistrationRepository,
    authorizedClientService: ReactiveOAuth2AuthorizedClientService,
): ReactiveOAuth2AuthorizedClientManager {
    val delegate = AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
        clientRegistrationRepository,
        authorizedClientService,
    ).apply {
        val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build()
        setAuthorizedClientProvider(authorizedClientProvider)
    }

    // 基于客户端ID的本地锁,单实例场景适用
    val clientLocks = ConcurrentHashMap<String, Mono<Void>>()

    return ReactiveOAuth2AuthorizedClientManager { context ->
        val clientId = context.clientRegistrationId ?: return@ReactiveOAuth2AuthorizedClientManager Mono.empty()
        
        clientLocks.compute(clientId) { _, existingLock ->
            existingLock ?: delegate.authorize(context)
                .doFinally { clientLocks.remove(clientId) }
                .then(Mono.empty())
        }?.then(delegate.authorize(context))
    }
}

2. 多实例场景:使用分布式token缓存

如果是多实例部署,替换默认的内存实现为分布式的ReactiveOAuth2AuthorizedClientService(比如基于Redis),让所有实例共享token缓存,避免每个实例单独获取token。

3. 调整token有效期与请求周期匹配

将token有效期设置为覆盖多个请求周期(比如30分钟以上),确保每次请求批次启动时token仍在有效期内,从根源减少token刷新的触发次数。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 08:43:25