Java Spring Security 5在应用程序运行程序中调用OAuth2安全API会导致IllegalArgumentException

Java Spring Security 5在应用程序运行程序中调用OAuth2安全API会导致IllegalArgumentException,java,spring-boot,spring-security,spring-webflux,Java,Spring Boot,Spring Security,Spring Webflux,给定以下代码,是否可以在应用程序运行程序中调用客户端凭据安全API @Bean public ApplicationRunner test( WebClient.Builder builder, ClientRegistrationRepository clientRegistrationRepo, OAuth2AuthorizedClientRepository authorizedClient) { return args -> {

给定以下代码,是否可以在应用程序运行程序中调用客户端凭据安全API

@Bean
public ApplicationRunner test(
    WebClient.Builder builder,
    ClientRegistrationRepository clientRegistrationRepo, 
    OAuth2AuthorizedClientRepository authorizedClient) {
        return args -> {
            try {
                var oauth2 =
                    new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                        clientRegistrationRepo,
                        authorizedClient);
                oauth2.setDefaultClientRegistrationId("test");
                var response = builder
                    .apply(oauth2.oauth2Configuration())
                    .build()
                    .get()
                    .uri("test")
                    .retrieve()
                    .bodyToMono(String.class)
                    .block();
                log.info("Response - {}", response);
            } catch (Exception e) {
                log.error("Failed to call test.", e);
            }
        };
    }
由于以下原因,代码失败:

java.lang.IllegalArgumentException: request cannot be null
满栈

java.lang.IllegalArgumentException: request cannot be null
    at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.loadAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:47) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.populateDefaultOAuth2AuthorizedClient(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:364) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$null$2(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:209) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:234) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:153) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
失败的方法看起来像

public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(
    String clientRegistrationId,  Authentication principal, HttpServletRequest request){

    Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
    Assert.notNull(request, "request cannot be null");
    return (OAuth2AuthorizedClient)this
        .getAuthorizedClients(request)
        .get(clientRegistrationId);
}
public T加载授权客户端(
字符串clientRegistrationId,身份验证主体,HttpServletRequest){
Assert.hasText(clientRegistrationId,“clientRegistrationId不能为空”);
Assert.notNull(请求,“请求不能为null”);
返回(OAuth2AuthorizedClient)此
.GetAuthorizedClient(请求)
.get(clientRegistrationId);
}
这是有意义的,因为没有可供其使用的
HttpServletRequest
,它在应用程序启动时被调用

除了制作我自己的no op
OAuth2AuthorizedClient存储库之外,还有其他解决方法吗

//编辑

这不是一个完全反应的堆栈。它是一个SpringWeb堆栈,其中使用WebClient

我很清楚
ServerOAuth2AuthorizedClientExchangeFilterFunction
适用于完全反应式堆栈,需要
ReactiveClientRegistrationRepository
ReactiveOauth2AuthorizedClient
,但由于它位于构建在Servlet堆栈之上的应用程序中而不可用,而不是反应式的

我最后问了Spring安全团队这个问题

不幸的是,如果您在servlet堆栈上,并在后台线程中使用纯Spring Security 5 API调用OAuth2资源,那么就没有可用的
OAuth2AuthorizedClientRepository

实际上有两种选择

  • 实现一个完全没有操作的版本
  • var oauth2=new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,
    新的OAuth2AuthorizedClientRepository(){
    @凌驾
    公共T loadAuthorizedClient(字符串s,
    身份验证,HttpServletRequest(HttpServletRequest){
    返回null;
    }
    @凌驾
    public void saveAuthorizedClient(OAuth2AuthorizedClient),
    身份验证,HttpServletRequest HttpServletRequest,
    HttpServletResponse(HttpServletResponse){
    }
    @凌驾
    public void removeAuthorizedClient(字符串s、身份验证、,
    HttpServletRequest HttpServletRequest,HttpServletResponse HttpServletResponse){
    }
    });
    
  • 实现未经身份验证的服务器OAuth2AuthorizedClientRepository的Servlet版本。它有一些基本的功能,而不是一个纯粹的禁止操作
  • 提供关于GitHub问题的反馈可能有助于Spring安全团队评估是否接受PR并维护未经身份验证的服务器OAuth2AuthorizedClient position的Servlet版本


    我联系了Spring安全团队,在此基础上,Spring安全5.2中将添加一个Servlet版本的
    ServerOAuth2AuthorizedClientExchangeFilterFunction
    ,用于非http线程

    由于我也偶然发现了这个问题,我将详细介绍一下最新的答案,以便其他人更容易找到:

    OP提交的问题导致实现了能够

    在HttpServletRequest上下文之外操作,例如在计划/后台线程和/或服务层中

    ()

    所述实现,即
    AuthorizedClientServiceOAuth2AuthorizedClientManager
    ,被传递给
    ServletOAuth2AuthorizedClientChangeFilterFunction
    以替换默认的实现

    在我的示例中,这看起来像这样:

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService clientService)
    {
    
        OAuth2AuthorizedClientProvider authorizedClientProvider = 
            OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();
    
        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = 
            new AuthorizedClientServiceOAuth2AuthorizedClientManager(
                clientRegistrationRepository, clientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    
        return authorizedClientManager;
    }
    
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager)
    {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                authorizedClientManager);
        oauth2.setDefaultClientRegistrationId("keycloak");
        return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
    }
    

    您的用例是什么?你能提供更多的上下文吗?“servletRequest不能为null”似乎是不在Servlet上下文中的结果。在Controller或RestController中使用WebClient可以正常工作,但从组件或服务中您将得到异常。天蝎座下面的答案很有用,这对我来说很有用。因此,每当您想使用@Scheduled on service(它使用OAuth2调用另一个rest服务)时,您都需要定制OAuth2AuthorizedClient Manager。它看起来很像SpringSecurityOAuth2迁移示例,但有两个重要的区别。注意第一个bean中的
    OAuth2AuthorizedClientService客户端服务
    ,以及第二个bean中创建的
    authorizedclientservice oauth2authorizedclientmanager authorizedClientManager
    。谢谢天蝎座这解决了我逾期未交的问题!这是什么spring安全版本?没有这样的类OAuth2AuthorizedClient Manager,我使用的是5.1.5我使用的是Spring Security Starter 2.2.2,它使用的是Spring Security的5.2.1。您节省了我的时间,先生,感谢您提供解决方案:)
    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService clientService)
    {
    
        OAuth2AuthorizedClientProvider authorizedClientProvider = 
            OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();
    
        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = 
            new AuthorizedClientServiceOAuth2AuthorizedClientManager(
                clientRegistrationRepository, clientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    
        return authorizedClientManager;
    }
    
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager)
    {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                authorizedClientManager);
        oauth2.setDefaultClientRegistrationId("keycloak");
        return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
    }