замена OAuth2 WebClient в тесте

У меня есть небольшой пакет Spring Boot 2.2, который записывает в OAuth2 REST API.

Мне удалось настроить WebClient следующий https://medium.com/@asce4s/oauth2-with-spring-webclient-761d16f89cdd, и он работает должным образом.

    @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 на тот, который будет вызывать мою поддельную удаленную службу, не пытаясь сначала получить токен? Я попытался добавить @Primary в свой bean-компонент webClientForTest, но это не сработало: производственный bean-компонент все еще включен, и я получаю исключение:

Нет подходящего bean-компонента типа org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository

который является типом параметра, который нужен производственному компоненту

  • мне нужно запустить поддельный сервер авторизации как часть моего теста и настроить WebClient для получения от него фиктивного токена? Есть ли библиотека, которая предоставляет это максимально готово?

person Vincent F    schedule 14.01.2020    source источник
comment
Вы смогли найти решение?   -  person Rocky4Ever    schedule 07.08.2020
comment
Это было решено?   -  person comeOnGetIt    schedule 03.02.2021
comment
Неа. Я все еще использую поддельный сервер авторизации в рамках своего теста и настраиваю WebClient для получения от него фиктивного токена.   -  person Vincent F    schedule 05.02.2021


Ответы (1)


Я был в той же ситуации, что и вы, и нашел решение. Прежде всего, чтобы увидеть это в действии, я создал репозиторий с демонстрационной реализацией всего, что объясняется ниже.

есть ли более «чистый» способ заменить используемый мной компонент WebClient на тот, который будет вызывать мою поддельную удаленную службу, не пытаясь сначала получить токен?

Я бы не стал заменять bean-компонент WebClient в вашем тесте, а скорее заменил бы _ 2_ bean с имитацией. Чтобы это сработало, вам нужно немного изменить свой MyRemoteServiceClientOauth2Config. Вместо использования устаревшего подхода с _ 4_, вы настраиваете его таким образом (это также больше соответствует задокументированная конфигурация в стеке сервлетов):

@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 _ 8_ вот так:

@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 как token-uri в src/test/resources/application.yml или что-то еще, что вы используете для предоставления свойств вашему тесту соответственно.


Примечания

Прямое введение WebClient

Рекомендуемый способ добавить WebClient в ваши beans - ввести WebClient.Builder, который получит предварительно настроен с помощью Spring Boot. Это также гарантирует, что WebClient в вашем тесте настроен точно так же, как и в производственной среде. Вы можете объявить WebClientCustomizer beans в настройте этот конструктор дальше. Именно так это реализовано в моем репозитории витрин, упомянутом выше.

Переопределение / приоритезация бина с @Primary на @Bean внутри @Configuration или @TestConfiguration

Я тоже пробовал это и обнаружил, что это не всегда работает так, как можно было бы ожидать, вероятно, из-за порядка, в котором Spring загружает и создает определения bean-компонентов. Например, макет ReactiveOAuth2AuthorizedClientManager используется только в том случае, если @TestConfiguration является классом static nested внутри тестового класса, но не если он @Imported. Наличие static nested @TestConfiguration на интерфейсе и реализация этого с помощью тестового класса также не работает. Итак, чтобы не использовать этот класс static nested в каждом интеграционном тесте, в котором он мне нужен, я предпочитаю подход @TestComponent, представленный здесь.

Другие типы грантов OAuth 2.0

Я тестировал свой подход только для Client Credentials типа гранта, но я думаю, что он также может быть адаптирован или расширен для других типов грантов.

person clocken    schedule 10.04.2021