Создание маршрута в Spring Cloud Gateway с типом предоставления пароля владельца ресурса OAuth2 - PullRequest
2 голосов
/ 08 января 2020

Как настроить маршрут в Spring Cloud Gateway для использования клиента OAuth2 с authorization-grant-type: password? Другими словами, как добавить заголовок авторизации с токеном в запросах к API? Поскольку я интегрируюсь с устаревшим приложением, я должен использовать пароль типа предоставления.

У меня есть это приложение:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
           .route("route_path", r -> r.path("/**")
                   .filters(f -> f.addRequestHeader("Authorization", "bearer <token>"))
                   .uri("http://localhost:8092/messages"))
           .build();
    }
}

Замена <token> на фактический токен, все просто отлично работает.

Я нашел этот проект, который делает нечто подобное: https://github.com/jgrandja/spring-security-oauth-5-2-migrate. У него есть клиент (messaging-client-password), который используется для настройки WebClient для добавления поддержки OAuth2 для выполнения запросов (т. Е. Путем добавления заголовка авторизации).

Мы не можем сразу использовать этот пример проекта потому что Spring Cloud Gateway реагирует, и способ, которым мы настраиваем вещи, существенно меняется. Я думаю, что решение этой проблемы в основном сводится к преобразованию класса WebClientConfig .

ОБНОВЛЕНИЕ

Я вроде заставляю это работать, но это очень плохая форма.

Во-первых, я обнаружил, как преобразовать WebClientConfig в реактивные:

@Configuration
public class WebClientConfig {

    @Bean
    WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth.setDefaultOAuth2AuthorizedClient(true);
        oauth.setDefaultClientRegistrationId("messaging-client-password");
        return WebClient.builder()
                .filter(oauth)
                .build();
    }

    @Bean
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
                ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .refreshToken()
                        .password()
                        .build();
        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultReactiveOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        // For the `password` grant, the `username` and `password` are supplied via request parameters,
        // so map it to `OAuth2AuthorizationContext.getAttributes()`.
        authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());

        return authorizedClientManager;
    }

    private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
        return authorizeRequest -> {
            Map<String, Object> contextAttributes = Collections.emptyMap();
            ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
            String username = serverWebExchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
            String password = serverWebExchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
            if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
                contextAttributes = new HashMap<>();

                // `PasswordOAuth2AuthorizedClientProvider` requires both attributes
                contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
                contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
            }
            return Mono.just(contextAttributes);
        };
    }
}

С этой конфигурацией мы можем использовать WebClient, чтобы сделать запрос. Это каким-то образом инициализирует клиента OAuth2 после вызова конечной точки:

@GetMapping("/explicit")
public Mono<String[]> explicit() {
    return this.webClient
        .get()
        .uri("http://localhost:8092/messages")
        .attributes(clientRegistrationId("messaging-client-password"))
        .retrieve()
        .bodyToMono(String[].class);
}

Затем, вызывая эту, мы можем получить ссылку на авторизованного клиента:

private OAuth2AuthorizedClient authorizedClient;
@GetMapping("/token")
public String token(@RegisteredOAuth2AuthorizedClient("messaging-client-password") OAuth2AuthorizedClient authorizedClient) {
    this.authorizedClient = authorizedClient;
    return authorizedClient.getAccessToken().getTokenValue();
}

И, наконец, настроив глобальный фильтр, мы можем изменить запрос, включив в него заголовок авторизации:

@Bean
public GlobalFilter customGlobalFilter() {
    return (exchange, chain) -> {
        //adds header to proxied request
        exchange.getRequest().mutate().header("Authorization", authorizedClient.getAccessToken().getTokenType().getValue() + " " + authorizedClient.getAccessToken().getTokenValue()).build();
        return chain.filter(exchange);
    };
}

После выполнения этих трех запросов по порядку мы можем использовать предоставление пароля с Spring Cloud Gateway.

Конечно, этот процесс очень грязный. Что еще нужно сделать:

  1. Получить ссылку для авторизованного клиента внутри фильтра
  2. Инициализировать авторизованного клиента с учетными данными, используя contextAttributesMapper
  3. Запись все это в фильтре, а не в глобальном фильтре. TokenRelayGatewayFilterFactory * Реализация 1052 * может помочь в этом.
...