Beliebte Suchanfragen
//

Authenticated REST Services mit Keycloak und Spring Boot

30.1.2025 | 9 Minuten Lesezeit

Einleitung

Dieser Artikel beschreibt die Implementierung einer Spring Boot-Anwendung, welche REST-Schnittstellen anbietet. Diese Schnittstellen sollen nur dann erwartete Responses liefern, wenn der Nutzer sich vorher vollständig gegenüber Keycloak authentifiziert hat und der Anfrage mit einem entsprechenden Authorization - Header und dem gültigen Bearer Json Web Token (JWT) abgesetzt hat. Für die einzelnen Anwendungen wie Keycloak oder der PostgreSQL Datenbank wird Docker verwendet.

Problemstellung

Mit der zunehmenden Anzahl an APIs steigt auch der Wartungsaufwand, berechtigten Usern Zugriff auf die Schnittstellen gewähren. Keycloak bietet hier eine etablierte, zentral verwaltete Lösung für die Authentifizierung von Benutzern ab.

Docker (compose...)

Im folgenden wird beschrieben wie Keycloak in eine Spring Boot-Anwendung mit REST-Schnittstellen integriert werden kann. Die einfachste Möglichkeit diese Instanzen für die Entwicklung lokal zu starten ist es, sich entsprechende Images herunterzuladen und diese als Docker Container zu starten. Abhängigkeiten zwischen den Services und einfacherer Skripte diese Container zu starten sind realisierbar durch eine docker-compose.yml Datei.

Dieser Blogartikel beschreibt die Ausführung der Container mittels docker-compose.

Erfahrungen mit docker und docker-compose sind für das Verständnis der folgenden Abschnitte hilfreich.

docker-compose.yml Initialisierung

Zunächst muss eine Datei mit dem Namen docker-compose.yml erstellt werden. Diese beinhaltet aktuell nur das leere Element services. Die benötigten Dienste werden in den kommenden Paragraphen nach und nach ergänzt.

docker-compose.yml

PostgreSQL Datenbank

Eine lokale Datenbank für Keycloak erleichtert die Entwicklung, da die Daten in Keycloak tatsächlich persistiert werden. In diesem Beispiel wird eine PostgreSQL Datenbank verwendet. Hierfür wird ein neuer Service mit dem Namen db erstellt, welcher unter anderem die Ports des Containers an das Host-System weiterleitet und ein externen Container Speicher anlegt damit die Daten bei einem Neustart nicht verloren gehen.

docker-compose.yml

Der Befehl docker-compose up db -d lädt das Image herunter und startet den Container. Das optionale -d Flag löst den Prozess vom Terminal.

Keycloak

Da nun eine lokale Datenbank zur Verfügung steht gilt es eine Keycloak Instanz zu starten. Hierfür wird die docker-compose.yml Datei um einen neuen Service erweitert, welcher von der Datenbank abhängt. Das liegt daran, dass Keycloak auf die lokale Datenbank zeigen soll und somit die Änderungen in dieser persisitert werden sollen. Die Verbindung zur lokalen Datenbank werden durch Umgebungsvariablen definiert. Bei der Datenbank URL wird als Host der Servicename der Datenbank gesetzt. In diesem fall ist es db. Damit auch lokaler Zugriff auf die Keycloak Instanz möglich ist, wird der Standartkeycloak Port 8080 auf den Host-Port 8081 gemappt. Der Grund hierfür ist, dass der Port 8080 bereits von der Spring Boot Anwendung genutzt werden soll, da dies auch der Standartport für Spring Boot ist.

Da dieser Blogartikel sich nicht darum handelt wie man eine produktive Keycloak Instanz startet, wird start-dev übergeben um eine Developmentumgebung zu instanziieren.

Damit ein initialer Keycloak Client beim Start zur Verfügung steht ist es für dieses docker-compose File notwending einen imports Folder zu erstellen welcher eine test1-client.json Datei beinhaltet. Diese Datei wird beim Start des Keycloak services importiert. Grundsätzlich erstellt dieser ausschließlich einen Client mit dem man dann die Anfragen an Keycloak machen kann

import/test1-client.json

docker-compose.yml

Der Befehl docker-compose up keycloak -d startet nun die Keycloak Umgebung und zusätzlich auch die Datenbank, sollte sie noch nicht laufen. Es dauert kurz bis die Anwendung vollständig hochgefahren ist.

Erhalten des Access Tokens

Nun sollte der Keycloak Service laufen und unter http://localhost:8081 erreichbar sein. Mit dem User admin und dem Password admin muss man sich einloggen und fehlende Informationen ergänzen bevor man ein JSON Web Token anfordern kann.

Die folgende CURL Anfrage sollte ein JSON zurückliefern mit verschiedenen Keys. Darunter auch den access_token.

Ziel wird es sein valide Ergebnisse von Endpoints zu erhalten, nur dann wenn man diesen mit dem Authorization Header im Request mitliefert.

Spring Boot Initialisierung

Zunächst muss eine Spring Boot Anwendung initialisiert werden. Dies ist mit dem spring initializr möglich. In diesem Blogartikel wird die Programmiersprache kotlin und als dependency management gradle verwendet.

Damit die Anwendung sauber in einem Tomcat starten kann ist folgende dependency notwendig.

build.gradle.kts

Jetzt sollte die Anwendung gestartet sein mit folgendem Output:

Output der erfolgreich hochgefahrenen Spring Boot Anwendung

Controller Implementierung

Auf Ebene der Main Funktion wird ein package mit dem Namen controller angelegt. Darunter eine neue Klasse UserController mit folgendem Inhalt:

UserController.kt

Wie dem Returnvalue zu entnehmen soll der Pfad /user jedem Request eine valide Response zurückliefern. Wohingegen der Pfad /authenticated-user nur Requests mit korrektem Authorization - Header vorenthalten ist. Aktuell wurde dies noch nicht konfiguriert.

Test getriebene Implementierung mit Testcontainer

Um nach der Konvention des Test Driven Developments (TDD) vorzugehen werden zunächst Tests entwickelt, die die Implementierung auf fachliche Korrektheit testen. Da die Implementierung noch nicht existiert werden diese Tests erwartungsgemäß fehlschlagen. Entwickelt werden Integration Tests. Damit die Integration Tests nicht abhängig von einer laufenden Keycloakumgebung ist, wird ein Testcontainer für den Test hochgefahren. Dieser wird somit als Authentifizierungserver genutzt. Folgende Dependencies sind notwendig:

build.gradle.kts

Keycloak Testcontainerintegration

Damit ein Keycloak Testcontainer vor dem Test gestartet werden bedarf es folgender Implementierung.

UserControllerTest.kt

Es wird ein Keycloak Testcontainer instanziiert. Damit auch ein User zur Verfügung steht, mit dem man Keycloak Token anfordern kann muss beim erstellen des Containers ein User erstellt werden. Dieses json File ist zu erstellen unter src/main/test/resources/test-realm.json und der Inhalt sieht folgendermaßen aus:

test-realm.json

Vor allen Tests wird dieser mit @BeforeAll gestartet und auf den lokalen port 8082 gemappt. Mit @Afterall wird der Container gestoppt. Somit wird sichergestellt, dass die Tests die notwendigen Elemente für die Authentifizierung beinhaltet.

Implementierung der Integration Tests

Um die Authentifizierung sauber zu testen ohne diese mocken zu müssen, werden Integration Tests implementiert.

  1. Test, dass /user ohne Authentifizierung eine valide Antwort liefert.
  2. Test, dass /authenticated-user ohne Authentifizierung ein HTTP 401 Unauthorized liefert.
  3. Test, dass /authenticated-user mit Authentifizierung eine valide Antwort liefert.

Für die Integration Tests wird im folgenden TestRestTemplate verwendet. Zunächst wird eine globale Instanz von TestRestTemplate mit @Autowired als dependency injected. Und damit man auf den zufällig erzeugten Port Zugriff hat, wird noch eine globale Variable erzeugt die auf die implizit gesetzte Property local.server.port zugreift. Dies ist für die kommenden Tests notwendig um eine vollständige URL zu haben um Anfragen abzusetzen.

UserControllerTest.kt

Implementierung zu 1.

UserControllerTest.kt

Implementierung zu 2.

UserControllerTest.kt

Info

Dieser Test wird fehlschlagen (und das sollte er auch), da die Implementierung dazu noch fehlt

Implementierung zu 3.

Um einen Token von der Keycloak umgebung zu erhalten muss eine Anfrage direkt an die Keycloak Testcontainer Instanz durchgeführt werden. Dadurch kann ein valider Test implementiert werden. Bei der Klasse AccessTokenHelper handelt es sich um eine eigene Implementierung für die Anfrage des Json Web Tokens direkt von Keycloak.

Die Implementierung dazu ist hier beschrieben.

UserControllerTest.kt

Aktuell liefert jeder Endpoint eine valide Response mit dem Statuscode 200 (HTTP OK) zurück. Somit schlägt nur der Test für die Prüfung auf den Httpstatus 401 (HTTP UNAUTHORIZED) fehl.

Security Implementierung

Spring security dependency

Es gibt eine starter dependency von Spring Boot um OAuth direkt einzubauen. Diese liefert neben OAuth spezifischen dependencies auch spring-security-core und spring-security-config mit. Diese werden benötigt um eine SecurityConfig zu erstellen.

build.gradle.kts

SecurityConfig Implementierung

Im Folgenden ist eine minimale Implementierung der Security Configuratioon. Hier wird definiert, dass alle Pfade die mit /users anfangen für jeden Request erlaubt sind, wohingegen die Pfade /authenticated-user für jeden authentifizierten User erlaubt sind.

Der Block http.oauth2ResourceServer konfiguriert einen OAuth2 Resource Server. Dieser wird so definiert, dass JSON Web Token basierte Authentifizierung für geschützte Endpunkte, unterstützt wird. In diesem fall /authenticated-user.

Die Spring Security validiert die JWTs dann automatisch anhand des Issuers aus der application.yml. Der Issuer definiert den Aussteller der Token. Dieser Endpunkt von Keycloak muss als Wert für den Key spring.security.oauth2.resourceserver.jwt.issuer-uri definiert werden. In diesem Fall sieht die application.yml folgendermaßen aus.

application.yml

Die Keycloak Instanz wurde unter dem Abschnitt Keycloak erstellt und in einem Docker container hochgefahren.

Ganz generell sind die verschiedenen Endpoints von Keycloak unter http://localhost:8081/realms/master/.well-known/openid-configuration abrufbar.

SecurityConfig.kt

Es gibt noch andere Möglichkeiten den JSON Web Token zu decodieren. Eine weitere wäre es, eine Bean zu definieren die einen Custom decoder konfiguriert. Folgendes Beispiel wäre eine Möglichkeit.

SecurityConfig.kt

Erneutes Ausführen der Tests

Nachdem die Tests erneut ausgeführt werden, sollten diese nun alle grün sein. Vor allem die Prüfung darauf ob ein HTTP 401 UNAUTHORIZED übermittelt wird.

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//
Jetzt für unseren Newsletter anmelden

Alles Wissenswerte auf einen Klick:
Unser Newsletter bietet dir die Möglichkeit, dich ohne großen Aufwand über die aktuellen Themen bei codecentric zu informieren.