web-dev-qa-db-fra.com

Remplacement de Spring Security 5 pour OAuth2RestTemplate

Dans spring-security-oauth2:2.4.0.RELEASE classes telles que OAuth2RestTemplate, OAuth2ProtectedResourceDetails et ClientCredentialsAccessTokenProvider ont tous été marqués comme obsolètes.

Du javadoc sur ces classes, il pointe vers un Spring security migration guide qui insinue que les gens doivent migrer vers le projet de base spring-security 5. Cependant, j'ai du mal à trouver comment j'implémenterais mon cas d'utilisation dans ce projet.

Toute la documentation et les exemples parlent d'intégration avec une tierce partie OAuth fournisseur si vous voulez que les requêtes entrantes vers votre application soient authentifiées et que vous souhaitez utiliser la tierce partie OAuth fournisseur pour vérifier l'identité.

Dans mon cas d'utilisation, tout ce que je veux faire est de faire une demande avec un RestTemplate à un service externe protégé par OAuth. Actuellement, je crée un OAuth2ProtectedResourceDetails avec mon identifiant client et mon secret que je passe dans un OAuth2RestTemplate. J'ai également un ClientCredentialsAccessTokenProvider personnalisé ajouté au OAuth2ResTemplate qui ajoute simplement des en-têtes supplémentaires à la demande de jeton requis par le fournisseur OAuth que j'utilise).

Dans la documentation de spring-security 5, j'ai trouvé une section qui mentionne personnalisation de la demande de jeton , mais qui semble être dans le contexte de l'authentification d'une demande entrante avec un tiers OAuth. On ne sait pas comment utiliser cela en combinaison avec quelque chose comme un ClientHttpRequestInterceptor pour garantir que chaque demande sortante à un service externe obtient d'abord un jeton, puis obtient celui ajouté à la demande. .

Toujours dans le guide de migration lié ci-dessus, il est fait référence à un OAuth2AuthorizedClientService dont il dit qu'il est utile pour l'utilisation dans les intercepteurs, mais encore une fois, il semble qu'il repose sur des choses comme le ClientRegistrationRepository qui semble être l'endroit où il maintient les enregistrements pour les fournisseurs tiers si vous souhaitez utiliser ce fournir pour garantir une demande entrante est authentifiée.

Existe-t-il un moyen d'utiliser les nouvelles fonctionnalités de Spring-Security 5 pour enregistrer les fournisseurs OAuth) afin d'obtenir un jeton à ajouter aux demandes sortantes de mon application?

13
Matt Williams

Salut, il est peut-être trop tard, mais RestTemplate est toujours pris en charge dans Spring Security 5, pour une application non réactive RestTemplate est toujours utilisé ce que vous devez faire est de configurer correctement la sécurité Spring et de créer un intercepteur comme mentionné dans le guide de migration

Utilisez la configuration suivante pour utiliser le flux 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

Configuration vers 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;
    }

}

Intercepteur

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<? extends GrantedAuthority> 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();
            }
        };
    }
}

Cela générera access_token lors du premier appel et à chaque expiration du jeton. OAuth2AuthorizedClientManager gérera tout cela pour vous

0
Leandro Assis

J'ai trouvé la réponse de @matt Williams très utile. Bien que j'aimerais ajouter au cas où quelqu'un voudrait transmettre par programme clientId et secret pour la configuration WebClient. Voici comment cela peut être fait.

 @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("<client_id>")
                .clientSecret("<client_secret>")
                .tokenUri("<token_uri>")
                .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);
    }
}
0
Jogger