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

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)

View File

@ -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.

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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
)
}

View File

@ -1,2 +0,0 @@
// Infrastructure Auth Module Container
// This is a container module for authentication-related subprojects

View File

@ -1,495 +1,114 @@
# Infrastructure/Cache Module - Comprehensive Documentation
# Infrastructure/Cache Modulbeschreibung und Implementierungsleitfaden
*Letzte Aktualisierung: 15. August 2025*
Letzte Aktualisierung: 03. September 2025
## Überblick
## Zweck und Aufgaben des Moduls
Das Infrastructure/Cache-Modul stellt eine einheitliche, technologieneutrale CacheSchnittstelle für alle Services bereit und liefert mit einer Redisbasierten AdapterImplementierung die produktionsreife Ausführung. Ziele:
- Antwortzeiten reduzieren und Primärdatenbanken entlasten.
- Einheitliche API für Lesen/Schreiben, BatchOperationen und TTLs.
- Resilienz bei RedisAusfällen durch lokalen Fallback.
- Operative Transparenz durch einfache Metriken, HealthInformationen und periodische Wartungsaufgaben.
Das **Cache-Modul** stellt eine zentrale, hochverfügbare und produktionsbereite Caching-Infrastruktur für alle Microservices bereit. Es dient der Verbesserung der Anwendungsperformance, der Reduzierung von Latenzen und der Entlastung der primären PostgreSQL-Datenbank.
## Architektur (PortAdapter)
- cacheapi: enthält die öffentlichen Verträge und Basistypen
- DistributedCache: zentrale PortSchnittstelle für CacheOperationen
- CacheEntry, CacheConfiguration, CacheSerializer
- ConnectionStatusTracker/ConnectionStateListener zur Verbindungsüberwachung
- rediscache: Adapter, der die PortSchnittstelle mit Spring Data Redis umsetzt
- RedisDistributedCache: konkrete Implementierung inkl. OfflineFallback, DirtySync, Batchs, KeyPrefixing, TTLHandling und einfachen Metriken
- JacksonCacheSerializer: serialisiert Werte und CacheEntry per Jackson
**Status: ✅ PRODUKTIONSBEREIT** - Vollständig getestet mit 39 Tests (94.7% Success Rate)
## Öffentliche API (Auszug)
DistributedCache
- get(key, clazz)/set(key, value, ttl?)
- delete(key), exists(key)
- multiGet(keys, clazz), multiSet(map, ttl?)
- multiDelete(keys)
- synchronize(keys?), markDirty(key), getDirtyKeys(), clear()
## Architektur: Port-Adapter-Muster
Idiomatic Kotlin Extensions
- cache.get<T>(key)
- cache.multiGet<T>(keys)
Das Modul folgt streng dem **Port-Adapter-Muster** (Hexagonale Architektur), um eine saubere Trennung zwischen der Caching-Schnittstelle (dem "Port") und der konkreten Implementierung (dem "Adapter") zu gewährleisten.
CacheConfiguration (DefaultCacheConfiguration vorhanden)
- defaultTtl?, localCacheMaxSize?, offlineModeEnabled, synchronizationInterval, offlineEntryMaxAge?, keyPrefix, compressionEnabled, compressionThreshold
### Module-Struktur
Hinweis: Die Kompression wird aktuell durch den Serializer bereitgestellt; Schwellwerte/Flags sind für zukünftiges Tuning vorgesehen.
* **`:infrastructure:cache:cache-api`**: Definiert den abstrakten "Vertrag" für das Caching (`DistributedCache`-Interface), ohne sich um die zugrunde liegende Technologie zu kümmern. Die Fach-Services programmieren ausschließlich gegen dieses Interface.
* **`:infrastructure:cache:redis-cache`**: Die konkrete Implementierung des Vertrags, die **Redis** als hochperformantes Caching-Backend verwendet. Kapselt die gesamte Redis-spezifische Logik.
## Implementierungsdetails (RedisDistributedCache)
- Lokaler Fallback: ConcurrentHashMap als lokaler Cache speichert CacheEntry inkl. expiresAt. Bei RedisAusfall werden Schreibvorgänge lokal gehalten und als „dirty“ markiert.
- DirtySynchronisation: Sobald die Verbindung wieder ONLINE ist, werden geänderte Schlüssel zu Redis synchronisiert (synchronize()).
- KeyPrefixing: Alle externen Keys werden mittels keyPrefix gekapselt, um Mandanten/Services zu isolieren.
- TTL/Expiration: TTL wird einheitlich über kotlin.time.Duration angegeben und für Redis in java.time.Duration konvertiert. Lokale Einträge enthalten expiresAt und werden periodisch bereinigt.
- BatchOperationen: multiGet/multiSet/multiDelete nutzen RedisBatching/Pipelining, lokal wird konsistent gespiegelt.
- Größenbegrenzung Local Cache (neu): Wenn localCacheMaxSize gesetzt ist, werden bei Überschreitung die am längsten nicht mehr modifizierten Einträge aus dem lokalen Cache entfernt (LRM least recently modified). Dadurch bleibt der lokale Fallback speichereffizient.
- Periodische Aufgaben (@Scheduled):
- Verbindungsprüfung: fixedDelayString = "${redis.connection-check-interval:10000}"
- Lokale Bereinigung: fixedDelayString = "${redis.local-cache-cleanup-interval:60000}"
- DirtySync: fixedDelayString = "${redis.sync-interval:300000}"
- MetrikenLog: fixedDelayString = "${redis.metrics-log-interval:300000}"
## Schlüsselfunktionen
Wichtige Robustheitsdetails
- Alle RedisOperationen fangen RedisConnectionFailureException ab und schalten den ConnectionState auf DISCONNECTED. Beim nächsten erfolgreichen Zugriff wird CONNECTED gesetzt und eine Synchronisation der dirty keys ausgelöst.
- multiSet setzt TTLs bei Bedarf per Pipeline nach (pExpire); einzelne setOperationen nutzen expire via Duration.
### Core Features
* **Offline-Fähigkeit & Resilienz:** Das Modul verfügt über einen In-Memory-Cache, der bei einem Ausfall der Redis-Verbindung als Fallback dient. Schreib-Operationen werden lokal als "dirty" markiert und automatisch mit Redis synchronisiert, sobald die Verbindung wiederhergestellt ist.
* **Idiomatische Kotlin-API:** Bietet neben der Standard-API auch ergonomische Erweiterungsfunktionen mit `reified`-Typen für eine saubere und typsichere Verwendung in Kotlin-Code (`cache.get<User>("key")`).
* **Projekweite Konsistenz:** Verwendet `kotlin.time.Duration` und `kotlin.time.Instant` für eine einheitliche Handhabung von Zeit- und Dauer-Angaben im gesamten Projekt.
* **Automatisierte Verbindungsüberwachung:** Überprüft periodisch den Zustand der Redis-Verbindung und informiert Listener über Statusänderungen (`CONNECTED`, `DISCONNECTED`).
### Enterprise Features
* **Multi-Tenant-Fähigkeit:** Key-Prefixes ermöglichen vollständige Isolation zwischen verschiedenen Anwendungen
* **Konfigurierbare Kompression:** Automatische Kompression für große Datenstrukturen (konfigurierbar ab 1KB)
* **Performance-Optimierung:** 5.000+ gleichzeitige Operationen mit >95% Erfolgsrate
* **Unicode-Vollunterstützung:** Internationale Deployment-fähig mit Emojis, Umlauten, Chinesisch, Arabisch
* **10MB+ Objektgrößen:** Automatische Kompression und Übertragung sehr großer Objekte
### Enhanced Monitoring & Operations (NEW)
* **Real-time Performance Metrics:** Automatisches Tracking aller Cache-Operationen mit Erfolgsraten
* **Strukturierte Metrics-Logging:** Periodische Performance-Reports mit detaillierten Metriken
* **Cache Warming Utilities:** Produktions-bereite Warming-Strategien für optimale Performance
* **Health Status Monitoring:** Umfassende Gesundheitschecks mit automatischer Status-Bewertung
* **Advanced Connection Tracking:** Erweiterte Verbindungsüberwachung mit detaillierten Zustandsinformationen
## Verwendung
Ein Microservice bindet `:infrastructure:cache:redis-cache` als Abhängigkeit ein und lässt sich das `DistributedCache`-Interface per Dependency Injection geben.
### Grundlegende Verwendung
## Verwendung (Beispiele)
Einbinden: Projekte hängen gegen :infrastructure:cache:redis-cache und injizieren DistributedCache.
Lesen/Schreiben mit TTL
```kotlin
@Service
class MasterdataService(
private val cache: DistributedCache // Nur das Interface wird verwendet!
) {
fun findCountryById(id: String): Country? {
val cacheKey = "country:$id"
// 1. Versuche, aus dem Cache zu lesen (typsicher und sauber)
val cachedCountry = cache.get<Country>(cacheKey)
if (cachedCountry != null) {
return cachedCountry
}
// 2. Wenn nicht im Cache, aus der DB lesen
val dbCountry = countryRepository.findById(id)
// 3. Ergebnis in den Cache schreiben für zukünftige Anfragen
dbCountry?.let {
cache.set(cacheKey, it, ttl = 1.hours) // Cache für 1 Stunde
}
return dbCountry
}
val user = cache.get<User>("user:42")
if (user == null) {
val loaded = userRepository.findById("42") ?: return null
cache.set("user:42", loaded, ttl = 1.hours)
}
```
### Erweiterte Verwendung
BatchLesezugriff
```kotlin
// Batch-Operationen für bessere Performance
val userIds = listOf("user:1", "user:2", "user:3")
val cachedUsers = cache.multiGet<User>(userIds)
// Bulk-Updates
val newUsers = mapOf(
"user:4" to User("Alice"),
"user:5" to User("Bob")
)
cache.multiSet(newUsers, ttl = 30.minutes)
// Connection-State-Monitoring
cache.registerConnectionListener(object : ConnectionStateListener {
override fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant) {
logger.info { "Cache connection state changed to: $newState" }
}
})
// Performance Monitoring (NEW)
val metrics = cache.getPerformanceMetrics()
logger.info { "Current performance: ${metrics["successRate"]} success rate, ${metrics["totalOperations"]} operations" }
// Health Status Checking (NEW)
val health = cache.getHealthStatus()
if (health["healthy"] as Boolean) {
logger.info { "Cache is healthy with ${health["successRate"]} success rate" }
} else {
logger.warn { "Cache health issue detected: ${health["connectionState"]}" }
}
// Cache Warming (NEW)
// Individual key warming with data loader
cache.warmCache(listOf("user:1", "user:2", "user:3")) { key ->
userService.loadUser(key.substringAfter(":"))
}
// Bulk cache warming
val preloadData = mapOf(
"config:app" to applicationConfig,
"config:features" to featureFlags
)
cache.warmCacheBulk(preloadData, ttl = 1.hours)
val ids = listOf("user:1", "user:2", "user:3")
val map = cache.multiGet<User>(ids)
```
## Test-Suite: Vollständige Produktionsabdeckung
### Test-Übersicht
- ✅ **39 Tests total** (12 Basis + 27 erweiterte Tests)
- ✅ **6 Test-Klassen** vollständig optimiert
- ✅ **94.7% Success Rate** (36/38 erfolgreich)
- ✅ **Professionelles SLF4J/kotlin-logging** durchgängig
### Test-Kategorien
| Kategorie | Tests | Zweck | Status |
|-----------|-------|-------|---------|
| **Basis-Funktionalität** | 12 | Core Cache Operations | ✅ Stabil |
| **Performance & Load** | 3 | Gleichzeitige Zugriffe, Speicherdruck, Bulk-Ops | ✅ Optimiert |
| **Edge Cases** | 6 | Serialisierung, große Daten, Unicode, null-Werte | ✅ Robust |
| **Resilience** | 6 | Timeouts, Verbindungsausfälle, Wiederverbindung | ✅ Resilient |
| **Configuration** | 6 | TTL, Kompression, Prefixes, Cache-Größen | ✅ Flexibel |
| **Integration** | 6 | Cross-Instance, Monitoring, Produktions-Szenarien | ✅ Produktionsready |
### Detaillierte Test-Abdeckung
#### Performance & Load Tests
- **`test cache performance with high concurrent access`**: 100 Coroutines mit je 50 Operationen (5.000 gleichzeitige Ops)
- **`test cache behavior under memory pressure`**: 500 Einträge mit kleinem Local-Cache (100)
- **`test bulk operations performance`**: 1000 Einträge mit multiSet/multiGet (1000+ Einträge/Sekunde)
#### Edge Cases & Error Handling
- **`test serialization with problematic objects`**: Zirkuläre Referenzen, tiefe Verschachtelung (50 Ebenen)
- **`test cache with extremely large values`**: 10MB Strings mit automatischer Kompression
- **`test special characters and unicode`**: Emojis, Umlaute, Chinesisch, Arabisch, gemischte Inhalte
- **`test cache with null and empty values`**: Leere Strings, null-Felder, leere Collections
- **`test complex nested objects`**: Verschachtelte Maps mit Listen und Metadaten
- **`test malformed data scenarios`**: Nicht-existierende Keys, gemischte Batch-Operationen
#### Resilience & Timeout Tests
- **`test connection timeout scenarios`**: 5-Sekunden-Delays simuliert, max. 10s Timeout
- **`test partial Redis failures`**: Intermittierende Ausfälle alle 3 Operationen
- **`test network partitioning simulation`**: Komplette Netzwerktrennung mit Offline-Mode
- **`test reconnection and synchronization`**: Automatische Wiederverbindung mit Dirty-Key-Sync
- **`test connection state listener notifications`**: Listener-Management und State-Tracking
- **`test Redis restart simulation`**: Neustart-Szenarien mit lokaler Pufferung
#### Configuration Tests
- **`test different cache configurations`**: Performance-, Storage- und Minimal-Configs
- **`test compression threshold behavior`**: 50-Byte-Schwelle konfigurierbar getestet
- **`test key prefix functionality`**: Vollständige Isolation zwischen "app1", "app2", ""
- **`test TTL configuration variations`**: null, 100ms, 30min TTLs flexibel konfigurierbar
- **`test offline mode configuration`**: Ein/Ausschalten des Offline-Modus
- **`test local cache size limits`**: 3 vs. unlimited vs. 1000 Einträge mit Redis-Fallback
#### Integration & Monitoring Tests
- **`test connection state listener functionality`**: Professionelles Listener-Management
- **`test different Redis configurations`**: Multi-Config-Isolation und Cross-Compatibility
- **`test cache warming scenarios`**: Bulk- (1000 Einträge <100ms), graduelle und selektive Vorwärmung
- **`test metrics and monitoring integration`**: State-Tracking, Dirty-Keys-Monitoring, Performance-Metriken
- **`test cross-instance synchronization`**: Multi-Instance-Datenaustausch mit kleinen Delays
- **`test production-like scenarios`**: User-Sessions (1000), Config-Caching, API-Responses (100)
### Produktionstauglichkeits-Validierung
#### ✅ **Performance-Benchmarks bestanden:**
- **5.000+ gleichzeitige Operationen** mit >95% Erfolgsrate
- **Sub-100ms Performance** für Standard-Operationen
- **1000+ Einträge/Sekunde** bei Bulk-Operationen
- **Cache-Warming: 1000 Einträge in <100ms** möglich
#### ✅ **Robustheit validiert:**
- **Graceful Degradation** bei allen Fehlersituationen
- **Automatische Wiederverbindung** mit Dirty-Key-Synchronisation
- **Speicher-effiziente** Local-Cache-Verwaltung mit Redis-Fallback
- **Cross-Instance-Synchronisation** zwischen Services funktionsfähig
#### ✅ **Enterprise-Features getestet:**
- **10MB+ Objektgrößen** mit automatischer Kompression
- **Unicode-Vollunterstützung** für internationale Deployments
- **Multi-Tenant-Fähigkeit** durch Key-Prefixes mit perfekter Isolation
- **Vollständige Offline-Fähigkeit** bei Redis-Ausfällen
## Logging-Architektur: Professionelle Standards
### Implementierte Standards
Das gesamte Modul verwendet professionelle SLF4J/kotlin-logging Standards:
BulkSchreiben
```kotlin
// Konsistentes Pattern in allen Klassen:
companion.object {
private val logger = KotlinLogging.logger {}
}
// Strukturierte Logging-Calls:
logger.info { "Cache operation completed with metrics: $metrics" }
logger.warn { "Connection state changed: $oldState -> $newState" }
logger.debug { "Processing batch of $size entries with config: $config" }
cache.multiSet(mapOf(
"cfg:app" to appConfig,
"cfg:features" to features
), ttl = 30.minutes)
```
### Log-Level-Richtlinien
| Level | Verwendung | Beispiel |
|-------|------------|----------|
| **INFO** | Cache-Operationen, State-Changes, Metriken | `logger.info { "Performance test completed: $metrics" }` |
| **DEBUG** | Detaillierte Ablaufinformationen | `logger.debug { "Processing batch of $size entries" }` |
| **WARN** | Verbindungsprobleme, Performance-Issues | `logger.warn { "Success rate below threshold: $rate" }` |
| **ERROR** | Kritische Fehler, Serialisierungsprobleme | `logger.error { "Unexpected exception in cache operation" }` |
### Logback-Konfiguration
```xml
<!-- Strukturierte Console-Ausgaben -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Cache-spezifische Logger -->
<logger name="at.mocode.infrastructure.cache" level="DEBUG" />
<logger name="RedisDistributedCachePerformanceTest" level="INFO" />
<!-- Reduzierte Verbosity für externe Komponenten -->
<logger name="org.testcontainers" level="WARN" />
<logger name="io.lettuce" level="WARN" />
```
## Dependency-Management: Single Source of Truth
### Vollständige SINGLE SOURCE OF TRUTH Konformität
Alle Dependencies verwenden jetzt zentrale `libs.versions.toml` Verwaltung:
```toml
# Zentrale Versionen
[versions]
logback = "1.5.13"
kotlinLogging = "3.0.5"
[libraries]
kotlin-logging-jvm = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlinLogging" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
[bundles]
redis-cache = ["spring-boot-starter-data-redis", "lettuce-core", "jackson-module-kotlin", "jackson-datatype-jsr310"]
testing-jvm = ["junit-jupiter-api", "junit-jupiter-engine", "mockk", "assertj-core", "kotlinx-coroutines-test"]
```
### Build-Konfiguration
```kotlin
// redis-cache/build.gradle.kts - VOLLSTÄNDIG OPTIMIERT
dependencies {
// Alle Dependencies über libs-Referenzen
implementation(libs.bundles.redis.cache)
testImplementation(projects.platform.platformTesting)
testImplementation(libs.bundles.testing.jvm)
testImplementation(libs.kotlin.test)
testImplementation(libs.kotlin.logging.jvm)
testImplementation(libs.logback.classic)
testImplementation(libs.logback.core)
}
```
## Konfiguration & Deployment
### Cache-Konfigurationen für verschiedene Umgebungen
#### Performance-optimiert (High-Throughput)
```kotlin
val performanceConfig = DefaultCacheConfiguration(
keyPrefix = "perf",
defaultTtl = 5.minutes,
localCacheMaxSize = 50000,
compressionEnabled = false, // Für maximale Geschwindigkeit
compressionThreshold = Int.MAX_VALUE
)
```
#### Storage-optimiert (Kompression)
```kotlin
val storageConfig = DefaultCacheConfiguration(
keyPrefix = "storage",
defaultTtl = 7.days,
localCacheMaxSize = 1000,
compressionEnabled = true,
compressionThreshold = 100 // Kompression ab 100 Bytes
)
```
#### Minimal (Entwicklung)
```kotlin
val minimalConfig = DefaultCacheConfiguration(
keyPrefix = "dev",
defaultTtl = null, // Kein TTL
localCacheMaxSize = null, // Unbegrenzt
offlineModeEnabled = false // Für Entwicklung optional
)
```
## Monitoring & Observability
### Connection-State-Monitoring
Verbindungsstatus überwachen
```kotlin
cache.registerConnectionListener(object : ConnectionStateListener {
override fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant) {
when (newState) {
ConnectionState.CONNECTED -> {
logger.info { "Cache reconnected at $timestamp" }
metricsCollector.increment("cache.reconnects")
}
ConnectionState.DISCONNECTED -> {
logger.warn { "Cache disconnected at $timestamp" }
alerting.sendAlert("Cache offline", "Redis connection lost")
}
ConnectionState.RECONNECTING -> {
logger.info { "Cache attempting reconnection at $timestamp" }
}
}
logger.info("Cache connection state: $newState at $timestamp")
}
})
```
### Performance-Metriken
```kotlin
// Beispiel für strukturierte Metriken-Sammlung
val metrics = mapOf(
"totalOperations" to totalOperations,
"successRate" to successRate,
"averageLatency" to averageLatency,
"operationsPerSecond" to opsPerSec,
"dirtyKeysCount" to cache.getDirtyKeys().size,
"connectionState" to cache.getConnectionState()
)
logger.info { "Cache performance metrics: $metrics" }
```
## Konfiguration
DefaultCacheConfiguration bietet sinnvolle Defaults. Relevante Properties (optional via Spring @Scheduled Platzhalter):
- redis.connection-check-interval: ms für Verbindungsprüfung (Default 10000)
- redis.local-cache-cleanup-interval: ms für lokale Bereinigung (Default 60000)
- redis.sync-interval: ms für Synchronisationsläufe (Default 300000)
- redis.metrics-log-interval: ms für periodisches MetrikenLogging (Default 300000)
### CI/CD Integration
```yaml
# Beispiel für GitHub Actions
- name: Run Cache Tests with Structured Logging
run: |
./gradlew :infrastructure:cache:redis-cache:test --info
Hinweise
- keyPrefix sollte pro Service gesetzt werden (z. B. "masterdata"), um Kollisionen zu vermeiden.
- localCacheMaxSize begrenzt die Größe des lokalen FallbackCaches. Bei null ist die Größe unbegrenzt.
# Log-Level für verschiedene Umgebungen:
# Development: DEBUG (alle Details)
# CI/CD: INFO (wichtige Ereignisse)
# Production: WARN (nur Probleme)
```
## Betrieb & Monitoring
- HealthInfos: getHealthStatus() liefert eine einfache Einschätzung basierend auf ConnectionState und Erfolgsrate der Operationen.
- Metriken: getPerformanceMetrics() liefert einfache Kennzahlen (Operations, SuccessRate, Größe lokaler Cache, Anzahl dirty Keys). Periodisches Logging per @Scheduled möglich.
- Cache Warming: warmCache(keys, loader) und warmCacheBulk(map) helfen, HotKeys/gefragte Konfigurationen beim Start vorzuwärmen.
## Best Practices & Empfehlungen
## Grenzen & bekannte Punkte
- Kompression ist im Serializer implementiert; die konfigurierbaren Flags/Schwellenwerte sind derzeit nicht dynamisch an/ausgeschaltet.
- OfflineModus: Die Konfiguration offlineModeEnabled ist vorhanden; die Implementierung betreibt den lokalen Fallback standardmäßig bei Verbindungsproblemen. Eine harte Deaktivierung dieses Verhaltens ist aktuell nicht verdrahtet.
### Produktionseinsatz-Empfehlungen
## Changelog (Kurz)
- 20250903: Fehlerbehebungen für @ScheduledPlatzhalter, korrektes Logging im CacheWarming, lokale CacheGrößenbegrenzung (LRMEviction) hinzugefügt. Dokumentation aktualisiert (diese Datei).
#### **Priorität HOCH (sofort umsetzbar):**
1. **Performance-Monitoring:** Strukturierte Logs für Produktions-Metriken nutzen
2. **Connection-State-Überwachung:** Listener für Alerting bei Redis-Ausfällen einrichten
3. **Cache-Warming:** Graduelle Warming-Strategien beim Service-Start implementieren
#### **Priorität MITTEL (mittelfristig):**
1. **Kompression-Tuning:** Threshold je nach Datenanforderungen anpassen (Standard: 1KB)
2. **Local-Cache-Größen:** Je nach verfügbarem RAM pro Service optimieren
3. **TTL-Strategien:** Spezifische TTLs für verschiedene Datentypen definieren
#### **Priorität NIEDRIG (langfristig):**
1. **Advanced Monitoring:** Integration mit Micrometer/Prometheus für detaillierte Metriken
2. **Multi-Redis-Cluster:** Unterstützung für Redis-Cluster-Konfigurationen
3. **Erweiterte Kompression:** Alternative Algorithmen (LZ4, Snappy) evaluieren
### Entwickler-Guidelines
#### **DO's ✅**
- Verwende `cache.get<Type>(key)` für typsichere Operationen
- Implementiere Connection-State-Listener für kritische Services
- Nutze Batch-Operationen (`multiGet`, `multiSet`) für bessere Performance
- Verwende aussagekräftige Key-Prefixes für Multi-Tenant-Szenarien
- Teste Cache-Warming-Strategien in Integration-Tests
#### **DON'Ts ❌**
- Niemals sensible Daten ohne Verschlüsselung cachen
- Vermeide sehr große TTLs ohne Begründung (>24h)
- Keine Hard-coded Cache-Keys - verwende Key-Factories
- Vermeide Blocking-Operations in Connection-State-Listeners
- Keine println() in Cache-bezogenem Code - verwende Logger
### Typische Anwendungsszenarien
#### User-Session-Caching
```kotlin
// TTL = Session-Timeout
cache.set("user:session:${sessionId}", userSession, ttl = 30.minutes)
```
#### API-Response-Caching
```kotlin
// Kurze TTL für häufig ändernde Daten
cache.set("api:response:${endpoint}", response, ttl = 5.minutes)
```
#### Configuration-Caching
```kotlin
// Lange TTL für stabile Konfiguration
cache.set("config:${service}", config, ttl = 1.hours)
```
#### Database-Result-Caching
```kotlin
// Mittlere TTL für Datenbankabfragen
cache.set("db:${query.hash()}", results, ttl = 15.minutes)
```
## Migration & Upgrade-Pfad
### Von Version < 1.0
1. **Dependencies aktualisieren:** Umstellung auf libs.versions.toml
2. **Logging modernisieren:** println() → SLF4J/kotlin-logging
3. **Test-Suite erweitern:** Neue Test-Kategorien hinzufügen
4. **Konfiguration migrieren:** Neue DefaultCacheConfiguration verwenden
### Backwards Compatibility
- ✅ Alle bestehenden API-Calls funktionieren weiterhin
- ✅ Bestehende Konfigurationen sind kompatibel
- ✅ Migration kann schrittweise erfolgen
## Changelog
### 2025-08-15 - Enhanced Monitoring & Operations Update v2.1
- ✅ **Real-time Performance Metrics:** Automatisches Tracking aller Cache-Operationen mit detaillierter Erfolgsraten-Überwachung
- ✅ **Strukturierte Metrics-Logging:** Periodische Performance-Reports alle 5 Minuten mit umfassenden Metriken
- ✅ **Cache Warming Utilities:** Produktions-bereite Warming-Strategien mit Individual- und Bulk-Operations
- ✅ **Health Status Monitoring:** Umfassende Gesundheitschecks mit automatischer Status-Bewertung (>90% Erfolgsrate)
- ✅ **Enhanced Connection Tracking:** Erweiterte Verbindungsüberwachung mit detaillierten Zustandsinformationen
- ✅ **Production-Ready Monitoring:** Integration hooks für Enterprise-Monitoring-Systeme
- ✅ **Performance Optimization:** Verbesserte Metriken-Sammlung ohne Performance-Impact
### 2025-08-14 - Major Update v2.0
- ✅ **Vollständige Test-Suite-Erweiterung:** Von 12 auf 39 Tests (94.7% Success Rate)
- ✅ **Professionelle Logging-Architektur:** Komplette Umstellung auf SLF4J/kotlin-logging
- ✅ **SINGLE SOURCE OF TRUTH:** Alle Dependencies über libs.versions.toml
- ✅ **Edge-Cases-Korrekturen:** Serialisierungstests von 71.4% auf 100% Success Rate
- ✅ **Enterprise-Features validiert:** 5.000+ concurrent operations, 10MB+ objects
- ✅ **Produktionstauglichkeit erreicht:** Vollständige Performance-, Resilience- und Integration-Tests
- ✅ **Erweiterte Konfigurierbarkeit:** Performance-, Storage- und Development-Presets
- ✅ **Advanced Monitoring:** Connection-State-Listener und strukturierte Metriken
### 2025-08-14 - Previous
- **Bug Fix:** Compiler-Warnungen in `JacksonCacheSerializer` bezüglich identity-sensitiver Operationen behoben
- **Verbesserung:** Objects.equals() für sichere nullable Instant-Vergleiche
## Testing-Strategie: Zweistufig & Umfassend
### Integrationstests mit Testcontainers
Die Kernfunktionalität wird gegen eine echte Redis-Datenbank getestet, die zur Laufzeit in einem Docker-Container gestartet wird. Dies garantiert 100%ige Kompatibilität und realistische Performance-Messungen.
### Unit-Tests mit MockK
Die komplexe Logik der Offline-Fähigkeit und Synchronisation wird durch das Mocking des RedisTemplate getestet. So können Verbindungsausfälle, Timeouts und Netzwerkpartitionierung zuverlässig simuliert werden.
### End-to-End Produktionstests
Production-like Scenarios testen realistische Anwendungsfälle:
- User-Session-Management (1000 Sessions)
- Configuration-Caching mit verschiedenen TTLs
- API-Response-Caching (100 Endpoints)
- Cross-Service-Kommunikation
## Fazit & Status
Das **Infrastructure/Cache-Modul** ist **vollständig produktionsbereit** und erfüllt alle Enterprise-Anforderungen:
- ✅ **94.7% Test Success Rate** mit 39 umfassenden Tests
- ✅ **Professionelle Logging-Architektur** durchgängig etabliert
- ✅ **Enterprise-Performance** validiert (5.000+ concurrent ops)
- ✅ **Vollständige Resilience** bei Netzwerk- und Redis-Ausfällen
- ✅ **SINGLE SOURCE OF TRUTH** für alle Dependencies
- ✅ **Internationale Deployment-Fähigkeit** mit Unicode-Support
- ✅ **Advanced Monitoring** mit Connection-State-Tracking
- ✅ **Multi-Tenant-Capable** durch Key-Prefix-Isolation
**Empfehlung: ✅ BEREIT FÜR PRODUKTIONSEINSATZ**
Das Modul kann sofort in produktiven Umgebungen eingesetzt werden. Die umfassende Test-Suite und professionelle Architektur gewährleisten höchste Zuverlässigkeit und Performance.
## Fazit
Das CacheModul bietet eine klare, wiederverwendbare CacheSchnittstelle mit einer robusten RedisImplementierung. Es unterstützt TTLs, BatchOperationen, lokalen Fallback bei Ausfällen und liefert einfache, praxistaugliche Betriebsinformationen. Mit keyPrefix und lokalen Limits ist der Einsatz in MultiServiceUmgebungen unkompliziert und stabil.

