【问题标题】:Spring Security 5 Calling OAuth2 Secured API in Application Runner results in IllegalArgumentExceptionSpring Security 5 在 Application Runner 中调用 OAuth2 Secured API 导致 IllegalArgumentException
【发布时间】:2019-08-13 23:44:50
【问题描述】:

鉴于以下代码,是否可以在应用程序运行程序中调用客户端凭据保护 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);
}

这是有道理的,因为它没有 HttpServletRequest 可供使用,它在应用程序启动时被调用。

除了让我自己无操作OAuth2AuthorizedClientRepository之外,还有其他解决方法吗?

//编辑,

这不是一个完全反应堆。它是一个 Spring Web 堆栈,其中使用了 WebClient。

我很清楚 ServerOAuth2AuthorizedClientExchangeFilterFunction 适用于完全反应式堆栈,并且需要 ReactiveClientRegistrationRepositoryReactiveOauth2AuthorizedClient 不可用,因为这是在 Servlet 堆栈之上构建的应用程序中,而不是反应式。

【问题讨论】:

  • 您的用例是什么?你能提供更多上下文吗
  • “servletRequest 不能为空”似乎是由于不在 Servlet 上下文中造成的。在 Controller 或 RestController 中使用 WebClient 可以正常工作,但是从 Component 或 Service 你会得到异常。天蝎座的回答效果很好。

标签: java spring-boot spring-security spring-webflux


【解决方案1】:

由于我也偶然发现了这个问题,我将详细说明Darren Forsythe's 更新的答案,以便其他人更容易找到:

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

在 HttpServletRequest 上下文之外运行,例如在调度/后台线程和/或服务层中

(from the official docs)

上述实现 AuthorizedClientServiceOAuth2AuthorizedClientManager 被传递给 ServletOAuth2AuthorizedClientExchangeFilterFunction 以替换默认实现。

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

@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();
}

【讨论】:

  • 这对我有用。因此,每当您想在使用 OAuth2 调用另一个休息服务的服务上使用 @Scheduled 时,您需要自定义 OAuth2AuthorizedClientManager。
  • 这个解决方案起初对我来说有点难以发现。它看起来很像 Spring Security Oauth2 Migration 示例,但有两个重要的区别。注意第一个 bean 中的 OAuth2AuthorizedClientService clientService 和第二个 bean 中 AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager 的创建。谢谢 Scorpio590 这解决了我过期的问题!
  • 这是什么 Spring Security 版本?没有这样的类 OAuth2AuthorizedClientManager ,我使用的是 5.1.5
  • 我正在使用 Spring Security Starter 2.2.2,它使用 Spring Security 5.2.1
  • 您节省了我的时间先生,感谢您提供解决方案 :)
【解决方案2】:

我最终向 Spring Security 团队提出了这个问题,

https://github.com/spring-projects/spring-security/issues/6683

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

实际上有两种选择,

  1. 实现完全无操作版本,
 var oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,
                        new OAuth2AuthorizedClientRepository() {
                            @Override
                            public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String s,
                                    Authentication authentication, HttpServletRequest httpServletRequest) {
                                return null;
                            }

                            @Override
                            public void saveAuthorizedClient(OAuth2AuthorizedClient oAuth2AuthorizedClient,
                                    Authentication authentication, HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse) {

                            }

                            @Override
                            public void removeAuthorizedClient(String s, Authentication authentication,
                                    HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {

                            }
                        });
  1. 实现UnAuthenticatedServerOAuth2AuthorizedClientRepository 的Servlet 版本。 UnAuthenticatedServerOAuth2AuthorizedClientRepository GitHub Source 具有一些基本功能,而不是纯无操作。

提供有关 GitHub 问题的反馈可能有助于 Spring Security 团队评估是否接受 PR 并维护 UnAuthenticatedServerOAuth2AuthorizedClientRepository 的 Servlet 版本

我联系了 Spring Security 团队 Spring Security Issue 6683,在此之后,Spring Security 5.2 中将添加一个 ServerOAuth2AuthorizedClientExchangeFilterFunction 的 Servlet 版本,用于非 http 线程。

【讨论】:

    猜你喜欢
    • 2019-03-23
    • 2020-11-30
    • 2012-01-16
    • 2017-04-04
    • 2019-09-25
    • 2020-07-07
    • 2012-01-12
    • 2016-05-25
    • 2015-12-10
    相关资源
    最近更新 更多