Spring Security 5 Ersatz für OAuth2RestTemplate

Lesezeit: 11 Minuten

In spring-security-oauth2:2.4.0.RELEASE Klassen wie z OAuth2RestTemplate, OAuth2ProtectedResourceDetails Und ClientCredentialsAccessTokenProvider wurden alle als veraltet markiert.

Vom Javadoc zu diesen Klassen zeigt es auf a Spring Security-Migrationsleitfaden das unterstellt, dass die Leute zum Kernprojekt spring-security 5 migrieren sollten. Ich habe jedoch Probleme zu finden, wie ich meinen Anwendungsfall in diesem Projekt implementieren würde.

Alle Dokumentationen und Beispiele beziehen sich auf die Integration mit einem Drittanbieter-OAuth-Anbieter, wenn Sie möchten, dass eingehende Anfragen an Ihre Anwendung authentifiziert werden, und Sie den Drittanbieter-OAuth-Anbieter verwenden möchten, um die Identität zu überprüfen.

In meinem Anwendungsfall möchte ich nur eine Anfrage mit a stellen RestTemplate an einen externen Dienst, der durch OAuth geschützt ist. Aktuell erstelle ich eine OAuth2ProtectedResourceDetails mit meiner Client-ID und meinem Geheimnis, die ich in eine übergebe OAuth2RestTemplate. Ich habe auch einen Brauch ClientCredentialsAccessTokenProvider hinzugefügt OAuth2ResTemplate Dadurch werden der Token-Anforderung nur einige zusätzliche Header hinzugefügt, die von dem von mir verwendeten OAuth-Anbieter benötigt werden.

In der Spring-Security-5-Dokumentation habe ich einen Abschnitt gefunden, der erwähnt Anpassen der Token-Anfrage, aber auch das scheint im Zusammenhang mit der Authentifizierung einer eingehenden Anfrage bei einem OAuth-Drittanbieter zu stehen. Es ist nicht klar, wie Sie dies in Kombination mit etwas wie a verwenden würden ClientHttpRequestInterceptor um sicherzustellen, dass jede ausgehende Anfrage an einen externen Dienst zuerst ein Token erhält und dieses dann der Anfrage hinzugefügt wird.

Auch im oben verlinkten Migrationsleitfaden findet sich ein Hinweis auf a OAuth2AuthorizedClientService was angeblich für die Verwendung in Abfangjägern nützlich ist, aber auch dies sieht so aus, als ob es auf Dingen wie dem beruht ClientRegistrationRepository Dort werden anscheinend Registrierungen für Drittanbieter verwaltet, wenn Sie diese Bereitstellung verwenden möchten, um sicherzustellen, dass eine eingehende Anfrage authentifiziert wird.

Gibt es eine Möglichkeit, die neue Funktionalität in Spring-Security 5 zum Registrieren von OAuth-Anbietern zu nutzen, um ein Token zu erhalten, das ich ausgehenden Anforderungen meiner Anwendung hinzufügen kann?

Benutzeravatar von Anar Sultanov
Anar Sultanow

OAuth 2.0-Clientfunktionen von Spring Security 5.2.x werden nicht unterstützt RestTemplateaber nur WebClient. Sehen Spring-Sicherheitsreferenz:

HTTP-Client-Unterstützung

  • WebClient Integration für Servlet-Umgebungen (zum Anfordern geschützter Ressourcen)

Zusätzlich, RestTemplate wird in einer zukünftigen Version veraltet sein. Sehen RestTemplate javadoc:

NOTIZ: Ab 5.0 die nicht blockierende, reaktive
org.springframework.web.reactive.client.WebClient bietet eine moderne Alternative zum RestTemplate mit effizienter Unterstützung für synchrone und asynchrone sowie Streaming-Szenarien. Der RestTemplate wird in einer zukünftigen Version veraltet sein und es werden in Zukunft keine größeren neuen Funktionen hinzugefügt. Siehe die WebClient Abschnitt der Spring Framework-Referenzdokumentation für weitere Details und Beispielcode.

