【问题标题】:replacing an OAuth2 WebClient in a test在测试中替换 OAuth2 WebClient
【发布时间】:2020-01-14 15:01:02
【问题描述】:

我有一个小的 Spring Boot 2.2 批处理写入 OAuth2 REST API。

我已经能够在https://medium.com/@asce4s/oauth2-with-spring-webclient-761d16f89cdd 之后配置WebClient 并且它按预期工作。

    @Configuration
    public class MyRemoteServiceClientOauth2Config {

        @Bean("myRemoteService")
        WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
            ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
                    new ServerOAuth2AuthorizedClientExchangeFilterFunction(
                            clientRegistrations,
                            new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
            oauth.setDefaultClientRegistrationId("myRemoteService");

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

    }

但是,现在我想为我的批次编写一个集成测试,并且我想避免使用“真实”授权服务器来获取令牌:如果外部服务器是,我不希望我的测试失败下。我希望我的测试是“自主的”。

在我的测试期间,我调用的远程服务被 mockserver 假冒的服务所取代。

在这种情况下,最佳做法是什么?

  • 对我有用的是仅在使用@Profile("!test") 的测试之外启用上述配置,并使用@ActiveProfiles("test") 运行我的测试。我还在我的测试中导入了一个测试特定的配置:
    @Configuration
    @Profile("test")
    public class BatchTestConfiguration {

        @Bean("myRemoteService")
        public WebClient webClientForTest() {

            return WebClient.create();
        }

    }

但我觉得必须在我的生产配置中添加@Profile("!test") 不是很好..

  • 是否有一种“更清洁”的方法来替换我正在使用的 WebClient bean,它会调用我的假远程服务而不首先尝试获取令牌?我试图在我的 webClientForTest bean 上放置一个@Primary,但它不起作用:生产 bean 仍然被启用并且我得到一个异常:

没有 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository 类型的合格 bean

生产bean需要的参数类型

  • 作为测试的一部分,我是否需要启动一个假授权服务器并配置 WebClient 以从中获取一个虚拟令牌?是否有尽可能开箱即用的库?

【问题讨论】:

  • 你找到解决办法了吗?
  • 解决了吗?
  • 不。我仍在使用“假授权服务器作为测试的一部分,并配置 WebClient 以从中获取虚拟令牌”

标签: spring-boot spring-webclient


【解决方案1】:

我和你的情况一样,找到了解决办法。首先,为了看到它的实际效果,我创建了一个repository with a showcase implementation,包含下面解释的所有内容。

是否有一种“更清洁”的方法来替换我正在使用的 WebClient bean,它会调用我的假远程服务而不首先尝试获取令牌?

我不会在您的测试中替换 WebClient bean,而是用模拟替换 ReactiveOAuth2AuthorizedClientManager bean。 为此,您必须稍微修改您的MyRemoteServiceClientOauth2Config。而不是使用现在已弃用的方法和UnAuthenticatedServerOAuth2AuthorizedClientRepository,而是以这种方式配置它(这也更符合documented configuration on the Servlet-Stack):

@Configuration
public class MyRemoteServiceClientOauth2Config {

    @Bean
    public WebClient webClient(ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2ClientCredentialsFilter =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(reactiveOAuth2AuthorizedClientManager);
        oauth2ClientCredentialsFilter.setDefaultClientRegistrationId("myRemoteService");

        return WebClient.builder()
                .filter(oauth2ClientCredentialsFilter)
                .build();
    }

    @Bean
    public ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager(ReactiveClientRegistrationRepository clientRegistrations,
                                                                                       ReactiveOAuth2AuthorizedClientService authorizedClients) {
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, authorizedClients);

        authorizedClientManager.setAuthorizedClientProvider(
                new ClientCredentialsReactiveOAuth2AuthorizedClientProvider());

        return authorizedClientManager;
    }
}

然后您可以创建一个 ReactiveOAuth2AuthorizedClientManager 的模拟,它始终返回 MonoOAuth2AuthorizedClient,如下所示:

@TestComponent
@Primary
public class AlwaysAuthorizedOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager {

    @Value("${spring.security.oauth2.client.registration.myRemoteService.client-id}")
    String clientId;

    @Value("${spring.security.oauth2.client.registration.myRemoteService.client-secret}")
    String clientSecret;

    @Value("${spring.security.oauth2.client.provider.some-keycloak.token-uri}")
    String tokenUri;

    /**
     * {@inheritDoc}
     *
     * @return
     */
    @Override
    public Mono<OAuth2AuthorizedClient> authorize(final OAuth2AuthorizeRequest authorizeRequest) {
        return Mono.just(
                new OAuth2AuthorizedClient(
                        ClientRegistration
                                .withRegistrationId("myRemoteService")
                                .clientId(clientId)
                                .clientSecret(clientSecret)
                                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                                .tokenUri(tokenUri)
                                .build(),
                        "some-keycloak",
                        new OAuth2AccessToken(TokenType.BEARER,
                                "c29tZS10b2tlbg==",
                                Instant.now().minus(Duration.ofMinutes(1)),
                                Instant.now().plus(Duration.ofMinutes(4)))));
    }
}

最后@Import 在你的测试中:

@SpringBootTest
@Import(AlwaysAuthorizedOAuth2AuthorizedClientManager.class)
class YourIntegrationTestClass {

  // here is your test code

}

对应的src/test/resources/application.yml如下:

spring:
  security:
    oauth2:
      client:
        registration:
          myRemoteService:
            authorization-grant-type: client_credentials
            client-id: test-client
            client-secret: 6b30087f-65e2-4d89-a69e-08cb3c9f34d2 # bogus
            provider: some-keycloak
        provider:
          some-keycloak:
            token-uri: https://some.bogus/token/uri

另类

您也可以使用相同的mockserver 来模拟您的 REST 资源,也可以模拟授权服务器并响应令牌请求。为此,您可以将mockserver 配置为src/test/resources/application.yml 中的token-uri 或您用于分别为测试提供属性的任何内容。


注意事项

直接注入WebClient

在 bean 中提供WebClient 的推荐方法是注入WebClient.Builder,Spring Boot 会得到preconfigured。这也保证了测试中的WebClient 的配置与生产中的完全相同。您可以将WebClientCustomizer bean 声明为configure this builder further。这是在我上面提到的展示存储库中实现的方式。

@Bean@Primary@TestConfiguration 中使用 @Primary 覆盖/优先级

我也尝试过,发现它并不总是按预期的方式工作,可能是因为 Spring 加载和实例化 bean 定义的顺序。例如,ReactiveOAuth2AuthorizedClientManager 模拟仅在 @TestConfiguration 是测试类中的 static nested 类时使用,但如果它是 @Imported 则不使用。在接口上使用static nested @TestConfiguration 并使用测试类实现它也不起作用。因此,为了避免将 static nested 类放在我需要的每个集成测试中,我宁愿选择这里介绍的 @TestComponent 方法。

其他 OAuth 2.0 授权类型

我只针对Client Credentials 授予类型测试了我的方法,但我认为它也可以针对其他授予类型进行调整或扩展。

【讨论】:

  • 嘿,你能解释一下替代方案吗?非常感谢
  • 嗨@Denson,您可以使用mockserver 以虚假的OAuth 令牌回复来响应令牌请求。例如,请参阅我在“测试”部分的答案中的TheRestClientImplIT 的实现:stackoverflow.com/a/67045987/6171138
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
  • 2016-10-09
  • 2015-10-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多