Zur Verknüpfung einer Springboot-Anwendung mit Keycloak gibt es wahrlich genug Tutorials im Netz.
Allerdings gibt es in den neuesten Versionen beider Anwendungen einige Dinge zu beachten, die sich geändert haben.
Zur Verbindung einer Springboot-Anwendung mit Keycloak gibt es wahrlich genug Tutorials im Netz…
Allerdings gibt es in den neuesten Versionen beider Anwendungen einige Dinge zu beachten die sich geändert haben. Und kaum ein Tutorial berücksichtigt PROD-Umgebungen, das Zusammenspiel mit einem Reverse-Proxy und allgemein die Verwendung von docker-compose. Natürlich wird auch die lokale Entwicklung möglich sein.
Nach Außen zum Internet haben wir einen öffentlichen Server mit öffentlichem DNS-Namen. Wir unterstützen auch HTTPS, wofür ein Let’s-Encrypt-Zertifikat genutzt wird.
Zur besseren Trennung der Anwendung mit seinen Umsystemen und dem Server verwenden wir zwei docker-compose Dateien.
Datei 1 beschreibt den Server:
Wir starten hier einfach 2 Services, nginx und einen Certificate-bot für das Zertifikat. Beide laufen im host Netzwerk-Modus.
Der nginx dient dabei als Reverse-Proxy der das SSL terminiert und zur Springboot-Anwendung und dem Keycloak weiterleitet:
Datei 2 beschreibt nun die Services unserer Anwendung: die eigentliche Springboot-Anwendung, Keycloak und die Datenbank die von beiden Systemen genutzt werden (jeweils mit eigener Umgebung).
Die interessanten Einstellungen hier sind folgende:
für Keycloak der KC_HTTP_RELATIVE_PATH, der exakt dem Pfad im nginx für location verwendet wird
für die Datenbank das Volume für docker-entrypoint-initdb.d. Hier wird ein Pfad mit einer init.sql Datei übergeben, die beim Start der Service ausgeführt wird und mit deren Hilfe zwei getrennte Datenbanken für Springboot und Keycloak erstellt werden.
für Springboot die beiden KEYCLOAK_*URI. Diese URIs müssen auf die öffentliche Adresse des Keycloak zeigen, damit eine Weiterleitung dorthin funktioniert; ein internes http://keycloak/ ist hier nicht ausreichend!
bei allen Services wird eine env-Datei mitgegeben. Hier sind alle Variablen (URIs, Passworte etc.) hinterlegt. Diese sollte nicht – zumindest nicht im Klartext - im Repository hinterlegt sein…
Der Netzwerk-Modus ist hier als bridge gewählt.
Da der Cloud-Server nicht über eine öffentliche IPv4-Adresse verfügt, muss auch das IPv6-Protokoll dafür aktiviert sein.
Für eine lokale Entwicklung bietet es sich an, eine separate docker-compose-testbed.yml Datei zu verwenden, so können die Datenbank und der Keycloak mit einer Testkonfiguration verwendet werden:
So kann lokal das Testbed mittels docker compose -f docker-compose-testbed.yml up -d gestartet werden und dazu die Springboot-Anwendung in der Entwicklungsumgebung (auch für Debugging).
Damit das Zusammenspiel aller Komponenten funktioniert sind noch zwei Dinge nötig: Die Springboot-Anwendung braucht eine entsprechende application.properties, sowie die Keycloak-Anwendung eine finale Konfiguration.
Bzw. für die lokale Entwicklung eine angepasste Version (die Springboot-Anwendung muss dann mit dem entsprechenden Profile gestartet werden):
Neben der so bereitgestellten Verbindung der Springboot-Anwendung zu Keycloak muss auch die SecurityConfiguration in Springboot bereitgestellt werden:
Hier werden einmal die einzelnen Endpunkte der Anwendung gesichert sowie ein Logout Handler konfiguriert, um den Nutzer bei Keycloak abzumelden. Ohne diesen Handler würde ein Logout zwar den Cookie in der Springboot-Anwendung löschen, allerdings werden danach die Anfragen über Keycloak geleitet – und dort wurde der Nutzer nie abgemeldet… Das würde dann dazu führen, dass Keycloak den Cookie wieder hinzufügt, ohne vorher eine erneute Abfrage der Login-Daten durchzuführen – ein Szenario, dass wir vermeiden wollen.
Der Handler ist daher entsprechend so aufgebaut:
Nach erfolgreicher Anmeldung im Keycloak fügt dieses ein JQT-Cookie mit allen Infos den Requests hinzu.
Un hier ist eine weitere Besonderheit bei Keycloak zusammen mit Springboot: Die Verwendung von Rollen in Realms ist anders, die Rollen-Informationen werden an Springboot anders als von diesem erwartet übergeben; sie stehen an „falscher“ Stelle im (decodierten) Json-Objekt des JWT. Wir müssen daher noch ein zusätzliches Mapping einbauen, um rollenbasierte Autorisation der Ressourcen zu ermöglichen.
Was noch fehlt, ist das Anlegen des Realms in Keycloak und entsprechend die Erstellung des CLIENT-SECRET dort um Springboot die Verbindung zu erlauben.
Zuerst wir ein neuer Realm erstellt:
Für diesen Realm wird ein neuer Client erzeugt - das ist die Abbildung der Springboot-Anwendung in Keycloak:
Bei der Konfiguration ist es wichtig den öffentlichen DNS-Namen zu verwenden - der gleiche, der auch in der nginx-Konfiguration verwendet wird.
Abschließend wird für diesen Client das Client Secret erzeugt, dass in der Springboot-Anwendung hinterlegt werden muss.
Je nach Anforderung können noch Rollen angelegt werden. Diese werden durch den GrantedAuthoritiesMapperImpl schließlich in den SecurityContextHolder gemapped und können zur Steuerung der Autorisation einzelner Ressourcen verwendet werden.
Sind nun alle Docker Container gestartet, kann https://my-own-server.com angesurft werden. Wenn eine geschützte Ressource angefordert wird, leitet Springboot an Keycloak zur Anmeldung weiter. Nach erfolreichem Login wird danach zurück zur angefragten Ressource weitergeleitet.