如何使用 client_credentials 从资源服务器访问另一个 oauth2 资源?

Posted

技术标签:

【中文标题】如何使用 client_credentials 从资源服务器访问另一个 oauth2 资源?【英文标题】:How can I use client_credentials to access another oauth2 resource from a resource server? 【发布时间】:2020-02-23 23:07:04 【问题描述】:

我想使用 client_credentials 从响应式资源服务器访问另一个受 oauth2 保护的资源。我使用颁发的令牌访问资源服务器的部分正在工作,但没有使用 webclient 调用其他资源。

使用 UnAuthenticatedServerOAuth2AuthorizedClientRepository 我得到 serverWebExchange must be null,使用 AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository 我得到 principalName must be null

只要我将客户端称为CommandLineRunner,就可以使用https://www.baeldung.com/spring-webclient-oauth2。我在 *** 上找到的其他建议都没有奏效。

我在这里缺少什么?我正在使用 Spring Security 5.2.0 和 Spring Boot 2.2.0。

客户端配置:

@Configuration
public class ClientSecurityConfig 

    // UnAuthenticatedServerOAuth2AuthorizedClientRepository version

    @Bean
    WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) 
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());

        return WebClient.builder()
                .filter(oauth)
                .build();
    

    @Bean
    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider(CustomClientConfig clientConfig) 
        return ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials(clientCredentialsGrantBuilder ->
                                clientCredentialsGrantBuilder.accessTokenResponseClient(new CustomClient(clientConfig))) // Used to send extra parameters to adfs server
                        .build();
    


    // AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository version

    @Bean
    WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) 
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
                .filter(oauth)
                .build();
    


    @Bean
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository, CustomClientConfig clientConfig) 

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
                ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials(clientCredentialsGrantBuilder ->
                                clientCredentialsGrantBuilder.accessTokenResponseClient(new CustomClient(clientConfig))) // Used to send extra parameters to adfs server
                        .build();
        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    

资源服务器配置:

@EnableWebFluxSecurity
class ResourceServerConfig 
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) 
        http
                .authorizeExchange(exchanges ->
                        exchanges
                                .pathMatchers("/actuators/**", "/api/v1").permitAll()
                                .pathMatchers("/api/v1/**").hasAuthority("SCOPE_read")
                                .anyExchange().authenticated()
                )
                .formLogin().disable()
                .httpBasic().disable()
                .oauth2Client(withDefaults())
                .oauth2ResourceServer().jwt();
        return http.build();
    
    @RestController()
    @RequestMapping("/api/v1")
    static class Ctrl 
        final static Logger logger = LoggerFactory.getLogger(Ctrl.class);
        final WebClient webClient;

        public Ctrl(WebClient webClient) 
            this.webClient = webClient;
        

        @RequestMapping("protected")
        Mono<JsonNode> protected(@RequestParam String data) 
            return webClient.post()
                    .uri("https://other-oauth2-protected-resource")
                    .attributes(clientRegistrationId("myclient"))
                    .bodyValue("\"data\": \"" + data + "\"")
                    .retrieve()
                    .bodyToMono(JsonNode.class);
        
    

application.yml:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://adfsserver.com/adfs/services/trust
          jwk-set-uri: https://adfsserver.com/adfs/discovery/keys
      client:
        registration:
          myclient:
            provider: adfs
            client-id: <client-id>
            client-secret: <client-secret>
            authorization-grant-type: client_credentials
            scope: read
        provider:
          adfs:
            token-uri: https://adfsserver.com/adfs/oauth2/token
            jwk-set-uri: https://adfsserver.com/adfs/discovery/keys

【问题讨论】:

似乎缺少 AuthorizedClientServiceOAuth2AuthorizedClientManager 的反应对应物;见github.com/spring-projects/spring-security/issues/7544 请检查我的回答(虽然我知道真的很晚了)。如果您接受并关闭问题,我们将不胜感激。 【参考方案1】:

Spring 项目贡献者最近已将这个问题作为PR 的一部分进行了修复,但不幸的是,Spring 官方文档尚未更新。

普通的servlet方法文档是here 如果您更喜欢选择“反应式”方法,那么配置 webclient 只需要两个 bean:

    AuthorizedClientManager Bean,以及 webClient Bean
  @Bean
  public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
      ReactiveClientRegistrationRepository clientRegistrationRepository,
      ReactiveOAuth2AuthorizedClientService authorizedClientService) 

    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
        ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build();

    AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
        new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
  


  @Bean
  public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) 
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    return WebClient.builder().filter(oauth).build();
  

你可以参考我的Github Gist,里面有所有需要的配置。

【讨论】:

以上是关于如何使用 client_credentials 从资源服务器访问另一个 oauth2 资源?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Spring Security 为 client_credentials 工作流向 Feign 客户端提供 OAuth2 令牌

OneLogin是否支持client_credentials?

带有 client_credentials 的 Spring Oauth2 不验证用户

spring security client_credentials grant_type - 支持刷新令牌

spring-oauth-server入门(1-11)使用授权方式四:client_credentials 模式的客戶端

Oaut2RestTemplate 与 client_credentials 错误(不允许匿名)