gpt4 book ai didi

spring-boot - 在测试中替换 OAuth2 WebClient

转载 作者:行者123 更新时间:2023-12-02 19:43:37 34 4
gpt4 key购买 nike

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

我已经能够在 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();
}

}

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

我正在调用的远程服务在我的测试期间被替换为一个 mockserver 假的。

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

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

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

return WebClient.create();
}

}

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

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

No qualifying bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository

生产bean需要的参数类型是什么

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

最佳答案

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

is there an 'cleaner' way to replace the WebClient bean I am using, by one that will call my fake remote service without trying to get a token first ?

我不会替换您测试中的 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 资源,同时模拟授权服务器并响应 token 请求。为此,您可以将 mockserver 配置为 src/test/resources/application.yml 中的 token-uri 或任何您分别用于为您的测试提供属性。


注意事项

直接注入(inject)WebClient

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

@Configuration@TestConfiguration 中的 @Bean 上使用 @Primary 覆盖/优先化 Bean

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

其他 OAuth 2.0 授权类型

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

关于spring-boot - 在测试中替换 OAuth2 WebClient,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59736407/

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com