Daher wäre die beste Lösung, aufzugeben RestTemplate zugunsten WebClient.


Verwenden WebClient für Client-Anmeldedatenfluss

Konfigurieren Sie die Clientregistrierung und den Anbieter entweder programmgesteuert oder mithilfe der automatischen Spring Boot-Konfiguration:

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

…​und die 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;
}

Konfigurieren Sie die WebClient Instanz zu verwenden ServerOAuth2AuthorizedClientExchangeFilterFunction mit dem bereitgestellten OAuth2AuthorizedClientManager:

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

Wenn Sie jetzt versuchen, eine Anfrage damit zu stellen WebClient Beispielsweise fordert er zunächst ein Token vom Autorisierungsserver an und schließt es in die Anforderung ein.

  • Das ist jetzt zu veraltet, lol … zumindest ist UnAuthenticatedServerOAuth2AuthorizedClientRepository …

    – Vorschlaghammer

    13. März 2020 um 22:06 Uhr

  • @AnarSultanov “Daher wäre die beste Lösung, RestTemplate zugunsten von WebClient aufzugeben.” Was ist mit Orten, an denen dies keine Option ist? Spring Cloud Discovery-, Configuration- und Feign-Clients verlassen sich beispielsweise immer noch auf RestTemplate und Dokumentationsstatus, um ein benutzerdefiniertes RestTemplate bereitzustellen, wenn Sie planen, diesen Diensten Sicherheit wie OAuth hinzuzufügen.

    – lösak

    4. April 2020 um 3:38 Uhr


  • @AnarSultanov Ich habe genau das Beispiel ausprobiert, das Sie gegeben haben, und ich erhalte einen 401-Fehler. Es scheint, dass es sich nicht authentifiziert, während versucht wird, Anforderungen auszuführen. Irgendwelche Tipps dazu?

    – Rafael Braga

    24. April 2020 um 1:08 Uhr

  • @rafael.braga Ich kann nichts empfehlen, ohne den gesamten Code und die gesamte Konfiguration zu sehen. Sie können das Beispiel aus dem offiziellen Repository ausprobieren und an Ihre Bedürfnisse anpassen: github.com/spring-projects/spring-security/tree/master/samples/…

    – Anar Sultanow

    24. April 2020 um 8:06 Uhr

  • Hier ist das relevante Spring Security-Dokument. Bietet etwas mehr Details und Erläuterungen zu den verschiedenen Möglichkeiten, wie Sie WebClient konfigurieren können: docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/…

    – Crafton

    16. Juli 2021 um 0:33 Uhr

Benutzeravatar von Leandro Assis
Leandro Ass

Hallo, vielleicht ist es zu spät, aber RestTemplate wird in Spring Security 5 noch unterstützt, um nicht reaktive App RestTemplate wird immer noch verwendet, was Sie tun müssen, ist nur Spring Security richtig zu konfigurieren und einen Interceptor zu erstellen, wie im Migrationsleitfaden erwähnt

Verwenden Sie die folgende Konfiguration, um den Fluss „client_credentials“ zu verwenden

Anwendung.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

Konfiguration für 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;
    }

}

Abfangjäger

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

