docs: add infrastructure guide for JWT in Docker and refactor Keycloak config
Added a detailed guide (`jwt-in-docker.md`) to address JWT validation challenges in Docker environments (Split Horizon issue). Refactored Keycloak realm configuration (`meldestelle-realm.json`) with updated roles, clients, and improved infrastructure clarity. Updated `.env` variables for streamlined token validation. Adjusted Docker Compose services (`dc-backend.yaml`) to use revised Keycloak environment variables.
This commit is contained in:
parent
a9b788aca9
commit
c692a2395c
8
.env
8
.env
|
|
@ -44,6 +44,12 @@ KC_HOSTNAME=localhost
|
|||
KC_PORT=8180:8080
|
||||
KC_DEBUG_PORT=9000:9000
|
||||
|
||||
# --- KEYCLOAK TOKEN VALIDATION ---
|
||||
# Public Issuer URI (must match the token issuer from browser/postman)
|
||||
KC_ISSUER_URI=http://localhost:8180/realms/meldestelle
|
||||
# Internal JWK Set URI (for service-to-service communication within Docker)
|
||||
KC_JWK_SET_URI=http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs
|
||||
|
||||
# --- PGADMIN ---
|
||||
PGADMIN_IMAGE=dpage/pgadmin4:8
|
||||
PGADMIN_EMAIL=meldestelle@mo-code.at
|
||||
|
|
@ -83,8 +89,6 @@ GATEWAY_SERVER_PORT=8081
|
|||
GATEWAY_SPRING_PROFILES_ACTIVE=docker
|
||||
GATEWAY_DEBUG=true
|
||||
GATEWAY_SERVICE_NAME=api-gateway
|
||||
SSEC_ISSUER_URI=http://keycloak:8080/realms/meldestelle
|
||||
SSEC_JWK_SET_URI=http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs
|
||||
GATEWAY_CONSUL_HOSTNAME=api-gateway
|
||||
GATEWAY_CONSUL_PREFER_IP=true
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@
|
|||
"oauth2DeviceCodeLifespan": 600,
|
||||
"oauth2DevicePollingInterval": 5,
|
||||
"internationalizationEnabled": true,
|
||||
"supportedLocales": ["de", "en"],
|
||||
"supportedLocales": [
|
||||
"de",
|
||||
"en"
|
||||
],
|
||||
"defaultLocale": "de",
|
||||
"roles": {
|
||||
"realm": [
|
||||
|
|
@ -52,6 +55,12 @@
|
|||
"composite": false,
|
||||
"clientRole": false
|
||||
},
|
||||
{
|
||||
"name": "MELD_USER",
|
||||
"description": "Verified user role (Technical placeholder for REITER)",
|
||||
"composite": false,
|
||||
"clientRole": false
|
||||
},
|
||||
{
|
||||
"name": "MONITORING",
|
||||
"description": "Monitoring role for system health checks",
|
||||
|
|
@ -195,6 +204,26 @@
|
|||
"attributes": {
|
||||
"pkce.code.challenge.method": "S256"
|
||||
}
|
||||
},
|
||||
{
|
||||
"clientId": "postman-client",
|
||||
"name": "Postman Test Client",
|
||||
"description": "Confidential client for backend testing via Postman",
|
||||
"enabled": true,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"secret": "postman-secret-123",
|
||||
"redirectUris": [
|
||||
"https://oauth.pstmn.io/v1/callback"
|
||||
],
|
||||
"webOrigins": [],
|
||||
"protocol": "openid-connect",
|
||||
"bearerOnly": false,
|
||||
"publicClient": false,
|
||||
"standardFlowEnabled": false,
|
||||
"implicitFlowEnabled": false,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"serviceAccountsEnabled": false,
|
||||
"fullScopeAllowed": true
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
|
|
@ -212,15 +241,26 @@
|
|||
"temporary": true
|
||||
}
|
||||
],
|
||||
"realmRoles": ["ADMIN", "USER"],
|
||||
"realmRoles": [
|
||||
"ADMIN",
|
||||
"USER",
|
||||
"MELD_USER"
|
||||
],
|
||||
"clientRoles": {
|
||||
"api-gateway": ["ADMIN"]
|
||||
"api-gateway": [
|
||||
"ADMIN"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"groups": [],
|
||||
"defaultRoles": ["USER", "GUEST"],
|
||||
"requiredCredentials": ["password"],
|
||||
"defaultRoles": [
|
||||
"USER",
|
||||
"GUEST"
|
||||
],
|
||||
"requiredCredentials": [
|
||||
"password"
|
||||
],
|
||||
"passwordPolicy": "length(8)",
|
||||
"otpPolicyType": "totp",
|
||||
"otpPolicyAlgorithm": "HmacSHA1",
|
||||
|
|
@ -228,12 +268,20 @@
|
|||
"otpPolicyDigits": 6,
|
||||
"otpPolicyLookAheadWindow": 1,
|
||||
"otpPolicyPeriod": 30,
|
||||
"otpSupportedApplications": ["FreeOTP", "Google Authenticator"],
|
||||
"otpSupportedApplications": [
|
||||
"FreeOTP",
|
||||
"Google Authenticator"
|
||||
],
|
||||
"webAuthnPolicyRpEntityName": "meldestelle",
|
||||
"webAuthnPolicySignatureAlgorithms": ["ES256", "RS256"],
|
||||
"webAuthnPolicySignatureAlgorithms": [
|
||||
"ES256",
|
||||
"RS256"
|
||||
],
|
||||
"smtpServer": {},
|
||||
"eventsEnabled": true,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": [
|
||||
"jboss-logging"
|
||||
],
|
||||
"enabledEventTypes": [
|
||||
"LOGIN",
|
||||
"LOGIN_ERROR",
|
||||
|
|
@ -254,7 +302,9 @@
|
|||
"providerId": "rsa-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"priority": ["100"]
|
||||
"priority": [
|
||||
"100"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -262,8 +312,12 @@
|
|||
"providerId": "hmac-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"priority": ["100"],
|
||||
"algorithm": ["HS256"]
|
||||
"priority": [
|
||||
"100"
|
||||
],
|
||||
"algorithm": [
|
||||
"HS256"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -271,7 +325,9 @@
|
|||
"providerId": "aes-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"priority": ["100"]
|
||||
"priority": [
|
||||
"100"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ services:
|
|||
DEBUG: "${GATEWAY_DEBUG:-true}"
|
||||
|
||||
# --- KEYCLOAK ---
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "${SSEC_ISSUER_URI:-http://keycloak:8080/realms/meldestelle}"
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "${SSEC_JWK_SET_URI:-http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs}"
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "${KC_ISSUER_URI}"
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "${KC_JWK_SET_URI}"
|
||||
|
||||
# --- CONSUL ---
|
||||
SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}"
|
||||
|
|
@ -98,6 +98,10 @@ services:
|
|||
DEBUG: "${PING_DEBUG:-true}"
|
||||
SERVER_PORT: "${PING_SERVER_PORT:-8082}"
|
||||
|
||||
# --- KEYCLOAK ---
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "${KC_ISSUER_URI}"
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "${KC_JWK_SET_URI}"
|
||||
|
||||
# --- CONSUL ---
|
||||
SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}"
|
||||
SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}"
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ Damit wir testen können, brauchen wir einen User und einen Client in Keycloak,
|
|||
* **Realm:** Wähle oben links `meldestelle` aus (wurde beim Start importiert).
|
||||
|
||||
**Check:**
|
||||
* **User:** Gibt es einen User? (z.B. `testuser` / `password` mit Rolle `MELD_USER`)?
|
||||
* *Falls nicht:* Lege schnell einen User an, setze Credentials (temporary: off) und weise ihm unter "Role Mapping" die Rolle `MELD_USER` zu.
|
||||
* **Client:** Gibt es einen Client? (z.B. `meldestelle-frontend` oder `postman`)?
|
||||
* *Falls nicht:* Lege einen Client `postman` an.
|
||||
* Access Type: `public` (oder `confidential` wenn du Client Secret nutzen willst, public reicht für Postman oft).
|
||||
* Valid Redirect URIs: `*` (für Tests ok) oder `https://oauth.pstmn.io/v1/callback`.
|
||||
* **User:** Der Standard-Admin User `admin` hat bereits die notwendige Rolle `MELD_USER`.
|
||||
* **Client:** Es gibt einen dedizierten Test-Client `postman-client`.
|
||||
* Client ID: `postman-client`
|
||||
* Client Secret: `postman-secret-123`
|
||||
* Access Type: `confidential`
|
||||
* Direct Access Grants: `Enabled`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -42,9 +42,10 @@ Erstelle eine neue Collection "Meldestelle Ping Test".
|
|||
Setze folgende Variablen in Postman (Environment "Local Docker"):
|
||||
* `gateway_url`: `http://localhost:8081`
|
||||
* `auth_url`: `http://localhost:8180/realms/meldestelle/protocol/openid-connect/token`
|
||||
* `client_id`: `meldestelle-frontend` (oder wie dein Client heißt)
|
||||
* `username`: `testuser` (dein User)
|
||||
* `password`: `password` (dein Passwort)
|
||||
* `client_id`: `postman-client`
|
||||
* `client_secret`: `postman-secret-123`
|
||||
* `username`: `admin`
|
||||
* `password`: `Change_Me_In_Production!`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -108,6 +109,7 @@ Wir testen nun die verschiedenen Endpunkte und Sicherheitsstufen.
|
|||
* Grant Type: `Password Credentials`
|
||||
* Access Token URL: `{{auth_url}}`
|
||||
* Client ID: `{{client_id}}`
|
||||
* Client Secret: `{{client_secret}}`
|
||||
* Username: `{{username}}`
|
||||
* Password: `{{password}}`
|
||||
* Klick auf "Get New Access Token".
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
type: Redirect
|
||||
status: ARCHIVED
|
||||
---
|
||||
|
||||
# MOVED
|
||||
|
||||
This documentation has been superseded.
|
||||
Please refer to: [Ping Service Reference](PingService_Reference.md)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
type: Redirect
|
||||
status: ARCHIVED
|
||||
---
|
||||
|
||||
# MOVED
|
||||
|
||||
This documentation has been superseded.
|
||||
Please refer to: [Ping Service Reference](PingService_Reference.md)
|
||||
79
docs/07_Infrastructure/guides/jwt-in-docker.md
Normal file
79
docs/07_Infrastructure/guides/jwt-in-docker.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: DevOps Engineer
|
||||
tags: [jwt, oidc, keycloak, docker, networking, security]
|
||||
---
|
||||
|
||||
# Leitfaden: JWT-Validierung in der Docker-Umgebung
|
||||
|
||||
Dieser Leitfaden erklärt eine kritische Herausforderung und deren Lösung bei der Arbeit mit Keycloak (OIDC) und Microservices in einer Docker-Umgebung.
|
||||
|
||||
## Das Problem: Das "Split Horizon"-Dilemma
|
||||
|
||||
In unserer lokalen Docker-Umgebung existieren zwei "Sichtweisen" (Horizons) auf Keycloak:
|
||||
|
||||
1. **Die externe Sicht (Browser/Postman):** Ein Client außerhalb von Docker (z.B. Postman) greift auf Keycloak über `http://localhost:8180` zu. Wenn dieser Client ein Token anfordert, stellt Keycloak dieses Token mit dem Issuer (`iss`) Claim `http://localhost:8180/realms/meldestelle` aus.
|
||||
|
||||
2. **Die interne Sicht (Microservices):** Ein Service innerhalb des Docker-Netzwerks (z.B. `ping-service`) kann `localhost` nicht verwenden, um Keycloak zu erreichen. Er muss den Docker-internen Hostnamen `http://keycloak:8080` verwenden.
|
||||
|
||||
Wenn der Service nun das von außen kommende Token validieren will, passiert Folgendes:
|
||||
* Das Token sagt: "Ich wurde von `http://localhost:8180/realms/meldestelle` ausgestellt."
|
||||
* Die Standardkonfiguration des Services sagt: "Ich vertraue aber nur Token von `http://keycloak:8080/realms/meldestelle`."
|
||||
* **Ergebnis:** Die Validierung schlägt mit `iss claim is not valid` fehl.
|
||||
|
||||
## Die Lösung: Getrennte Konfiguration von Issuer und JWK-Pfad
|
||||
|
||||
Die Lösung besteht darin, Spring Security so zu konfigurieren, dass es zwischen der **Validierung des Issuers** und dem **technischen Abruf der Schlüssel** unterscheidet.
|
||||
|
||||
* `spring.security.oauth2.resourceserver.jwt.issuer-uri`: **Muss exakt mit dem `iss`-Claim im Token übereinstimmen.** Hier verwenden wir die öffentliche URL.
|
||||
* `spring.security.oauth2.resourceserver.jwt.jwk-set-uri`: Der technische Endpunkt, unter dem der Service die Public Keys (JWKs) zur Signaturprüfung abruft. Hier verwenden wir die interne Docker-URL.
|
||||
|
||||
### Implementierung
|
||||
|
||||
Wir steuern dies zentral über unsere `.env`-Datei, um eine "Single Source of Truth" zu haben:
|
||||
|
||||
**`.env`**
|
||||
```dotenv
|
||||
# Public Issuer URI (must match the token issuer from browser/postman)
|
||||
KC_ISSUER_URI=http://localhost:8180/realms/meldestelle
|
||||
|
||||
# Internal JWK Set URI (for service-to-service communication within Docker)
|
||||
KC_JWK_SET_URI=http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs
|
||||
```
|
||||
|
||||
Diese Variablen werden dann in der `dc-backend.yaml` an die Services durchgereicht:
|
||||
|
||||
**`dc-backend.yaml`**
|
||||
```yaml
|
||||
services:
|
||||
ping-service:
|
||||
environment:
|
||||
# ...
|
||||
# --- KEYCLOAK ---
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "${KC_ISSUER_URI}"
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "${KC_JWK_SET_URI}"
|
||||
```
|
||||
|
||||
Diese Konfiguration stellt sicher, dass die Validierung erfolgreich ist, während die Kommunikation innerhalb des Docker-Netzwerks korrekt funktioniert.
|
||||
|
||||
---
|
||||
|
||||
## Empfehlungen für die Entwickler
|
||||
|
||||
### Für den Senior Backend Developer
|
||||
|
||||
1. **Konfigurations-Muster übernehmen:** Wenn du neue Microservices erstellst, kopiere exakt den `environment`-Block für Keycloak aus dem `ping-service` in der `dc-backend.yaml`. Das stellt sicher, dass die JWT-Validierung von Anfang an korrekt funktioniert.
|
||||
2. **Rollen-Synchronisation:** Bevor du einen Endpunkt mit `@PreAuthorize("hasRole('DEINE_ROLLE')")` sicherst, stelle sicher, dass die Rolle `DEINE_ROLLE` auch in der `config/docker/keycloak/meldestelle-realm.json` definiert ist.
|
||||
3. **Debugging-Tipp:** Bei einem `401 Unauthorized` auf einem geschützten Endpunkt, prüfe immer zuerst die Logs des Services. Die Fehlermeldungen von Spring Security sind sehr aussagekräftig (z.B. `iss claim not valid`, `Connection refused`, `An error occurred while attempting to decode the Jwt`).
|
||||
|
||||
### Für den KMP Frontend Expert
|
||||
|
||||
1. **Stabiler Auth-Flow:** Die Authentifizierung ist jetzt stabil. Du kannst dich auf die Implementierung des Login-Prozesses konzentrieren.
|
||||
2. **Client-Konfiguration:** Der `web-app` Client in Keycloak ist für dich vorbereitet. Er ist ein **public client** und nutzt den sicheren **Authorization Code Flow mit PKCE**. Du musst eine OIDC-Client-Bibliothek verwenden, die diesen Flow unterstützt (z.B. `keycloak-js` oder eine moderne Alternative wie `oidc-client-ts`).
|
||||
3. **Benötigte Konfiguration:** Dein Frontend benötigt folgende Informationen, die du am besten in einer Environment-Datei ablegst:
|
||||
* Keycloak URL: `http://localhost:8180`
|
||||
* Realm: `meldestelle`
|
||||
* Client ID: `web-app`
|
||||
4. **Keine Secrets im Frontend:** Der `web-app` Client hat absichtlich **kein** Secret. Versuche niemals, ein Secret im Frontend-Code zu speichern oder zu verwenden. PKCE sorgt hier für die nötige Sicherheit.
|
||||
5. **Token-Handling:** Nach dem Login erhältst du ein **Access Token**. Dieses muss bei jeder API-Anfrage an das Backend (`http://localhost:8081/api/...`) im `Authorization: Bearer <token>` HTTP-Header mitgesendet werden.
|
||||
44
docs/99_Journal/2026-01-22_Session_Log.md
Normal file
44
docs/99_Journal/2026-01-22_Session_Log.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
type: Journal
|
||||
status: COMPLETED
|
||||
owner: Curator
|
||||
date: 2026-01-22
|
||||
participants:
|
||||
- DevOps & Infrastructure Engineer
|
||||
- Owner (Stefan)
|
||||
---
|
||||
|
||||
# Session Log: 22. Jänner 2026
|
||||
|
||||
## Zielsetzung
|
||||
Analyse der Infrastruktur, Behebung von Authentifizierungs-Problemen (JWT/Keycloak) im Docker-Netzwerk und Validierung der "Tracer Bullet" Architektur (Ping-Service).
|
||||
|
||||
## Durchgeführte Arbeiten
|
||||
|
||||
### 1. Infrastruktur & IAM (Keycloak)
|
||||
* **Analyse:** Diskrepanz zwischen Dokumentation (`Testing_with_Postman.md`) und Konfiguration (`meldestelle-realm.json`) festgestellt.
|
||||
* **Fix (Realm):**
|
||||
* Neue Rolle `MELD_USER` als technischer Platzhalter für verifizierte Benutzer eingeführt.
|
||||
* Neuen Confidential Client `postman-client` erstellt, um saubere Backend-Tests via Password-Grant zu ermöglichen, ohne den Frontend-Client unsicher zu machen.
|
||||
* Dem User `admin` die Rolle `MELD_USER` zugewiesen.
|
||||
* **Fix (Networking/JWT):**
|
||||
* Das "Split Horizon"-Problem identifiziert (Token Issuer `localhost` vs. Docker-interner Keycloak-Host).
|
||||
* `.env` Datei refactored: Trennung in `KC_ISSUER_URI` (Public) und `KC_JWK_SET_URI` (Internal).
|
||||
* `dc-backend.yaml` aktualisiert: `api-gateway` und `ping-service` nutzen nun diese expliziten Variablen.
|
||||
|
||||
### 2. Dokumentation (Single Source of Truth)
|
||||
* **Update:** `docs/05_Backend/Guides/Testing_with_Postman.md` an den neuen `postman-client` angepasst.
|
||||
* **Neu:** `docs/07_Infrastructure/guides/jwt-in-docker.md` erstellt. Dieses Dokument erklärt das "Split Horizon"-Problem und dient als Referenz für Frontend- und Backend-Entwickler.
|
||||
|
||||
### 3. Testing
|
||||
* Erfolgreicher Durchlauf aller Postman-Tests (Connectivity, Health, Security Block, Auth Login, Security Access).
|
||||
* Bestätigung, dass der `ping-service` nun korrekt Token validiert, die von außen (Postman) kommen, aber intern (Docker) geprüft werden.
|
||||
|
||||
## Ergebnisse
|
||||
* Die lokale Entwicklungsumgebung ist nun **vollständig funktionsfähig** und **auth-ready**.
|
||||
* Die Infrastruktur-Konfiguration ist sauberer und expliziter (Trennung von Public/Internal URLs).
|
||||
* Eine solide Basis für die Frontend-Login-Implementierung ist geschaffen.
|
||||
|
||||
## Nächste Schritte
|
||||
* **Frontend:** Implementierung des Login-Flows (Authorization Code Flow mit PKCE) unter Nutzung des `web-app` Clients.
|
||||
* **Backend:** Beginn der Modellierung der **Events Domain** (Veranstaltungen).
|
||||
Loading…
Reference in New Issue
Block a user