我和你的情况一样,找到了解决办法。首先,为了看到它的实际效果,我创建了一个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 的模拟,它始终返回 Mono 的 OAuth2AuthorizedClient,如下所示:
@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 授予类型测试了我的方法,但我认为它也可以针对其他授予类型进行调整或扩展。