Dies generiert access_token beim ersten Aufruf und immer dann, wenn das Token abgelaufen ist. OAuth2AuthorizedClientManager verwaltet all dies für Sie

  • Ich mag diesen Ansatz, weil er mit dem von Spring Security übereinstimmt DefaultClientCredentialsTokenResponseClient was nutzt RestTemplate im Inneren. Das bedeutet, dass mein Projekt keine Abhängigkeit von spring-webflux benötigt.

    – Nathan

    21. Juli 2022 um 2:38 Uhr

  • beste Lösung. Prägnant und funktioniert wie ein Zauber. Selbst die Frühlingsdokumente boten keine so klare Anleitung.

    – Walnussbär

    17. August 2022 um 10:04 Uhr

  • Nur eine kleine Anmerkung, basierend auf dem Quellcode scheint es mir so AuthorizedClientServiceOAuth2AuthorizedClientManager ist nicht streng Thread-sicher. Nicht in einer Weise, die die Anwendung zum Absturz bringt, aber es werden mehrere Aufrufe zum Abrufen neuer Tokens getätigt, wenn mehrere Anforderungen gleichzeitig bearbeitet werden und alle einen Token verwenden, der bald abläuft/abgelaufen ist.

    – Alexis

    16. September 2022 um 7:25 Uhr


  • @Alexis wie können wir das vermeiden? Ich meine, die abgelaufenen nicht zu verwenden?

    – John Roschan

    22. Dezember 2022 um 16:48 Uhr

  • @JohnRoshan Es aktualisiert Token bei Bedarf automatisch, es ist nur so, dass es in einigen Grenzfällen mehrere Aktualisierungen parallel vornehmen kann, von denen einige redundant wären. Um dies zu vermeiden, müssten Sie die Synchronisierung selbst durchführen.

    – Alexis

    22. Dezember 2022 um 17:36 Uhr

Ich fand die Antwort von @matt Williams sehr hilfreich. Ich möchte jedoch hinzufügen, falls jemand clientId und Geheimnis für die WebClient-Konfiguration programmgesteuert übergeben möchte. Hier ist, wie es gemacht werden kann.

 @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);
    }
}

  • Gibt es einen Beispielcode, der für das obige Code-Snippet getestet werden kann?

    – Sagar Pilkhwal

    15. Juni 2020 um 14:41 Uhr

  • @SagarPilkhwal Sie können eine einfache, auf Federsicherheit basierende Beispiel-Spring-Boot-Anwendung erstellen (die Sie leicht online finden können). Legen Sie dort den client_credentials-basierten Zugriff fest und stellen Sie eine Test-API bereit. Dann können Sie WebClient mit dem obigen Code erstellen und versuchen, diese API aufzurufen.

    – Jogger

    15. Juni 2020 um 18:26 Uhr

Die obige Antwort von @Anar Sultanov hat mir geholfen, an diesen Punkt zu gelangen, aber da ich meiner OAuth-Token-Anforderung einige zusätzliche Header hinzufügen musste, dachte ich, ich würde eine vollständige Antwort darauf geben, wie ich das Problem für meinen Anwendungsfall gelöst habe.

Anbieterdetails konfigurieren

Fügen Sie Folgendes hinzu application.properties

spring.security.oauth2.client.registration.uaa.client-id=${CLIENT_ID:}
spring.security.oauth2.client.registration.uaa.client-secret=${CLIENT_SECRET:}
spring.security.oauth2.client.registration.uaa.scope=${SCOPE:}
spring.security.oauth2.client.registration.uaa.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.uaa.token-uri=${UAA_URL:}

Benutzerdefiniert implementieren ReactiveOAuth2AccessTokenResponseClient

Da dies eine Server-zu-Server-Kommunikation ist, müssen wir die verwenden ServerOAuth2AuthorizedClientExchangeFilterFunction. Dies akzeptiert nur a ReactiveOAuth2AuthorizedClientManagernicht die nicht reaktive OAuth2AuthorizedClientManager. Daher, wenn wir verwenden ReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider() (um ihm den Anbieter zu geben, der für die OAuth2-Anfrage verwendet werden soll) müssen wir ihm a geben ReactiveOAuth2AuthorizedClientProvider anstelle der nicht reaktiven OAuth2AuthorizedClientProvider. Gemäß der spring-security Referenzdokumentation wenn Sie ein nicht reaktives verwenden DefaultClientCredentialsTokenResponseClient du kannst den … benutzen .setRequestEntityConverter() -Methode zum Ändern der OAuth2-Tokenanforderung, sondern das reaktive Äquivalent WebClientReactiveClientCredentialsTokenResponseClient bietet diese Möglichkeit nicht, also müssen wir unsere eigene implementieren (wir können die vorhandene verwenden WebClientReactiveClientCredentialsTokenResponseClient Logik).

Meine Implementierung wurde aufgerufen UaaWebClientReactiveClientCredentialsTokenResponseClient (Implementierung weggelassen, da sie die headers() Und body() Methoden aus der Vorgabe WebClientReactiveClientCredentialsTokenResponseClient um einige zusätzliche Header/Body-Felder hinzuzufügen, ändert dies nicht den zugrunde liegenden Authentifizierungsablauf).

Konfigurieren WebClient

Der ServerOAuth2AuthorizedClientExchangeFilterFunction.setClientCredentialsTokenResponseClient() Methode ist veraltet, also folgen Sie den Verwerfungshinweisen dieser Methode:

Veraltet. Verwenden ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager) stattdessen. Erstellen Sie eine Instanz von ClientCredentialsReactiveOAuth2AuthorizedClientProvider konfiguriert mit a WebClientReactiveClientCredentialsTokenResponseClient (oder ein benutzerdefiniertes) und dann liefern DefaultReactiveOAuth2AuthorizedClientManager.

Dies endet mit einer Konfiguration, die in etwa so aussieht:

@Bean("oAuth2WebClient")
public WebClient oauthFilteredWebClient(final ReactiveClientRegistrationRepository 
    clientRegistrationRepository)
{
    final ClientCredentialsReactiveOAuth2AuthorizedClientProvider
        clientCredentialsReactiveOAuth2AuthorizedClientProvider =
            new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
    clientCredentialsReactiveOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(
        new UaaWebClientReactiveClientCredentialsTokenResponseClient());

    final DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager =
        new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository,
            new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
    defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(
        clientCredentialsReactiveOAuth2AuthorizedClientProvider);

    final ServerOAuth2AuthorizedClientExchangeFilterFunction oAuthFilter =
        new ServerOAuth2AuthorizedClientExchangeFilterFunction(defaultReactiveOAuth2AuthorizedClientManager);
    oAuthFilter.setDefaultClientRegistrationId("uaa");

    return WebClient.builder()
        .filter(oAuthFilter)
        .build();
}

Verwenden WebClient wie normal

Der oAuth2WebClient bean kann jetzt für den Zugriff auf Ressourcen verwendet werden, die durch unseren konfigurierten OAuth2-Anbieter geschützt sind, so wie Sie jede andere Anfrage mit a stellen würden WebClient.

Dies ist eine einfache Alternative zu OAuth2RestTemplate. Das folgende Snippet wurde mit Spring Boot getestet 3.0.0-M4 und es gibt keine application.yml Konfiguration erforderlich.

SecurityConfig.java

    @Bean
    public ReactiveClientRegistrationRepository getRegistration() {
        ClientRegistration registration = ClientRegistration
                .withRegistrationId("custom")
                .tokenUri("<token_URI>")
                .clientId("<client_id>")
                .clientSecret("<secret>")
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .build();
        return new InMemoryReactiveClientRegistrationRepository(registration);
    }

    @Bean
    public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
        InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService);
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth.setDefaultClientRegistrationId("custom");
        return WebClient.builder()
                .filter(oauth)
                .filter(errorHandler()) // This is an optional
                .build();

    }

    public static ExchangeFilterFunction errorHandler() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {

            if (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError()) {
                return clientResponse.bodyToMono(String.class)
                        .flatMap(errorBody -> Mono.error(new IllegalAccessException(errorBody)));
            } else {
                return Mono.just(clientResponse);
            }
        });
    }

pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.0-M4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
    <dependencies>

1444580cookie-checkSpring Security 5 Ersatz für OAuth2RestTemplate

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy