Spring OAuth2令牌全局缓存需求:寻求官方替代自定义实现方案
问题背景
当使用ServletOAuth2AuthorizedClientExchangeFilterFunction配置WebClient调用下游OAuth2资源服务时,官方提供的OAuth2AuthorizedClientService实现(InMemoryOAuth2AuthorizedClientService和JdbcOAuth2AuthorizedClientService)均以clientRegistrationId + principalName作为缓存键,导致令牌按调用API的客户端主体隔离存储。
这种机制在多客户端调用的API场景下会引发问题:API自身使用客户端凭证模式调用下游服务,但不同客户端请求API时,即使已有有效令牌,仍会重复请求新令牌,触发下游资源服务的限流规则,尤其是当API有大量不同客户端调用时,问题更为突出。
开箱即用的解决方案:固定全局主体
无需自定义OAuth2AuthorizedClientService,可以通过扩展AuthorizedClientServiceOAuth2AuthorizedClientManager,将所有令牌请求的主体替换为固定的全局标识,让官方实现自动全局缓存令牌。
修改后的WebClient配置如下:
@Bean protected WebClient webClient(WebClient.Builder builder, ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) { // 定义全局共享的固定主体 Authentication GLOBAL_SERVICE_PRINCIPAL = new AnonymousAuthenticationToken( "global-service", "api-internal-service", AuthorityUtils.createAuthorityList("ROLE_INTERNAL_SERVICE") ); // 扩展AuthorizedClientManager,替换请求主体为全局主体 AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService) { @Override public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) { // 构建使用全局主体的授权请求 OAuth2AuthorizeRequest globalAuthRequest = OAuth2AuthorizeRequest.with(authorizeRequest) .principal(GLOBAL_SERVICE_PRINCIPAL) .build(); return super.authorize(globalAuthRequest); } }; // 配置令牌失败处理器,同样使用全局主体清理缓存 OAuth2AuthorizationFailureHandler authFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler( (clientId, principal, attrs) -> authorizedClientService.removeAuthorizedClient(clientId, GLOBAL_SERVICE_PRINCIPAL.getName()) ); ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Filter.setDefaultOAuth2AuthorizedClient(true); oauth2Filter.setDefaultClientRegistrationId("oauth2"); oauth2Filter.setAuthorizationFailureHandler(authFailureHandler); return builder.apply(oauth2Filter.oauth2Configuration()).build(); }
方案说明
- 全局主体替换:重写
AuthorizedClientManager的authorize方法,将所有传入的请求主体替换为固定的全局服务主体,这样官方的OAuth2AuthorizedClientService会使用同一个缓存键存储令牌,实现全局共享。 - 失败处理适配:令牌失效时,清理缓存也使用全局主体,确保缓存一致性。
- 贴合Spring设计:基于现有组件扩展,无需封装整个
OAuth2AuthorizedClientService,更符合Spring Security的扩展规范。
与自定义Service方案的对比
你自定义的IgnorePrincialOAuth2AuthorizedClientService通过统一映射到匿名主体实现全局缓存,本质逻辑与上述方案一致,但通过AuthorizedClientManager扩展的方式更轻量,无需额外封装Service层,直接在授权流程中拦截替换主体,代码侵入性更低。
适用场景
该方案仅适用于客户端凭证模式(Client Credentials Grant),因为这种模式下令牌是基于客户端身份颁发,与调用API的用户/客户端主体无关,全局缓存令牌是合理的设计。
内容的提问来源于stack exchange,提问作者Mike Cordeiro

