optimierungen auth-Modul und cache-Modul

This commit is contained in:
stefan
2025-09-03 15:19:11 +02:00
parent abd2543caf
commit 63a1b97db7
16 changed files with 578 additions and 797 deletions
+88 -8
View File
@@ -1,4 +1,4 @@
# Infrastructure/Auth Module - Comprehensive Documentation
# Infrastructure/Auth Modul Aktuelle Dokumentation (Stand: September 2025)
## Überblick
@@ -6,6 +6,21 @@ Das **Auth-Modul** ist die zentrale Komponente für die gesamte Authentifizierun
Als Identity Provider wird **Keycloak** verwendet. Dieses Modul kapselt die gesamte Interaktion mit Keycloak und stellt dem Rest des Systems eine einheitliche und vereinfachte Sicherheitsschicht zur Verfügung.
## Aufgabe des Moduls
- Zentrale Bereitstellung von Authentifizierungs- und Autorisierungsfunktionen für alle Services
- Minimierung der Kopplung an Keycloak durch eine API/Client-Abstraktion (`auth-client`)
- Einheitliche, typsichere Repräsentation von Rollen und Berechtigungen als Enums
- Sichere Erzeugung, Validierung und Auswertung von JWTs (Issuer, Audience, Ablauf, Signatur)
- Bereitstellung eines dedizierten Auth-Servers für Benutzer-Workflows (Login, optional Passwortänderung, Token-Ausstellung)
## Umsetzung (High-Level)
- Authentifizierung findet gegen Keycloak statt; der `auth-server` kapselt dessen Aufrufe.
- Nach erfolgreicher Authentifizierung wird ein signiertes JWT erzeugt, das Rollen/Berechtigungen enthält.
- Downstream-Services validieren das JWT über den `auth-client` und führen autorisierte Domänenaktionen aus.
- Das API-Gateway kann JWTs vorvalidieren und Metadaten-Header weitergeben; vollständige Validierung sollte via `auth-client` erfolgen.
## Architektur
Das Auth-Modul ist in zwei spezialisierte Komponenten aufgeteilt, um eine klare Trennung der Verantwortlichkeiten zu gewährleisten:
@@ -20,12 +35,17 @@ infrastructure/auth/
Dieses Modul ist eine **wiederverwendbare Bibliothek** und kein eigenständiger Service. Es enthält die gesamte Logik, die andere Microservices (wie `masterdata-service`, `members-service` etc.) benötigen, um ihre Endpunkte abzusichern.
**Hauptaufgaben:**
* **JWT-Management:** Stellt einen `JwtService` zur Erstellung und Validierung von JSON Web Tokens bereit.
* **Modell-Definition:** Definiert die **Quelle der Wahrheit** für sicherheitsrelevante Konzepte wie `RolleE` und `BerechtigungE` als typsichere Kotlin-Enums. Dies stellt sicher, dass alle Services dieselbe "Sprache" für Berechtigungen sprechen.
* **Schnittstellen:** Bietet saubere Schnittstellen wie `AuthenticationService` an, die von der konkreten Implementierung (z.B. Keycloak) abstrahieren.
Aktueller Stand (09/2025):
- Enthält ein typensicheres Rollen- und Berechtigungsmodell: `RolleE`, `BerechtigungE` (kotlinx.serialization-annotiert für konsistente JSON-Serialisierung).
- Definiert die Schnittstelle `AuthenticationService` mit suspend-Funktionen und Result-Typen zur Authentifizierung und Passwortänderung. Rückgabewerte sind versiegelt (sealed) und decken Success/Failure/Locked ab. Dadurch klare, explizite Fehlerfälle ohne Exceptions in Kontrollflüssen.
- Stellt den `JwtService` bereit, der via Spring konfiguriert werden kann und in Services zur Token-Erzeugung/-Validierung genutzt wird.
Jeder Microservice, der geschützte Endpunkte anbietet, bindet dieses Modul als Abhängigkeit ein.
**Hauptaufgaben:**
* **JWT-Management:** Stellt einen `JwtService` zur Erstellung und Validierung von JSON Web Tokens bereit (Signatur, Claims, Ablaufzeiten). Neue, result-basierte APIs erleichtern das Fehler-Handling.
* **Modell-Definition:** Definiert die **Quelle der Wahrheit** für sicherheitsrelevante Konzepte wie `RolleE` und `BerechtigungE` als typsichere Kotlin-Enums. Dies stellt sicher, dass alle Services dieselbe "Sprache" für Berechtigungen sprechen.
* **Schnittstellen:** Bietet saubere Schnittstellen wie `AuthenticationService` an, die von der konkreten Implementierung (z.B. Keycloak) abstrahieren. Dadurch können Implementierungen im `auth-server` oder in Tests (Mocks/Fakes) ausgetauscht werden.
Einbindung: Jeder Microservice, der geschützte Endpunkte anbietet, bindet dieses Modul als Abhängigkeit ein.
### `auth-server`
@@ -36,6 +56,42 @@ Dies ist ein **eigenständiger Spring Boot Microservice**, der als Brücke zwisc
* **Token-Endpunkte:** Ist verantwortlich für das Ausstellen von Tokens nach einer erfolgreichen Authentifizierung.
* **Implementierung der `AuthenticationService`-Schnittstelle:** Enthält die konkrete Logik, die gegen Keycloak prüft, ob ein Benutzername und ein Passwort korrekt sind.
**Konfiguration (AuthServerConfiguration):**
Der Service stellt einen konfigurierbaren `JwtService` per Spring-Bean bereit. Die dazugehörigen Properties werden über `auth.jwt.*` gesetzt:
```yaml
auth:
jwt:
secret: <32+ Zeichen starkes Secret>
issuer: meldestelle-auth-server
audience: meldestelle-services
expiration: 60 # Minuten
```
Kotlin-Konfiguration (vereinfacht):
```kotlin
@Configuration
@EnableConfigurationProperties(JwtProperties::class)
class AuthServerConfiguration {
@Bean
fun jwtService(props: JwtProperties) = JwtService(
secret = props.secret,
issuer = props.issuer,
audience = props.audience,
expiration = props.expiration.minutes
)
@ConfigurationProperties(prefix = "auth.jwt")
data class JwtProperties(
val secret: String,
val issuer: String,
val audience: String,
val expiration: Long
)
}
```
Hinweis: Standardwerte sind nur für lokale Entwicklung gedacht und müssen in Produktion überschrieben werden. Zusätzlich validiert der Auth-Server die JWT-Properties: Secret min. 32 Zeichen, issuer/audience nicht leer; bei Verwendung des Default-Secrets wird eine Laufzeit-Warnung ausgegeben.
## Zusammenspiel im System
1. Ein **Benutzer** meldet sich über eine Client-Anwendung am **`auth-server`** an.
@@ -43,10 +99,11 @@ Dies ist ein **eigenständiger Spring Boot Microservice**, der als Brücke zwisc
3. Bei Erfolg erstellt der `auth-server` mit dem `JwtService` aus dem `auth-client` ein JWT, das die Berechtigungen des Benutzers enthält, und sendet es an den Client zurück.
4. Der **Client** sendet eine Anfrage an einen anderen Microservice (z.B. `members-service`) und fügt das JWT als Bearer-Token in den Header ein.
5. Der **`members-service`**, der ebenfalls den `auth-client` als Abhängigkeit hat, nutzt den `JwtService`, um das Token zu validieren und die Berechtigungen typsicher auszulesen.
6. Das **Gateway** kann vorgelagert JWT-basierte Authentifizierung durchführen. Aktuell existiert ein `JwtAuthenticationFilter`, der über `gateway.security.jwt.enabled=true` aktiviert wird. In der vorliegenden Codebasis nutzt dieser noch eine vereinfachte Validierung; die geplante Integration ist die Nutzung des `auth-client` zur vollständigen Validierung und Claim-Extraktion.
Diese Architektur entkoppelt die Fach-Services von der Komplexität der Identitätsverwaltung und schafft eine robuste, zentrale Sicherheitsinfrastruktur.
## Modernisierungen (August 2025)
## Modernisierungen (September 2025)
### Technische Verbesserungen
@@ -68,6 +125,21 @@ Diese Architektur entkoppelt die Fach-Services von der Komplexität der Identit
- Entfernung von `Thread.sleep()` für zuverlässigere Tests
- Bessere Expired-Token-Tests mit eindeutigen Zeitstempel-Differenzen
### Token Claims und Struktur
Empfohlene Claims im JWT (Beispiel):
- sub: Benutzer-ID (UUID)
- pid: Personen-ID (UUID)
- preferred_username: Loginname (derzeit intern als Claim "username" umgesetzt)
- email: E-Mail-Adresse
- roles: Liste von Rollen (`RolleE`)
- perms: Liste von Berechtigungen (`BerechtigungE`)
- iss: Issuer (z.B. meldestelle-auth-server)
- aud: Audience (z.B. meldestelle-services)
- iat/exp: Ausstellungs- und Ablaufzeitpunkt
Diese Claims werden vom `auth-client` gelesen und in typsichere Modelle abgebildet.
### API-Änderungen
**Neue Result-basierte APIs:**
@@ -255,6 +327,14 @@ Das Auth-Modul wurde von **kritisch untergetestet** auf **umfassend getestet** t
└── kotlinx-serialization-json (JSON Serialization)
```
## Aktualitäts-Check (Repo-Stand September 2025)
- `auth-client` enthält `AuthenticationService` mit suspend-Funktionen und versiegelten Result-Typen (Success/Failure/Locked; PasswordChangeResult inkl. WeakPassword). Diese Schnittstelle ist in dieser README beschrieben und aktuell.
- `auth-server` stellt `JwtService` via `AuthServerConfiguration` bereit und liest Properties unter `auth.jwt.*`. Beispielkonfiguration ist oben dokumentiert und entspricht dem Code.
- Das API-Gateway besitzt einen `JwtAuthenticationFilter`, der derzeit eine vereinfachte Tokenprüfung implementiert. Geplante nächste Stufe: Verwendung des `auth-client` zur echten JWT-Validierung und Claim-Extraktion. Property-Schalter: `gateway.security.jwt.enabled`.
Diese README wurde am 03.09.2025 aktualisiert und spiegelt den aktuellen Stand der Implementierung wider.
## Production-Readiness Status
### ✅ Production-Ready Bereiche
@@ -305,7 +385,7 @@ Das infrastructure/auth Modul ist **production-ready** und umfassend modernisier
Die Transformation von "kritisch untergetestet" zu "production-ready" ist vollständig abgeschlossen und erfüllt alle Anforderungen für ein sicherheitskritisches Authentifizierungs-System in einer Microservice-Landschaft.
---
**Letzte Aktualisierung**: 15. August 2025
**Letzte Aktualisierung**: 3. September 2025
**Status**: Production-Ready mit umfassender Test-Abdeckung
**Dokumentation**: Vollständig konsolidiert aus allen Teilbereichen
**Validierung**: Sicherheitstests erfolgreich bestanden (15.08.2025)
@@ -9,6 +9,21 @@ plugins {
alias(libs.plugins.spring.dependencyManagement)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
tasks.test {
useJUnitPlatform()
}
java {
withJavadocJar()
withSourcesJar()
}
dependencies {
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
@@ -5,37 +5,37 @@ import com.benasher44.uuid.Uuid
import java.time.LocalDateTime
/**
* Service for user authentication and password management.
* Service für Benutzerauthentifizierung und Passwortverwaltung.
*/
interface AuthenticationService {
/**
* Authenticates a user with the given username and password.
* Authentifiziert einen Benutzer mit Benutzernamen und Passwort.
*
* @param username The username
* @param password The password
* @return The authentication result
* @param username Der Benutzername
* @param password Das Passwort
* @return Das Authentifizierungsergebnis
*/
suspend fun authenticate(username: String, password: String): AuthResult
/**
* Changes a user's password.
* Ändert das Passwort eines Benutzers.
*
* @param userId The user ID
* @param currentPassword The current password
* @param newPassword The new password
* @return The password change result
* @param userId Die Benutzer-ID
* @param currentPassword Das aktuelle Passwort
* @param newPassword Das neue Passwort
* @return Das Ergebnis der Passwortänderung
*/
suspend fun changePassword(userId: Uuid, currentPassword: String, newPassword: String): PasswordChangeResult
/**
* Possible results of an authentication attempt.
* Mögliche Ergebnisse eines Authentifizierungsversuchs.
*/
sealed class AuthResult {
/**
* Authentication was successful.
* Authentifizierung war erfolgreich.
*
* @param token The JWT token
* @param user The authenticated user
* @param token Das JWT-Token
* @param user Der authentifizierte Benutzer
*/
data class Success(val token: String, val user: AuthenticatedUser) : AuthResult()
@@ -23,6 +23,12 @@ class JwtService(
) {
private val logger = KotlinLogging.logger {}
init {
require(secret.length >= 32) { "JWT secret must be at least 32 characters for HMAC512" }
require(issuer.isNotBlank()) { "JWT issuer must not be blank" }
require(audience.isNotBlank()) { "JWT audience must not be blank" }
}
private val algorithm = Algorithm.HMAC512(secret)
private val verifier = JWT.require(algorithm)
.withIssuer(issuer)
@@ -47,7 +47,7 @@ class JwtServiceTest {
@Test
fun `validateToken should return false for token with wrong secret`() {
// Arrange
val otherService = JwtService("a-different-wrong-secret", testIssuer, testAudience)
val otherService = JwtService("a-different-wrong-secret-that-is-long-enough-1234567890", testIssuer, testAudience)
val token = otherService.generateToken("user-123", "test", emptyList())
// Act & Assert
@@ -9,6 +9,12 @@ plugins {
alias(libs.plugins.spring.dependencyManagement)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
// Konfiguriert die Hauptklasse für das ausführbare JAR.
springBoot {
mainClass.set("at.mocode.infrastructure.auth.AuthServerApplicationKt")
@@ -5,21 +5,29 @@ import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.validation.annotation.Validated
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
import kotlin.time.Duration.Companion.minutes
/**
* Spring configuration for the Auth Server module.
* Provides the necessary beans and configuration for JWT handling and authentication.
* Spring-Konfiguration für das Auth-Server-Modul.
* Stellt die notwendigen Beans und Einstellungen für JWT-Verarbeitung und Authentifizierung bereit.
*/
@Configuration
@EnableConfigurationProperties(AuthServerConfiguration.JwtProperties::class)
class AuthServerConfiguration {
/**
* Creates a JwtService bean with configuration from application properties.
* Erstellt einen JwtService-Bean mit Konfiguration aus den Application Properties.
*/
@Bean
fun jwtService(jwtProperties: JwtProperties): JwtService {
// Basic safeguard: warn if default secret is used
if (jwtProperties.secret == "default-secret-for-development-only-please-change-in-production") {
System.err.println("[SECURITY WARNING] Using default JWT secret DO NOT use this in production!")
}
return JwtService(
secret = jwtProperties.secret,
issuer = jwtProperties.issuer,
@@ -29,13 +37,19 @@ class AuthServerConfiguration {
}
/**
* Configuration properties for JWT settings.
* Konfigurationseigenschaften für JWT-Einstellungen.
*/
@ConfigurationProperties(prefix = "auth.jwt")
@Validated
data class JwtProperties(
@field:NotBlank
@field:Size(min = 32, message = "JWT secret must be at least 32 characters for HMAC512")
val secret: String = "default-secret-for-development-only-please-change-in-production",
@field:NotBlank
val issuer: String = "meldestelle-auth-server",
@field:NotBlank
val audience: String = "meldestelle-services",
@field:Min(1)
val expiration: Long = 60 // minutes
)
}
-2
View File
@@ -1,2 +0,0 @@
// Infrastructure Auth Module Container
// This is a container module for authentication-related subprojects