在已认证的用户会话中通过OpenFeign调用微服务
我有一个设置,用来使用Keycloak进行用户身份验证。我使用Spring Boot和 Spring Security。我的后端通过Keycloak使用 authorization_code 授权(grant)成功完成认证,并获取了ID Token、Access Token和 Refresh Token。接下来,我搭建一个OpenFeign客户端,从其他微服务获取额外数据,这样当用户向后端发出请求时,后端就可以返回所需的全部数据。
共有两层通信:
- 用户到后端
- 后端到微服务
默认情况下,后端到微服务的通信属于 client_credentials 授权(grant),我已成功设置。OpenFeign使用 AuthorizedClientServiceOAuth2AuthorizedClientManager,它专门针对 client_credentials 授权(grant)进行定制。它使用Keycloak获取访问令牌,当令牌过期时会再次获取。
我发现的问题是,我对Keycloak定义了两套客户端配置,产生两套令牌:
- 用户到后端所需的ID Token、Access Token和 Refresh Token
- 后端到微服务所需的单独的Access Token
是否有可能统一使用同一个Access Token?我可以只有一个配置(用户到后端),让OpenFeign使用其中的Access Token,并自动刷新它吗?我还没有找到一个优雅的实现方式。
解决方案
OpenFeign通过 org.springframework.cloud.openfeign.FeignAutoConfiguration 使用默认配置,在这里有这样一个Bean:
@Bean
OAuth2AuthorizedClientManager feignOAuth2AuthorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService
) {
return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
}
AuthorizedClientServiceOAuth2AuthorizedClientManager 内部仅支持 "client_credentials" 授权(grant)。这很合理,因为OpenFeign主要设计用于机器对机器(M2M)的授权流程。
为了让OpenFeign使用通过 "authorization_code" 获取到的Access Token(就像人类用户认证的情况),需要用我们自己的实现来替换这个Bean。我们可以在Spring @Configuration 中定义如下:
@Bean
OAuth2AuthorizedClientManager feignOAuth2AuthorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository
) {
return new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientRepository);
}
DefaultOAuth2AuthorizedClientManager 支持 "authorization_code"、"refresh_token" 和 "client_credentials"。它将轮换它们,最终获得正确的Access Token,或在它过期时刷新它。