Saltar al contenido

Reemplazo de Spring Security 5 para OAuth2RestTemplate

Después de de esta prolongada compilación de información dimos con la solución esta interrogante que tienen ciertos usuarios. Te dejamos la solución y nuestro deseo es servirte de mucha apoyo.

Solución:

Las características del cliente OAuth 2.0 de Spring Security 5.2.x no son compatibles RestTemplate, pero sólo WebClient. Consulte la referencia de seguridad de Spring:

Soporte de cliente HTTP

  • WebClient integración para entornos Servlet (para solicitar recursos protegidos)

Además, RestTemplate quedará obsoleto en una versión futura. Ver RestTemplate javadoc:

NOTA: A partir de 5.0, el reactivo no bloqueante
org.springframework.web.reactive.client.WebClient ofrece una alternativa moderna a la RestTemplate con soporte eficiente para sincronización y async, así como escenarios de transmisión. los RestTemplate quedará obsoleto en una versión futura y no se agregarán nuevas funciones importantes en el futuro. Ver el WebClient sección de la documentación de referencia de Spring Framework para obtener más detalles y código de ejemplo.

Por tanto, la mejor solución sería abandonar RestTemplate a favor de WebClient.


Utilizando WebClient para el flujo de credenciales del cliente

Configure el registro del cliente y el proveedor de forma programática o mediante la configuración automática de Spring Boot:

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: clientId
            client-secret: clientSecret
            authorization-grant-type: client_credentials
        provider:
          custom:
            token-uri: http://localhost:8081/oauth/token

… y el OAuth2AuthorizedClientManager@Bean:

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) 

    OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                    .clientCredentials()
                    .build();

    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientRepository);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;

Configurar el WebClient instancia para usar ServerOAuth2AuthorizedClientExchangeFilterFunction con el proporcionado OAuth2AuthorizedClientManager:

@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) 
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultClientRegistrationId("custom");
    return WebClient.builder()
            .apply(oauth2Client.oauth2Configuration())
            .build();

Ahora, si intenta realizar una solicitud utilizando este WebClient Por ejemplo, primero solicitará un token del servidor de autorización y lo incluirá en la solicitud.

Hola, tal vez sea demasiado tarde, sin embargo, RestTemplate todavía es compatible con Spring Security 5, para la aplicación no reactiva RestTemplate todavía se usa, lo que tiene que hacer es solo configurar Spring Security correctamente y crear un interceptor como se menciona en la guía de migración

Use la siguiente configuración para usar el flujo client_credentials

application.yml

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: $okta.oauth2.issuer/v1/keys
      client:
        registration:
          okta:
            client-id: $okta.oauth2.clientId
            client-secret: $okta.oauth2.clientSecret
            scope: "custom-scope"
            authorization-grant-type: client_credentials
            provider: okta
        provider:
          okta:
            authorization-uri: $okta.oauth2.issuer/v1/authorize
            token-uri: $okta.oauth2.issuer/v1/token

Configuración de OauthResTemplate

@Configuration
@RequiredArgsConstructor
public class OAuthRestTemplateConfig 

    public static final String OAUTH_WEBCLIENT = "OAUTH_WEBCLIENT";

    private final RestTemplateBuilder restTemplateBuilder;
    private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    private final ClientRegistrationRepository clientRegistrationRepository;

    @Bean(OAUTH_WEBCLIENT)
    RestTemplate oAuthRestTemplate() 
        var clientRegistration = clientRegistrationRepository.findByRegistrationId(Constants.OKTA_AUTH_SERVER_ID);

        return restTemplateBuilder
                .additionalInterceptors(new OAuthClientCredentialsRestTemplateInterceptorConfig(authorizedClientManager(), clientRegistration))
                .setReadTimeout(Duration.ofSeconds(5))
                .setConnectTimeout(Duration.ofSeconds(1))
                .build();
    

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager() 
        var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    


Interceptador

public class OAuthClientCredentialsRestTemplateInterceptor implements ClientHttpRequestInterceptor 

    private final OAuth2AuthorizedClientManager manager;
    private final Authentication principal;
    private final ClientRegistration clientRegistration;

    public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) 
        this.manager = manager;
        this.clientRegistration = clientRegistration;
        this.principal = createPrincipal();
    

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException 
        OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
                .withClientRegistrationId(clientRegistration.getRegistrationId())
                .principal(principal)
                .build();
        OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
        if (isNull(client)) 
            throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
        

        request.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + client.getAccessToken().getTokenValue());
        return execution.execute(request, body);
    

    private Authentication createPrincipal() 
        return new Authentication() 
            @Override
            public Collection getAuthorities() 
                return Collections.emptySet();
            

            @Override
            public Object getCredentials() 
                return null;
            

            @Override
            public Object getDetails() 
                return null;
            

            @Override
            public Object getPrincipal() 
                return this;
            

            @Override
            public boolean isAuthenticated() 
                return false;
            

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException 
            

            @Override
            public String getName() 
                return clientRegistration.getClientId();
            
        ;
    

Esto generará access_token en la primera llamada y siempre que caduque el token. OAuth2AuthorizedClientManager se encargará de todo esto

Encontré la respuesta de @matt Williams bastante útil. Aunque me gustaría agregar en caso de que alguien quisiera pasar programáticamente el clientId y el secreto para la configuración de WebClient. Así es como se puede hacer.

 @Configuration
    public class WebClientConfig 

    public static final String TEST_REGISTRATION_ID = "test-client";

    @Bean
    public ReactiveClientRegistrationRepository clientRegistrationRepository() 
        var clientRegistration = ClientRegistration.withRegistrationId(TEST_REGISTRATION_ID)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .clientId("")
                .clientSecret("")
                .tokenUri("")
                .build();
        return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
    

    @Bean
    public WebClient testWebClient(ReactiveClientRegistrationRepository clientRegistrationRepo) 

        var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,  new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
        oauth.setDefaultClientRegistrationId(TEST_REGISTRATION_ID);

        return WebClient.builder()
                .baseUrl("https://.test.com")
                .filter(oauth)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    

Valoraciones y comentarios

Ten en cuenta dar difusión a este enunciado si lograste el éxito.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *