OAuth2 REST-API Clients leicht gemacht mit Spring

Viele REST-APIs sind heute mit OAuth2 abgesichert. Daher bietet uns Spring eine einfache Möglichkeit das OAuth2-Protokoll in den bekannten HTTP-Clients WebClient und RestTemplate zu implementieren, so dass wir uns nicht mehr selbst um Access und Refresh-Tokens kümmern müssen. Wie das geht zeige ich euch in diesem Artikel.

Was ist OAuth2?

OAuth steht für "Open Authorization" und ist ein offenes Standard-Protokoll zur sicheren Autorisierung von APIs. OAuth2 ist die überarbeitete Version von OAuth, welche die ursprüngliche Version häufig abgelöst hat. Mit OAuth2 können Endbenutzer einer Anwendung den Zugriff auf ihre Daten, bereitgestellt durch einen anderen Dienst, erlauben. Insbesondere im Kontext von Smartphone-Apps hat sich OAuth schnell durchgesetzt. Weiterführende Informationen zu OAuth2 findet ihr hier:

OAuth2 API konsumieren

Wie man mit Spring RestTemplate und WebClient REST-APIs aufruft, habe ich bereits in diesem Artikel gezeigt: reactive-webclient.html. Dort zeige ich die reaktive Kommunikation mit einer API mittels dem neueren Spring WebClient, aber auch die klassische, synchrone Kommunikation mittles Spring RestTemplate. Mit dem Spring 5.0 Release wurde angekündigt, dass das RestTemplate nicht mehr weiterentwickelt wird und stattdessen der moderne WebClient verwendet werden sollte, siehe die "Note" im JavaDoc dazu: 
https://www.javadoc.io/.../org/springframework/web/client/RestTemplate.html
Aus diesem Grund zeige ich hier auch nur, wie man mit dem WebClient eine OAuth2 gesicherte API konsumiert.

Schaut man sich andere Spring OAuth2 Tutorials an, fällt auf, dass in vielen anderen als deprecated markierte Klassen und Methoden verwendet werden. Das zeigt, dass sich am WebClient einiges tut, von dem wir sicherlich auch in Zukunft profitieren werden. Ich zeige hier im aller neusten Stand, wie der WebClient ohne Verwendung von deprecated Klassen und Methoden mit einem OAuth2 Filter verwendet wird (Neuster Stand = Spring Boot Version 2.4.5 vom April 2021).

OAuth Client Credential Prozess - im folgenden Code implementiert

WebClient mit OAuth2 Filter in Spring Boot 2.4

@Service
@ConfigurationProperties(prefix = "geo-api.oauth2")
@Setter
public class GeoApiClient {
    private String tokenUri;     private String clientId;     private String clientSecret;
    private String serviceBaseUrl;
    private WebClient client;
    @PostConstruct
    private void initClient() {
String regId = "any_id_to_identify_oauth_registration";
var repo = createRegistrationRepo(regId);
this.client = WebClient.builder()
             .baseUrl(serviceBaseUrl)
.filter(createOauthFilter(regId, repo))
.build();
    }
    ...
  • @Service und @PostConstruct erstellt und initialisiert die Spring Bean GeoApiClient mit der wir eine API für geografische Adressen konsumieren werden. Für Details zu diesen Annotation schaut euch diesen Artikel an: kernkonzepte-von-spring.html
  • Die WebClient Instanz wird mittels Builder erstellt, dabei ist es wichtig, dass wir einen Filter mit der Methode filter übergeben, der sich für uns, um das OAuth2 Protokoll kümmert. Ansonsten setze ich beim Bauen des WebClients nur noch die Basis-URL zum Server, der den REST Service anbietet. 
  • Bevor wir uns den Filter in der Methode createOauthFilter genau anschauen, benötigen wir noch eine Instanz von ReactiveClientRegistrationRepository, die ich in der Methode createRegistrationRepo erstelle:
private ReactiveClientRegistrationRepository
        createRegistrationRepo(String regId) {
    var registration = ClientRegistration
            .withRegistrationId(regId)
    .tokenUri(tokenUri)
    .clientId(clientId)
    .clientSecret(clientSecret)
    .authorizationGrantType(
                    AuthorizationGrantType.CLIENT_CREDENTIALS)
    .build();
    return new InMemoryReactiveClientRegistrationRepository(
            registration);
}
  • Die Klasse ClientRegistration liefert mit der statischen Methode withRegistrationId einen Builder, um die OAuth-Konfiguration zu hinterlegen. Diese besteht hier aus:
    • tokenUri: einem Link zum OAuth Token Endpunkt am Autorisierungsserver.
    • authorizationGrantType: legt den Verwendeten Autorisierungsprozess fest. Hier wird "Client Credentials" festgelegt, so dass wir noch clientId und clientSecret als Credential definieren müssen.
    • regId: ist eine selbst definierte Id vom Typ String.
  • ClientRegistration und die anderen hier gezeigten OAuth-Klassen werden von der Bibliothek spring-security-oauth2-client bereitgestellt. Weiter unten zeige ich die Gradle-Dependency dazu.
  • Die mittels build Methode gebaute ClientRegistration Instanz wird dann an den Konstruktor der Klasse InMemoryReactiveClientRegistrationRepository übergeben. Die so erzeugte ReactiveClientRegistrationRepository Repository-Instanz benötigen wir dann für den OAuth-Filter, welchen Spring zum Managen des OAuth-Prozess nutzt. Die Repository-Instanz und die Registierungs-Id (regId) übergeben wir nun an die Methode createOauthFilter zum Bauen des OAuth-Filters:
private ExchangeFilterFunction createOauthFilter(
        String regId, ReactiveClientRegistrationRepository repo) {
    var manager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            repo, new InMemoryReactiveOAuth2AuthorizedClientService(repo));

    var oauthFilter = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
            manager);

    oauthFilter.setDefaultClientRegistrationId(regId);
    return oauthFilter;
}
  • Das ReactiveClientRegistrationRepository übergeben wir zusammen mit einer neuen Instanz von InMemoryReactiveOAuth2AuthorizedClientService in eine neue Instanz der OAuth-Client-Manager Klasse AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager. Beide neu erstellten Instanzen benötigen als Eingabeparameter nur das, in der zuvor gezeigten Methode, erstellte Repository.
  • Mit der AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager Instanz können wir nun einen OAuth-Filter ServerOAuth2AuthorizedClientExchangeFilterFunction erstellen, den wir dann auch als Ergebnis dieser Methode zurückgeben. 
  • Im Filter muss dann noch mit setDefaultClientRegistrationId die zuvor selbst definierte Id gesetzt werden. Also dieselbe Id, die wir auch schon in der ClientRegistration gesetzt haben.
Das Ergebnis der 3 zuvor gezeigten Code-Ausschnitte ist nun eine Spring Bean GeoApiClient, die im Attribute client einen WebClient mit konfiguriertem OAuth2 Filter hat. Verwenden wir die regulären Methoden dieser WebClient Instanz, kümmert sich der OAuth2 Filter automatisch, um die Autorisierung bzw. das Token-Handling.

Konfigurationswerte in Spring Beans nutzen

Im vorherigen Abschnitt habe ich nicht erklärt, woher die Werte der 4 String Attribute tokenUri, clientId, clientSecret und serviceBaseUrl kommen. Ich habe sie in der Spring Konfigurationsdatei application.properties als Key-Value-Paare definiert: 

geo-api.oauth2.serviceBaseUrl=https://api.server.de/geographicAddress
geo-api.oauth2.tokenUri=https://token.server.de/openid-connect/token
geo-api.oauth2.clientId=api-consumer
geo-api.oauth2.clientSecret=xxx

Spring bietet verschiedene Möglichkeiten um Konfiguration zu definieren und auszulesen. Ich habe die Konfiguration aus der zuvor gezeigten Datei in der Klasse GeoApiClient mit der Annotation @ConfigurationProperties ausgelesen. Dazu habe ich das Annotations-Attribut prefix auf "geo-api.oauth2" gesetzt. Das ist der gleiche Prefix, der auch in alle relevanten Property-Keys der Datei application.properties verwendet wird. Außerdem müssen dann die Namen der zu setzenden Attribute in der annotierten Klasse mit dem Key (ohne Prefix) aus der application.properties übereinstimmen.

Damit Spring nun automatisch die Werte aus der properties-Datei in die Attribute der Bean schreibt, werden Bibliotheken benötigt, die durch Spring Boot über Maven oder Gradle Build-Konfiguration bereitgestellt werden. In Gradle sieht die Dependency dazu so aus:

dependencies {
    implementation 'org.springframework.boot:spring-boot-configuration-processor'
    implementation 'org.springframework.security:spring-security-oauth2-client'
    annotationProcessor 'org.projectlombok:lombok'
    ...
 
Damit der Spring Boot Konfigurations-Prozessor die Attribute in die Bean schreiben kann, werden Setter-Methoden für jedes zu setzende Attribut benötigt. Die Setter Methoden lasse ich mit der Annotation @Setter durch Lombok generieren, siehe https://projectlombok.org/.
Die zuvor gezeigte Gradle Dependencies-Liste enthält auch die Lombok und die notwendige Spring-Security-OAuth2-Client Bibliothek.


Für weitere Infos zu Spring Properties, schaut euch z.B. das an:

OAuth2 gesicherte API Request verschicken

Das eigentliche Verschicken von einem OAuth2 abgesicherten Request funktioniert genauso wie auch ungesicherte Requests verschickt werden. Durch den zuvor konfigurierten OAuth-Filter ist das Token-Handling für uns transparent. Hier ist mein Beispiel Request, der an der zuvor konfigurierten Geographic Address API Address-Details anhand eines Strings bestehend aus Straße, Postleitzahl und Stadt abruft. Diese API soll hier nur ein Beispiel sein, es könnte auch jede beliebige andere mit OAuth2 gesicherte API abgefragt werden. Wer sich für die API im Detail interessiert kann sie sich gerne im tmforum anschauen:
https://www.tmforum.org/.../tmf673-geographic-address-management-api-user-guide-v4-0-0/

public Flux<GeographicAddress> getRealAddresses(
        String street, String postcode, String city) {
    return client.get()
        .uri(builder -> 
    builder.path("/v2/geographicAddress")
        .queryParam(".fullText", 
                    postcode + " " + city + " " + street)
        .queryParam("limit", 5)
        .queryParam(".searchtype", "exact")
        .build())
.retrieve().bodyToFlux(GeographicAddress.class);
}

Wie der WebClient funktioniert habe ich bereits in diesem Blog-Artikel erklärt:
reactive-webclient.html
Dort ist auch gezeigt, wie man mit dem WebClient synchrone Requests macht. Da der WebClient beides unterstützt, benötigt man das RestTemplate eigentlich nicht mehr.

Fazit 

Den kompletten Code aus diesem Blog findet ihr in GitHub:

Man könnte das OAuth Access- und Refresh-Token-Handling auch selbst implementieren - ich habe das schon häufiger in der Praxis gesehen. In der Praxis findet man an diesen Stellen aber auch viele Fehler, die ihr mit der OAuth-Lösung von Spring sehr gut vermeiden könnt!

Solltet ihr noch das RestTemplate verwenden und ein eigenes OAuth-Handling geschrieben haben, bietet sich eine Migration auf den WebClient an. Wenn ihr aber unbedingt beim RestTemplate bleiben wollt, schaut euch wenigstens das OAuth2RestTemplate an, welches ebenfalls den OAuth-Prozess für euch managen kann.

Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

CronJobs mit Spring

Kernkonzepte von Spring: Beans und Dependency Injection