Клиент Spring Security OAuth для WebClient с типом предоставления пароля не запрашивает новый токен - PullRequest
0 голосов
/ 10 июля 2020

У меня есть веб-приложение Spring, в котором настроен сервер ресурсов oauth2 для конечных точек API и совершенно другой клиент oauth2 для вызовов REST, которые он выполняет. Клиент oauth2 должен иметь тип предоставления пароля. Имя пользователя и пароль фиксированные (не поступают из HTTP-запроса). Моя проблема в том, что через 30 минут как токен доступа, так и токен refre sh истекают, поэтому нет возможности сделать refre sh. Я ожидал, что Spring Security просто запросит новый токен доступа, но нет. Он вызывает конечную точку REST с истекшим сроком действия и возвращает 403. Вот что у меня есть:

application.yml:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://our.idp.keycloak.host/auth/realms/firstrealm
      client:
        registration:
          my-client-authorization:
            client-id: my_client
            client-secret: ${CLIENT_SECRET}
            authorization-grant-type: password
            scope: openid, profile
        provider:
          my-client-authorization:
            token-uri: https://our.idp.keycloak.host/auth/realms/secondrealm/protocol/openid-connect/token

MyClientConfig. java:

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Map;

@Configuration
@RequiredArgsConstructor
public class MyClientConfig {
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId("my-client-authorization");
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .baseUrl("https://the.api.host.to.call")
                .build();
    }

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository
    ) {
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .password()
                .build();

        DefaultOAuth2AuthorizedClientManager result = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository,
                authorizedClientRepository
        );

        result.setAuthorizedClientProvider(authorizedClientProvider);
        result.setContextAttributesMapper(oAuth2AuthorizeRequest -> Map.of(
                OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "user",
                OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password"
        ));

        return result;
    }
}

Сам вызов API:

private <T> T callApi(Function<UriBuilder, URI> uriFunction, Class<T> resultType) {
    return this.webClient
            .get()
            .uri(uriFunction)
            .retrieve()
            .bodyToMono(resultType)
            .block();
}

Он работает при первом вызове. Но через 30 минут токен сдох, и я понятия не имею, как получить новый. Если я переключу это на тип предоставления client_credentials, он работает, он автоматически получает новый токен, когда это необходимо. Но по какой-то причине я не могу сделать то же самое для типа предоставления пароля.

Изменить: Так что мне удалось исправить это благодаря этому: https://github.com/spring-projects/spring-security/issues/8831. Когда я настраиваю WebClient также для refreshToken, он вылетает, когда истекает срок действия токена refre sh. Но когда вы снова выполните запрос после этого, он получит новый токен. Поэтому мне пришлось обернуть вызов API в try catch, и если это ошибка, которая мне небезразлична, я снова вызываю API. Это не очень элегантное решение, но оно работает.

1 Ответ

0 голосов
/ 10 июля 2020

Здесь четко указано здесь , что Spring автоматически обновляет токен доступа по истечении срока его действия.

Я думаю, вам нужны «реактивные» эквивалентные классы тех, которые вы использовали. Попробуйте этот фрагмент.

@Configuration
@RequiredArgsConstructor
public class MyClientConfig {
  @Bean
  WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultClientRegistrationId("my-client-authorization");
    return WebClient.builder()
        .filter(oauth2Client)
        .baseUrl("https://the.api.host.to.call")
        .build();
  }

  @Bean
  public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
      ReactiveClientRegistrationRepository clientRegistrationRepository,
      ReactiveOAuth2AuthorizedClientService authorizedClientService
  ) {
    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
        .password()
        .build();

    AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager result = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
        clientRegistrationRepository,
        authorizedClientService
    );

    result.setAuthorizedClientProvider(authorizedClientProvider);
    result.setContextAttributesMapper(oAuth2AuthorizeRequest -> Mono.just(Map.of(
        OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "user",
        OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password"
    )));

    return result;
  }
}
...