如何用Spring Security OAuth2 Client实现服务间OAuth2认证
使用Spring Security OAuth2 Client实现服务到服务的OAuth2认证
核心思路
服务自身调用下游OAuth2服务,应采用客户端凭证模式(Client Credentials Grant)——这种模式无需用户参与,完全是服务间的身份认证,不会和上游传入的用户SecurityContext产生冲突。
步骤1:修正配置文件
你的配置中包含了redirectUri,这是授权码模式的参数,客户端凭证模式不需要。授权服务器收到多余参数可能返回unauthorized_client错误,修改配置如下:
downstream: location: "https://downstream.domain.com" oauth2: registration-id: "downstream-client" client-id: "你的客户端ID" client-secret: "你的客户端密钥" # 若未使用加密直接配置值,加密场景保留key/value结构 tokenUri: "https://授权服务器地址/oauth2/token" scope: "quote.read" # 下游要求的权限范围 # 移除redirectUri、authorizationUri,客户端凭证模式不需要这两个参数
步骤2:配置OAuth2客户端管理器
创建独立的AuthorizedClientManager,用于管理服务自身的OAuth2凭证,不依赖请求上下文的用户:
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.service.InMemoryOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.service.OAuth2AuthorizedClientService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OAuth2ClientConfig { @Bean public OAuth2AuthorizedClientManager downstreamAuthorizedClientManager( ClientRegistrationRepository clientRegistrationRepository) { // 用内存存储服务自身的授权客户端,不关联用户 OAuth2AuthorizedClientService authorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); // 构建客户端凭证模式的授权提供器 OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .clientCredentials() .build(); AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; } }
步骤3:构建带OAuth2认证的HttpInterface
通过WebClient实现你的DownstreamClient,绑定上面的AuthorizedClientManager,确保自动获取/刷新Token且不干扰上游SecurityContext:
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.support.WebClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; @Configuration public class DownstreamClientConfig { @Bean public DownstreamClient downstreamClient(OAuth2AuthorizedClientManager downstreamAuthorizedClientManager) { // 创建专属OAuth2过滤函数,使用服务自身的授权管理器 ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(downstreamAuthorizedClientManager); // 指定使用的客户端注册ID oauth2Filter.setDefaultClientRegistrationId("downstream-client"); // 构建WebClient WebClient webClient = WebClient.builder() .baseUrl("https://downstream.domain.com") .filter(oauth2Filter) .build(); // 生成HttpInterface代理 HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)) .build(); return factory.createClient(DownstreamClient.class); } }
关键注意事项
- 避免干扰上游SecurityContext:这里使用的
AuthorizedClientServiceOAuth2AuthorizedClientManager是服务级别的存储,不绑定当前请求的用户,上游传入的用户认证信息不会被修改或覆盖。 - @RegisteredOAuth2AuthorizedClient无效原因:该注解用于绑定当前请求用户对应的授权客户端,服务自身认证无需用户,因此不适用。
- unauthorized_client错误排查:除了多余的
redirectUri,还要检查:- client-id和client-secret是否与授权服务器配置一致
- tokenUri是否正确(客户端凭证模式仅需该地址)
- 授权服务器是否允许该客户端使用客户端凭证模式
- scope是否为授权服务器允许该客户端访问的范围
替代方案:RestClient实现
若偏好使用RestClient,配置逻辑类似:
import org.springframework.web.client.RestClient; import org.springframework.web.client.support.RestClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.web.client.OAuth2AuthorizedClientExchangeFilterFunction; @Configuration public class DownstreamClientConfig { @Bean public DownstreamClient downstreamClient(OAuth2AuthorizedClientManager downstreamAuthorizedClientManager) { OAuth2AuthorizedClientExchangeFilterFunction oauth2Filter = new OAuth2AuthorizedClientExchangeFilterFunction(downstreamAuthorizedClientManager); oauth2Filter.setDefaultClientRegistrationId("downstream-client"); RestClient restClient = RestClient.builder() .baseUrl("https://downstream.domain.com") .filter(oauth2Filter) .build(); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(RestClientAdapter.for(restClient)) .build(); return factory.createClient(DownstreamClient.class); } }
内容的提问来源于stack exchange,提问作者user4932655
相关产品推荐
相关产品推荐