View File

@ -5,6 +5,12 @@ plugins {
alias(libs.plugins.kotlin.jvm)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
// Erlaubt die Verwendung der kotlin.time API im gesamten Modul
kotlin {
compilerOptions {
@ -12,11 +18,15 @@ kotlin {
}
}
tasks.test {
useJUnitPlatform()
}
dependencies {
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
implementation(platform(projects.platform.platformBom))
// Stellt gemeinsame Abhängigkeiten wie Logging bereit.
implementation(projects.platform.platformDependencies)
api(platform(projects.platform.platformBom))
// Stellt gemeinsame Abhängigkeiten wie Logging bereit und exportiert sie für Konsumenten der API.
api(projects.platform.platformDependencies)
// Stellt Test-Abhängigkeiten bereit.
testImplementation(projects.platform.platformTesting)

View File

@ -1,56 +1,56 @@
package at.mocode.infrastructure.cache.api
/**
* Interface for serializing and deserializing cache entries.
* Schnittstelle zum Serialisieren und Deserialisieren von Cache-Einträgen.
*/
interface CacheSerializer {
/**
* Serializes a value to a byte array.
* Serialisiert einen Wert zu einem Byte-Array.
*
* @param value The value to serialize
* @return The serialized value as a byte array
* @param value Der zu serialisierende Wert
* @return Der serialisierte Wert als Byte-Array
*/
fun <T : Any> serialize(value: T): ByteArray
/**
* Deserializes a byte array to a value.
* Deserialisiert ein Byte-Array zu einem Wert.
*
* @param bytes The byte array to deserialize
* @param clazz The class of the value to deserialize to
* @return The deserialized value
* @param bytes Das zu deserialisierende Byte-Array
* @param clazz Die Zielklasse des zu deserialisierenden Werts
* @return Der deserialisierte Wert
*/
fun <T : Any> deserialize(bytes: ByteArray, clazz: Class<T>): T
/**
* Serializes a cache entry to a byte array.
* Serialisiert einen Cache-Eintrag zu einem Byte-Array.
*
* @param entry The cache entry to serialize
* @return The serialized cache entry as a byte array
* @param entry Der zu serialisierende Cache-Eintrag
* @return Der serialisierte Cache-Eintrag als Byte-Array
*/
fun <T : Any> serializeEntry(entry: CacheEntry<T>): ByteArray
/**
* Deserializes a byte array to a cache entry.
* Deserialisiert ein Byte-Array zu einem Cache-Eintrag.
*
* @param bytes The byte array to deserialize
* @param valueClass The class of the value in the cache entry
* @return The deserialized cache entry
* @param bytes Das zu deserialisierende Byte-Array
* @param valueClass Die Klasse des Werts im Cache-Eintrag
* @return Der deserialisierte Cache-Eintrag
*/
fun <T : Any> deserializeEntry(bytes: ByteArray, valueClass: Class<T>): CacheEntry<T>
/**
* Compresses a byte array.
* Komprimiert ein Byte-Array.
*
* @param bytes The byte array to compress
* @return The compressed byte array
* @param bytes Das zu komprimierende Byte-Array
* @return Das komprimierte Byte-Array
*/
fun compress(bytes: ByteArray): ByteArray
/**
* Decompresses a byte array.
* Dekomprimiert ein Byte-Array.
*
* @param bytes The byte array to decompress
* @return The decompressed byte array
* @param bytes Das zu dekomprimierende Byte-Array
* @return Das dekomprimierte Byte-Array
*/
fun decompress(bytes: ByteArray): ByteArray
}

View File

@ -1,20 +1,20 @@
package at.mocode.infrastructure.cache.api
/**
* Kotlin-idiomatic extension function to retrieve a value from the cache
* using reified types.
* Kotlin-idiomatische Extension-Funktion, um einen Wert aus dem Cache zu lesen
* mit reified Typen.
*
* Example: `val user = cache.get<User>("user:123")`
* Beispiel: `val user = cache.get<User>("user:123")`
*/
inline fun <reified T : Any> DistributedCache.get(key: String): T? {
return this.get(key, T::class.java)
}
/**
* Kotlin-idiomatic extension function to retrieve multiple values from the cache
* using reified types.
* Kotlin-idiomatische Extension-Funktion, um mehrere Werte aus dem Cache zu lesen
* mit reified Typen.
*
* Example: `val users = cache.multiGet<User>(listOf("user:123", "user:124"))`
* Beispiel: `val users = cache.multiGet<User>(listOf("user:123", "user:124"))`
*/
inline fun <reified T : Any> DistributedCache.multiGet(keys: Collection<String>): Map<String, T> {
return this.multiGet(keys, T::class.java)

View File

@ -3,13 +3,24 @@
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.spring)
alias(libs.plugins.spring.boot)
// Als Bibliothek benötigt dieses Modul das Spring Boot Plugin nicht.
alias(libs.plugins.spring.dependencyManagement)
}
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
tasks.getByName("bootJar") {
enabled = false
// Stellt sicher, dass ein normales JAR gebaut wird (Bibliotheks-Modul).
java {
withJavadocJar()
withSourcesJar()
}
tasks.test {
useJUnitPlatform()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
dependencies {

View File

@ -82,6 +82,7 @@ class RedisDistributedCache(
// Store in a local cache
@Suppress("UNCHECKED_CAST")
localCache[prefixedKey] = entry as CacheEntry<Any>
enforceLocalCacheSize()
trackOperation(true)
return entry.value
@ -109,6 +110,7 @@ class RedisDistributedCache(
@Suppress("UNCHECKED_CAST")
localCache[prefixedKey] = entry as CacheEntry<Any>
enforceLocalCacheSize()
if (!isConnected()) {
markDirty(key)
@ -231,6 +233,7 @@ class RedisDistributedCache(
// Store in a local cache
@Suppress("UNCHECKED_CAST")
localCache[key] = entry as CacheEntry<Any>
enforceLocalCacheSize()
// Add to result
result[removePrefix(key)] = entry.value
@ -263,6 +266,7 @@ class RedisDistributedCache(
)
@Suppress("UNCHECKED_CAST")
localCache[prefixedKey] = entry as CacheEntry<Any>
enforceLocalCacheSize()
redisBatch[prefixedKey] = serializer.serializeEntry(entry)
}
@ -437,6 +441,22 @@ class RedisDistributedCache(
return if (config.keyPrefix.isEmpty()) key else key.substring(config.keyPrefix.length + 1)
}
/**
* Erzwingt die maximale Größe des lokalen Caches, indem die am längsten nicht
* mehr modifizierten Einträge entfernt werden.
*/
private fun enforceLocalCacheSize() {
val max = config.localCacheMaxSize ?: return
val overflow = localCache.size - max
if (overflow <= 0) return
val toEvict = localCache.entries
.sortedBy { it.value.lastModifiedAt }
.take(overflow)
.map { it.key }
toEvict.forEach { localCache.remove(it) }
logger.debug("Evicted ${toEvict.size} entries to enforce local cache size limit $max")
}
private fun handleConnectionFailure(e: Exception) {
logger.warn("Redis connection failure: ${e.message}")
setConnectionState(ConnectionState.DISCONNECTED)
@ -468,9 +488,9 @@ class RedisDistributedCache(
}
/**
* Periodically check the connection to Redis.
* Prüft periodisch die Verbindung zu Redis.
*/
@Scheduled(fixedDelayString = $$"${redis.connection-check-interval:10000}")
@Scheduled(fixedDelayString = "\${redis.connection-check-interval:10000}")
fun checkConnection() {
try {
redisTemplate.hasKey("connection-test")
@ -481,9 +501,9 @@ class RedisDistributedCache(
}
/**
* Periodically clean up expired entries from the local cache.
* Bereinigt periodisch abgelaufene Einträge aus dem lokalen Cache.
*/
@Scheduled(fixedDelayString = $$"${redis.local-cache-cleanup-interval:60000}")
@Scheduled(fixedDelayString = "\${redis.local-cache-cleanup-interval:60000}")
fun cleanupLocalCache() {
val now = Clock.System.now()
val expiredKeys = localCache.entries
@ -498,9 +518,9 @@ class RedisDistributedCache(
}
/**
* Periodically synchronize dirty keys when connected.
* Synchronisiert periodisch schmutzige Schlüssel, sobald verbunden.
*/
@Scheduled(fixedDelayString = $$"${redis.sync-interval:300000}")
@Scheduled(fixedDelayString = "\${redis.sync-interval:300000}")
fun scheduledSync() {
if (isConnected() && dirtyKeys.isNotEmpty()) {
synchronize(null)
@ -512,7 +532,7 @@ class RedisDistributedCache(
//
/**
* Track a cache operation for metrics
* Zeichnet eine Cache-Operation für Metriken auf.
*/
private fun trackOperation(success: Boolean) {
synchronized(this) {
@ -522,7 +542,7 @@ class RedisDistributedCache(
}
/**
* Get current performance metrics
* Liefert aktuelle Performance-Metriken.
*/
fun getPerformanceMetrics(): Map<String, Any> {
val now = Clock.System.now()
@ -543,9 +563,9 @@ class RedisDistributedCache(
}
/**
* Log performance metrics (called periodically)
* Loggt Performance-Metriken (periodisch aufgerufen).
*/
@Scheduled(fixedDelayString = $$"${redis.metrics-log-interval:300000}")
@Scheduled(fixedDelayString = "\${redis.metrics-log-interval:300000}")
fun logPerformanceMetrics() {
val metrics = getPerformanceMetrics()
logger.info("Cache performance metrics: $metrics")
@ -553,7 +573,7 @@ class RedisDistributedCache(
}
/**
* Cache warming utility - preloads specified keys
* Cache-Warming-Helfer lädt angegebene Schlüssel vor.
*/
fun warmCache(keys: Collection<String>, dataLoader: (String) -> Any?) {
logger.info("Starting cache warming for ${keys.size} keys")
@ -571,11 +591,11 @@ class RedisDistributedCache(
}
val duration = Clock.System.now() - startTime
logger.info("Cache warming completed: $warmedCount/$${keys.size} keys loaded in $duration")
logger.info("Cache warming completed: $warmedCount/${keys.size} keys loaded in $duration")
}
/**
* Bulk cache warming with batch operations
* Bulk-Cache-Warming mit Batch-Operationen.
*/
fun warmCacheBulk(keyDataMap: Map<String, Any>, ttl: Duration? = null) {
logger.info("Starting bulk cache warming for ${keyDataMap.size} entries")
@ -588,7 +608,7 @@ class RedisDistributedCache(
}
/**
* Get cache health status
* Liefert den Cache-Gesundheitsstatus.
*/
fun getHealthStatus(): Map<String, Any> {
val metrics = getPerformanceMetrics()

View File

@ -12,49 +12,49 @@ import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
import java.time.Duration
/**
* Enhanced reactive security configuration for the Gateway.
* Erweiterte reaktive Sicherheitskonfiguration für das Gateway.
*
* ARCHITECTURE OVERVIEW:
* ARCHITEKTUR-ÜBERBLICK:
* ======================
* Diese Konfiguration stellt die grundlegende Sicherheits-Schicht für das Spring Cloud Gateway bereit.
* Sie arbeitet zusammen mit mehreren weiteren Sicherheitskomponenten:
*
* 1. JwtAuthenticationFilter (GlobalFilter) Validiert JWT-Tokens und authentifiziert Benutzer
* 2. RateLimitingFilter (GlobalFilter) Bietet IP-basiertes Rate-Limiting mit benutzerbezogenen Limits
* 3. CorrelationIdFilter (GlobalFilter) Fügt Request-Tracing-Fähigkeiten hinzu
* 4. EnhancedLoggingFilter (GlobalFilter) Liefert strukturiertes Request/Response-Logging
*
* SICHERHEITSSTRATEGIE:
* =====================
* This configuration establishes the foundational security layer for the Spring Cloud Gateway.
* It works in conjunction with several other security components:
* Das Gateway verwendet einen mehrschichtigen Sicherheitsansatz:
* - Diese SecurityWebFilterChain liefert grundlegende Einstellungen (CORS, CSRF, Basis-Header)
* - Der JwtAuthenticationFilter übernimmt die eigentliche Authentifizierung, wenn per Property aktiviert
* - Die SecurityWebFilterChain bleibt permissiv (permitAll), damit der JWT-Filter den Zugriff steuert
* - Rate-Limiting- und Logging-Filter liefern operative Sicherheit und Monitoring
*
* 1. JwtAuthenticationFilter (GlobalFilter) - Handles JWT token validation and user authentication
* 2. RateLimitingFilter (GlobalFilter) - Provides IP-based rate limiting with user-aware limits
* 3. CorrelationIdFilter (GlobalFilter) - Adds request tracing capabilities
* 4. EnhancedLoggingFilter (GlobalFilter) - Provides structured request/response logging
* ENTWURFSBEGRÜNDUNG:
* ===================
* - Während Tests ist Spring Security auf dem Classpath (testImplementation), was
* die Auto-Konfiguration aktiviert und alle Endpunkte sperren kann, sofern keine SecurityWebFilterChain bereitgestellt wird
* - Das Gateway erzwingt Authentifizierung über den JwtAuthenticationFilter (falls per Property aktiviert),
* daher sollte die SecurityWebFilterChain permissiv bleiben und sich auf grundlegende Belange konzentrieren
* - Explizite CORS-Konfiguration stellt eine korrekte Behandlung von Cross-Origin-Anfragen aus Web-Clients sicher
* - Konfigurierbare Properties erlauben umgebungsspezifische Sicherheitseinstellungen ohne Codeänderungen
* - CSRF-Schutz ist deaktiviert, da er für zustandslose JWT-basierte Authentifizierung nicht benötigt wird
*
* SECURITY STRATEGY:
* ==================
* The Gateway employs a layered security approach:
* - This SecurityWebFilterChain provides foundational settings (CORS, CSRF, basic headers)
* - JwtAuthenticationFilter handles actual authentication when enabled via property
* - The SecurityWebFilterChain remains permissive (permitAll) to let the JWT filter control access
* - Rate limiting and logging filters provide operational security and monitoring
*
* DESIGN RATIONALE:
* CORS-INTEGRATION:
* =================
* - During tests, Spring Security is on the classpath (testImplementation), which enables
* security autoconfiguration and can lock down all endpoints unless a SecurityWebFilterChain is provided
* - The Gateway enforces authentication using JwtAuthenticationFilter when enabled via property,
* so the SecurityWebFilterChain should stay permissive and focus on foundational concerns
* - Explicit CORS configuration ensures proper handling of cross-origin requests from web clients
* - Configurable properties allow environment-specific security settings without code changes
* - CSRF protection is disabled as it's not needed for stateless JWT-based authentication
* Die CORS-Konfiguration arbeitet mit der bestehenden Filterkette zusammen:
* - Erlaubt Anfragen von konfigurierten Ursprüngen (Dev/Prod-Umgebungen)
* - Gibt benutzerdefinierte Header aus Gateway-Filtern frei (Korrelations-IDs, Rate-Limits)
* - Unterstützt Credentials für JWT-Authentifizierung
* - Cacht Preflight-Antworten für bessere Performance
*
* CORS INTEGRATION:
* =================
* The CORS configuration works with the existing filter chain:
* - Allows requests from configured origins (dev/prod environments)
* - Exposes custom headers from Gateway filters (correlation IDs, rate limits)
* - Supports credentials for JWT authentication
* - Caches preflight responses for performance
*
* TESTING CONSIDERATIONS:
* =======================
* - Configuration is designed to work seamlessly with existing security tests
* - Test profile can override CORS settings if needed
* - Permissive authorization ensures tests can focus on filter-level security
* TESTHINWEISE:
* =============
* - Die Konfiguration ist so gestaltet, dass sie nahtlos mit bestehenden Sicherheitstests funktioniert
* - Das Test-Profil kann CORS-Einstellungen bei Bedarf überschreiben
* - Eine permissive Autorisierung stellt sicher, dass Tests sich auf die Sicherheit der Filterebene konzentrieren können
*/
@Configuration
@EnableConfigurationProperties(GatewaySecurityProperties::class)
@ -63,113 +63,113 @@ class SecurityConfig(
) {
/**
* Main Spring Security filter chain configuration.
* Hauptkonfiguration der Spring-Security-Filterkette.
*
* This method configures the reactive security filter chain with:
* - CSRF disabled for stateless API operation
* - Explicit CORS configuration for cross-origin support
* - Permissive authorization (authentication handled by JWT filter)
* Diese Methode konfiguriert die reaktive Sicherheits-Filterkette mit:
* - CSRF deaktiviert für zustandslosen API-Betrieb
* - Expliziter CORS-Konfiguration für Cross-Origin-Unterstützung
* - Permissiver Autorisierung (Authentifizierung durch den JWT-Filter)
*
* The configuration maintains compatibility with the existing filter architecture
* while providing enhanced CORS control and configurability.
* Die Konfiguration bleibt kompatibel mit der bestehenden Filterarchitektur
* und bietet zugleich bessere CORS-Steuerung und Konfigurierbarkeit.
*/
@Bean
fun springSecurityFilterChain(): SecurityWebFilterChain {
return ServerHttpSecurity.http()
.csrf { csrf ->
// Disable CSRF for stateless API gateway
// CSRF protection is not required for JWT-based stateless authentication
// The Gateway operates as a stateless proxy with no session state
// CSRF für zustandsloses API-Gateway deaktivieren
// CSRF-Schutz ist für JWT-basierte zustandslose Authentifizierung nicht erforderlich
// Das Gateway arbeitet als zustandsloser Proxy ohne Session-Zustand
csrf.disable()
}
.cors { cors ->
// Use explicit CORS configuration instead of default
// This provides better control over cross-origin access policies
// Explizite CORS-Konfiguration anstelle des Defaults verwenden
// Dies ermöglicht eine bessere Kontrolle über Cross-Origin-Zugriffsrichtlinien
cors.configurationSource(corsConfigurationSource())
}
.httpBasic { basic ->
// Disable HTTP Basic auth for stateless API
// HTTP Basic Auth für zustandslose API deaktivieren
basic.disable()
}
.formLogin { form ->
// Disable form login for API gateway
// Formular-Login für API-Gateway deaktivieren
form.disable()
}
.authorizeExchange { exchanges ->
// Allow all requests through Spring Security
// Authentication and authorization are handled by JwtAuthenticationFilter
// This approach maintains the existing security architecture while
// allowing the JWT filter to make granular access control decisions
// Alle Anfragen durch Spring Security erlauben
// Authentifizierung und Autorisierung erfolgen durch den JwtAuthenticationFilter
// Dieser Ansatz bewahrt die bestehende Sicherheitsarchitektur und
// ermöglicht dem JWT-Filter granulare Zugriffskontroll-Entscheidungen
exchanges.anyExchange().permitAll()
}
.build()
}
/**
* Explicit CORS configuration source.
* Explizite CORS-Konfigurationsquelle.
*
* This bean provides detailed control over cross-origin resource sharing settings,
* replacing the default empty CORS configuration with explicit, configurable settings.
* Dieser Bean bietet eine detaillierte Steuerung der Cross-Origin-Resource-Sharing-Einstellungen
* und ersetzt die leere Standard-CORS-Konfiguration durch explizite, konfigurierbare Einstellungen.
*
* Key features:
* - Environment-specific allowed origins
* - Comprehensive HTTP method support
* - JWT-aware header configuration
* - Integration with Gateway filter headers
* - Performance-optimized preflight caching
* Schlüsselfunktionen:
* - Umgebungsspezifische erlaubte Ursprünge (Allowed Origins)
* - Umfassende Unterstützung für HTTP-Methoden
* - JWT-bewusste Header-Konfiguration
* - Integration mit Headern aus Gateway-Filtern
* - Performance-optimiertes Preflight-Caching
*
* The configuration is designed to work with typical web application architectures
* where a JavaScript frontend makes API calls to the Gateway.
* Die Konfiguration ist darauf ausgelegt, mit typischen Webanwendungs-Architekturen zu funktionieren,
* bei denen ein JavaScript-Frontend API-Aufrufe an das Gateway sendet.
*/
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration().apply {
// Allowed origins - configurable per environment
// Development: localhost URLs for local testing
// Production: domain-specific URLs for deployed applications
// Erlaubte Ursprünge pro Umgebung konfigurierbar
// Entwicklung: localhost-URLs für lokale Tests
// Produktion: domainspezifische URLs für ausgelieferte Anwendungen
allowedOrigins = securityProperties.cors.allowedOrigins.toList()
// Allowed HTTP methods - comprehensive REST API support
// Includes all standard methods plus OPTIONS for preflight requests
// Erlaubte HTTP-Methoden umfassende REST-API-Unterstützung
// Enthält alle Standardmethoden plus OPTIONS für Preflight-Anfragen
allowedMethods = securityProperties.cors.allowedMethods.toList()
// Allowed request headers - includes JWT and custom headers
// Authorization: for JWT Bearer tokens
// X-Correlation-ID: for request tracing
// Standard headers: Content-Type, Accept, etc.
// Erlaubte Request-Header beinhaltet JWT und benutzerdefinierte Header
// Authorization: für JWT Bearer Tokens
// X-Correlation-ID: für Request-Tracing
// Standard-Header: Content-Type, Accept, etc.
allowedHeaders = securityProperties.cors.allowedHeaders.toList()
// Exposed response headers - allows client access to custom headers
// Includes headers added by Gateway filters:
// - X-Correlation-ID from CorrelationIdFilter
// - X-RateLimit-* from RateLimitingFilter
// Sichtbare Response-Header ermöglicht Client-Zugriff auf benutzerdefinierte Header
// Beinhaltet Header, die von Gateway-Filtern hinzugefügt werden:
// - X-Correlation-ID vom CorrelationIdFilter
// - X-RateLimit-* vom RateLimitingFilter
exposedHeaders = securityProperties.cors.exposedHeaders.toList()
// Allow credentials - required for JWT authentication
// Enables cookies and authorization headers in cross-origin requests
// Credentials erlauben erforderlich für JWT-Authentifizierung
// Aktiviert Cookies und Authorization-Header in Cross-Origin-Anfragen
allowCredentials = securityProperties.cors.allowCredentials
// Preflight cache duration - performance optimization
// Reduces the number of OPTIONS requests for repeated API calls
// Preflight-Cache-Dauer Performance-Optimierung
// Reduziert die Anzahl an OPTIONS-Anfragen für wiederholte API-Aufrufe
maxAge = securityProperties.cors.maxAge.seconds
}
return UrlBasedCorsConfigurationSource().apply {
// Apply CORS configuration to all Gateway routes
// CORS-Konfiguration auf alle Gateway-Routen anwenden
registerCorsConfiguration("/**", configuration)
}
}
}
/**
* Configuration properties for Gateway security settings.
* Konfigurationseigenschaften für die Sicherheits-Einstellungen des Gateways.
*
* Enables environment-specific security configuration via application.yml/properties.
* This approach allows different security settings across development, testing, and
* production environments without requiring code changes.
* Ermöglicht umgebungsspezifische Sicherheitskonfiguration über application.yml/-properties.
* Dieser Ansatz erlaubt unterschiedliche Sicherheitseinstellungen für Entwicklung, Test und
* Produktion, ohne Codeänderungen vornehmen zu müssen.
*
* Example application.yml configuration:
* Beispielkonfiguration in application.yml:
* ```yaml
* gateway:
* security:
@ -192,26 +192,26 @@ data class GatewaySecurityProperties(
)
/**
* CORS-specific configuration properties with sensible defaults.
* CORS-spezifische Konfigurationseigenschaften mit sinnvollen Defaults.
*
* Default values are chosen to work with typical development and production setups:
* - Common development URLs (localhost with standard ports)
* - Production domain pattern matching
* - Full REST API method support
* - JWT and Gateway filter header support
* - Reasonable preflight cache duration
* Die Default-Werte sind so gewählt, dass sie mit typischen Entwicklungs- und Produktions-Setups funktionieren:
* - Übliche Entwicklungs-URLs (localhost mit Standardports)
* - Produktions-Domain-Muster
* - Volle Unterstützung der REST-API-Methoden
* - Unterstützung für JWT- und Gateway-Filter-Header
* - Sinnvolle Preflight-Cache-Dauer
*/
data class CorsProperties(
/**
* Allowed origins for CORS requests.
* Erlaubte Ursprünge (Allowed Origins) für CORS-Anfragen.
*
* Defaults support common development and production scenarios:
* - localhost:3000 - typical React development server
* - localhost:8080 - common alternative development port
* - localhost:4200 - typical Angular development server
* - Specific meldestelle.at subdomains for production
* Defaults unterstützen gängige Entwicklungs- und Produktionsszenarien:
* - localhost:3000 typischer React-Entwicklungsserver
* - localhost:8080 gängiger alternativer Entwicklungsport
* - localhost:4200 typischer Angular-Entwicklungsserver
* - Spezifische Subdomains von meldestelle.at für die Produktion
*
* Can be overridden per environment as needed.
* Kann je Umgebung bei Bedarf überschrieben werden.
*/
val allowedOrigins: Set<String> = setOf(
"http://localhost:3000",
@ -224,23 +224,23 @@ data class CorsProperties(
/**
* Allowed HTTP methods for CORS requests.
* Erlaubte HTTP-Methoden für CORS-Anfragen.
*
* Includes all standard REST API methods plus OPTIONS for preflight
* and HEAD for metadata requests.
* Enthält alle Standard-REST-API-Methoden sowie OPTIONS für Preflight-
* und HEAD für Metadaten-Anfragen.
*/
val allowedMethods: Set<String> = setOf(
"GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"
),
/**
* Allowed request headers for CORS requests.
* Erlaubte Request-Header für CORS-Anfragen.
*
* Includes:
* - Standard headers: Content-Type, Accept, etc.
* - JWT authentication: Authorization
* - Gateway tracing: X-Correlation-ID
* - Cache control: Cache-Control, Pragma
* Beinhaltet:
* - Standard-Header: Content-Type, Accept, etc.
* - JWT-Authentifizierung: Authorization
* - Gateway-Tracing: X-Correlation-ID
* - Cache-Steuerung: Cache-Control, Pragma
*/
val allowedHeaders: Set<String> = setOf(
"Authorization",
@ -254,13 +254,13 @@ data class CorsProperties(
),
/**
* Exposed response headers for CORS requests.
* Sichtbare Response-Header für CORS-Anfragen.
*
* Headers that client JavaScript can access in responses.
* Includes custom headers added by Gateway filters:
* - X-Correlation-ID: request tracing (CorrelationIdFilter)
* - X-RateLimit-*: rate limiting info (RateLimitingFilter)
* - Standard headers: Content-Length, Date
* Header, auf die Client-JavaScript in Antworten zugreifen darf.
* Beinhaltet benutzerdefinierte Header, die von Gateway-Filtern hinzugefügt werden:
* - X-Correlation-ID: Request-Tracing (CorrelationIdFilter)
* - X-RateLimit-*: Informationen zum Rate-Limiting (RateLimitingFilter)
* - Standard-Header: Content-Length, Date
*/
val exposedHeaders: Set<String> = setOf(
"X-Correlation-ID",
@ -272,21 +272,21 @@ data class CorsProperties(
),
/**
* Allow credentials in CORS requests.
* Credentials in CORS-Anfragen erlauben.
*
* Set to true to support:
* - JWT Bearer tokens in Authorization headers
* - Cookies (if used)
* - Client certificates (if used)
* Auf true setzen, um zu unterstützen:
* - JWT Bearer Tokens im Authorization-Header
* - Cookies (falls verwendet)
* - Client-Zertifikate (falls verwendet)
*/
val allowCredentials: Boolean = true,
/**
* Maximum age for preflight request caching.
* Maximales Alter für das Caching von Preflight-Anfragen.
*
* Duration that browsers can cache preflight responses, reducing
* the number of OPTIONS requests for repeated API calls.
* Default: 1 hour (reasonable balance of performance vs. flexibility)
* Dauer, für die Browser Preflight-Antworten cachen können, wodurch
* die Anzahl der OPTIONS-Anfragen für wiederholte API-Aufrufe reduziert wird.
* Default: 1 Stunde (guter Kompromiss zwischen Performance und Flexibilität)
*/
val maxAge: Duration = Duration.ofHours(1)
)

View File

@ -35,140 +35,142 @@ spring:
max-idle-time: 15s
max-life-time: 60s
# Verbesserte CORS-Konfiguration
globalcors:
corsConfigurations:
'[/**]':
allowedOriginPatterns:
- "https://*.meldestelle.at"
- "http://localhost:*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- PATCH
- OPTIONS
allowedHeaders:
- "*"
allowCredentials: true
maxAge: 3600
# Antwort-Header bereinigen und globale Filter
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
- name: CircuitBreaker
args:
name: defaultCircuitBreaker
fallbackUri: forward:/fallback
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
methods: GET,POST,PUT,DELETE
backoff:
firstBackoff: 50ms
maxBackoff: 500ms
factor: 2
basedOnPreviousValue: false
# Security Headers for enhanced protection
- name: AddResponseHeader
args:
name: X-Content-Type-Options
value: nosniff
- name: AddResponseHeader
args:
name: X-Frame-Options
value: DENY
- name: AddResponseHeader
args:
name: X-XSS-Protection
value: 1; mode=block
- name: AddResponseHeader
args:
name: Referrer-Policy
value: strict-origin-when-cross-origin
- name: AddResponseHeader
args:
name: Cache-Control
value: no-cache, no-store, must-revalidate
# Route definitions with service discovery
routes:
# Health Check und Gateway Info Routes
- id: gateway-info-route
uri: http://localhost:${server.port}
predicates:
- Path=/
- Method=GET
filters:
- SetStatus=200
- SetResponseHeader=Content-Type,application/json
# Members Service Routes
- id: members-service-route
uri: lb://members-service
predicates:
- Path=/api/members/**
filters:
- StripPrefix=1
server:
webflux:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
- name: CircuitBreaker
args:
name: membersCircuitBreaker
fallbackUri: forward:/fallback/members
args:
name: defaultCircuitBreaker
fallbackUri: forward:/fallback
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
methods: GET,POST,PUT,DELETE
backoff:
firstBackoff: 50ms
maxBackoff: 500ms
factor: 2
basedOnPreviousValue: false
# Security Headers for enhanced protection
- name: AddResponseHeader
args:
name: X-Content-Type-Options
value: nosniff
- name: AddResponseHeader
args:
name: X-Frame-Options
value: DENY
- name: AddResponseHeader
args:
name: X-XSS-Protection
value: 1; mode=block
- name: AddResponseHeader
args:
name: Referrer-Policy
value: strict-origin-when-cross-origin
- name: AddResponseHeader
args:
name: Cache-Control
value: no-cache, no-store, must-revalidate
routes:
# Health Check und Gateway Info Routes
- id: gateway-info-route
uri: http://localhost:${server.port}
predicates:
- Path=/
- Method=GET
filters:
- SetStatus=200
- SetResponseHeader=Content-Type,application/json
# Horses Service Routes
- id: horses-service-route
uri: lb://horses-service
predicates:
- Path=/api/horses/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: horsesCircuitBreaker
fallbackUri: forward:/fallback/horses
# Members Service Routes
- id: members-service-route
uri: lb://members-service
predicates:
- Path=/api/members/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: membersCircuitBreaker
fallbackUri: forward:/fallback/members
# Events Service Routes
- id: events-service-route
uri: lb://events-service
predicates:
- Path=/api/events/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: eventsCircuitBreaker
fallbackUri: forward:/fallback/events
# Horses Service Routes
- id: horses-service-route
uri: lb://horses-service
predicates:
- Path=/api/horses/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: horsesCircuitBreaker
fallbackUri: forward:/fallback/horses
# Masterdata Service Routes
- id: masterdata-service-route
uri: lb://masterdata-service
predicates:
- Path=/api/masterdata/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: masterdataCircuitBreaker
fallbackUri: forward:/fallback/masterdata
# Events Service Routes
- id: events-service-route
uri: lb://events-service
predicates:
- Path=/api/events/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: eventsCircuitBreaker
fallbackUri: forward:/fallback/events
# Auth Service Routes (if exists)
- id: auth-service-route
uri: lb://auth-service
predicates:
- Path=/api/auth/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: authCircuitBreaker
fallbackUri: forward:/fallback/auth
# Masterdata Service Routes
- id: masterdata-service-route
uri: lb://masterdata-service
predicates:
- Path=/api/masterdata/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: masterdataCircuitBreaker
fallbackUri: forward:/fallback/masterdata
# Ping Service Routes (existing)
- id: ping-service-route
uri: lb://ping-service
predicates:
- Path=/api/ping/**
filters:
- StripPrefix=1
# Auth Service Routes (if exists)
- id: auth-service-route
uri: lb://auth-service
predicates:
- Path=/api/auth/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: authCircuitBreaker
fallbackUri: forward:/fallback/auth
# Ping Service Routes (existing)
- id: ping-service-route
uri: lb://ping-service
predicates:
- Path=/api/ping/**
filters:
- StripPrefix=1
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns:
- "https://*.meldestelle.at"
- "http://localhost:*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- PATCH
- OPTIONS
allowedHeaders:
- "*"
allowCredentials: true
maxAge: 3600
# Circuit Breaker Configuration
resilience4j:
@ -228,14 +230,14 @@ management:
probes:
enabled: true
metrics:
enabled: true
access: unrestricted
info:
enabled: true
access: unrestricted
prometheus:
enabled: true
access: unrestricted
gateway:
enabled: true
circuitbreakers:
access: unrestricted
circuit breakers:
enabled: true
metrics:
export: