refactoring Gateway
This commit is contained in:
-55
@@ -1,55 +0,0 @@
|
||||
package at.mocode.infrastructure.gateway.config
|
||||
|
||||
import at.mocode.infrastructure.auth.client.JwtService
|
||||
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-Konfiguration für JWT-Verarbeitung im Gateway.
|
||||
* Stellt den JwtService-Bean für die Token-Validierung bereit.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(JwtConfiguration.JwtProperties::class)
|
||||
class JwtConfiguration {
|
||||
|
||||
/**
|
||||
* Erstellt einen JwtService-Bean für JWT-Token-Validierung im Gateway.
|
||||
*/
|
||||
@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("[GATEWAY SECURITY WARNING] Using default JWT secret – DO NOT use this in production!")
|
||||
}
|
||||
return JwtService(
|
||||
secret = jwtProperties.secret,
|
||||
issuer = jwtProperties.issuer,
|
||||
audience = jwtProperties.audience,
|
||||
expiration = jwtProperties.expiration.minutes
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Konfigurationseigenschaften für JWT-Einstellungen im Gateway.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "gateway.security.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
|
||||
)
|
||||
}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
package at.mocode.infrastructure.gateway.security
|
||||
|
||||
import at.mocode.infrastructure.auth.client.JwtService
|
||||
import at.mocode.infrastructure.auth.client.model.BerechtigungE
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter
|
||||
import org.springframework.core.Ordered
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.util.AntPathMatcher
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* JWT Authentication Filter für das Gateway.
|
||||
* Validiert JWT-Tokens für alle geschützten Endpunkte.
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(value = ["gateway.security.jwt.enabled"], havingValue = "true", matchIfMissing = true)
|
||||
class JwtAuthenticationFilter(
|
||||
private val jwtService: JwtService
|
||||
) : GlobalFilter, Ordered {
|
||||
|
||||
private val pathMatcher = AntPathMatcher()
|
||||
|
||||
// Öffentliche Pfade, die keine Authentifizierung erfordern
|
||||
private val publicPaths = listOf(
|
||||
"/",
|
||||
"/health",
|
||||
"/actuator/**",
|
||||
"/api/auth/login",
|
||||
"/api/auth/register",
|
||||
"/api/auth/refresh",
|
||||
"/fallback/**",
|
||||
"/docs/**",
|
||||
"/swagger-ui/**",
|
||||
"/api/ping/**" // Ping Service für Monitoring
|
||||
)
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
|
||||
val request = exchange.request
|
||||
val path = request.path.value()
|
||||
|
||||
// Prüfe, ob der Pfad öffentlich zugänglich ist
|
||||
if (isPublicPath(path)) {
|
||||
return chain.filter(exchange)
|
||||
}
|
||||
|
||||
// Extrahiere JWT aus Authorization Header
|
||||
val authHeader = request.headers.getFirst("Authorization")
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
return handleUnauthorized(exchange, "Missing or invalid Authorization header")
|
||||
}
|
||||
|
||||
val token = authHeader.substring(7)
|
||||
|
||||
// Hier würde normalerweise die JWT-Validierung mit dem auth-client erfolgen,
|
||||
// für diese Implementation verwenden wir eine vereinfachte Validierung
|
||||
return validateJwtToken(token, exchange, chain)
|
||||
}
|
||||
|
||||
private fun isPublicPath(path: String): Boolean {
|
||||
return publicPaths.any { publicPath ->
|
||||
pathMatcher.match(publicPath, path)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateJwtToken(
|
||||
token: String,
|
||||
exchange: ServerWebExchange,
|
||||
chain: GatewayFilterChain
|
||||
): Mono<Void> {
|
||||
|
||||
// Use auth-client JwtService for comprehensive JWT validation
|
||||
val validationResult = jwtService.validateToken(token)
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return handleUnauthorized(exchange, "Invalid JWT token: ${validationResult.exceptionOrNull()?.message}")
|
||||
}
|
||||
|
||||
try {
|
||||
// Extract user ID using auth-client
|
||||
val userIdResult = jwtService.getUserIdFromToken(token)
|
||||
if (userIdResult.isFailure) {
|
||||
return handleUnauthorized(exchange, "Failed to extract user ID from token")
|
||||
}
|
||||
val userId = userIdResult.getOrThrow()
|
||||
|
||||
// Extract permissions using auth-client
|
||||
val permissionsResult = jwtService.getPermissionsFromToken(token)
|
||||
val permissions = permissionsResult.getOrElse { emptyList() }
|
||||
|
||||
// Convert permissions to role for backward compatibility
|
||||
val userRole = determineRoleFromPermissions(permissions)
|
||||
val permissionsHeader = permissions.joinToString(",") { it.name }
|
||||
|
||||
val mutatedRequest = exchange.request.mutate()
|
||||
.header("X-User-ID", userId)
|
||||
.header("X-User-Role", userRole)
|
||||
.header("X-User-Permissions", permissionsHeader)
|
||||
.build()
|
||||
|
||||
val mutatedExchange = exchange.mutate()
|
||||
.request(mutatedRequest)
|
||||
.build()
|
||||
|
||||
return chain.filter(mutatedExchange)
|
||||
|
||||
} catch (e: Exception) {
|
||||
return handleUnauthorized(exchange, "JWT processing failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the user role based on permissions for backward compatibility.
|
||||
* Maps permissions to traditional role-based access control.
|
||||
*/
|
||||
private fun determineRoleFromPermissions(permissions: List<BerechtigungE>): String {
|
||||
return when {
|
||||
permissions.any { it.name.contains("ADMIN", ignoreCase = true) } -> "ADMIN"
|
||||
permissions.any { it.name.contains("DELETE") } -> "ADMIN" // DELETE permissions indicate admin-level access
|
||||
permissions.any { it.name.contains("WRITE") || it.name.contains("CREATE") } -> "USER"
|
||||
permissions.isNotEmpty() -> "USER"
|
||||
else -> "GUEST"
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUnauthorized(exchange: ServerWebExchange, message: String): Mono<Void> {
|
||||
val response: ServerHttpResponse = exchange.response
|
||||
response.statusCode = HttpStatus.UNAUTHORIZED
|
||||
response.headers.add("Content-Type", "application/json")
|
||||
|
||||
val errorJson = """{
|
||||
"error": "UNAUTHORIZED",
|
||||
"message": "$message",
|
||||
"timestamp": "${java.time.LocalDateTime.now()}",
|
||||
"status": 401
|
||||
}"""
|
||||
|
||||
val buffer = response.bufferFactory().wrap(errorJson.toByteArray())
|
||||
return response.writeWith(Mono.just(buffer))
|
||||
}
|
||||
|
||||
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE + 3
|
||||
}
|
||||
+39
-267
@@ -1,328 +1,100 @@
|
||||
package at.mocode.infrastructure.gateway.security
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
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.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.web.cors.CorsConfiguration
|
||||
import org.springframework.web.cors.reactive.CorsConfigurationSource
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Erweiterte reaktive Sicherheitskonfiguration für das Gateway.
|
||||
*
|
||||
* 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:
|
||||
* =====================
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* CORS-INTEGRATION:
|
||||
* =================
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
@EnableWebFluxSecurity
|
||||
@EnableConfigurationProperties(GatewaySecurityProperties::class)
|
||||
class SecurityConfig(
|
||||
private val securityProperties: GatewaySecurityProperties,
|
||||
@Value($$"${keycloak.issuer-uri:}") private val issuerUri: String,
|
||||
@Value($$"${keycloak.jwk-set-uri:}") private val jwkSetUri: String
|
||||
private val securityProperties: GatewaySecurityProperties
|
||||
) {
|
||||
|
||||
/**
|
||||
* Hauptkonfiguration der Spring-Security-Filterkette.
|
||||
* Konfiguriert die zentrale Security-Filter-Kette für das Gateway.
|
||||
*
|
||||
* Diese Methode konfiguriert die reaktive Sicherheits-Filterkette mit:
|
||||
* - CSRF deaktiviert für zustandslosen API-Betrieb
|
||||
* - Explizite CORS-Konfiguration für Cross-Origin-Unterstützung
|
||||
* - Permissiver Autorisierung (Authentifizierung durch den JWT-Filter)
|
||||
*
|
||||
* Die Konfiguration bleibt kompatibel mit der bestehenden Filterarchitektur
|
||||
* und bietet zugleich bessere CORS-Steuerung und Konfigurierbarkeit.
|
||||
* Diese Konfiguration nutzt den Standard-OAuth2-Resource-Server von Spring Security,
|
||||
* um JWTs (z.B. von Keycloak) automatisch zu validieren.
|
||||
*/
|
||||
@Bean
|
||||
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
val httpSecurity = http
|
||||
.cors { it.configurationSource(corsConfigurationSource()) }
|
||||
.csrf { it.disable() }
|
||||
.authorizeExchange { exchanges ->
|
||||
exchanges
|
||||
// Public paths
|
||||
.pathMatchers(
|
||||
"/",
|
||||
"/health/**",
|
||||
"/actuator/**",
|
||||
"/api/ping/**",
|
||||
"/api/auth/**", // All auth endpoints (includes test endpoints)
|
||||
"/api/auth/login",
|
||||
"/api/auth/register",
|
||||
"/api/auth/refresh",
|
||||
"/fallback/**",
|
||||
"/docs/**",
|
||||
"/swagger-ui/**",
|
||||
"/test/**", // Test paths for integration tests
|
||||
"/mock/**" // Mock controller paths for tests
|
||||
).permitAll()
|
||||
.apply {
|
||||
// Only enforce role-based authorization when oauth2ResourceServer is active
|
||||
// In tests without oauth2, JwtAuthenticationFilter handles auth via GlobalFilter
|
||||
if (jwkSetUri.isNotBlank()) {
|
||||
// Admin paths
|
||||
pathMatchers("/api/admin/**").hasRole("ADMIN")
|
||||
// Monitoring paths
|
||||
pathMatchers("/api/monitoring/**").hasAnyRole("ADMIN", "MONITORING")
|
||||
// All other requests require authentication
|
||||
anyExchange().authenticated()
|
||||
} else {
|
||||
// Permissive mode for tests - JwtAuthenticationFilter handles auth
|
||||
anyExchange().permitAll()
|
||||
}
|
||||
}
|
||||
return http { // Start der modernen Kotlin-DSL
|
||||
// 1. CORS-Konfiguration anwenden
|
||||
cors { }
|
||||
|
||||
// 2. CSRF deaktivieren (für zustandslose APIs)
|
||||
csrf { disable() }
|
||||
|
||||
// 3. Routen-Berechtigungen definieren
|
||||
authorizeExchange {
|
||||
// Öffentlich zugängliche Pfade aus der .yml-Datei laden
|
||||
pathMatchers(*securityProperties.publicPaths.toTypedArray()).permitAll()
|
||||
|
||||
// Alle anderen Pfade erfordern eine Authentifizierung
|
||||
anyExchange.authenticated()
|
||||
}
|
||||
|
||||
// Only configure oauth2ResourceServer if Keycloak JWK URI is configured
|
||||
// In tests, this will be empty, allowing JwtAuthenticationFilter to handle auth
|
||||
if (jwkSetUri.isNotBlank()) {
|
||||
httpSecurity.oauth2ResourceServer { oauth2 ->
|
||||
oauth2.jwt { jwt ->
|
||||
jwt.jwtDecoder(jwtDecoder())
|
||||
}
|
||||
// 4. JWT-Validierung via Keycloak aktivieren
|
||||
oauth2ResourceServer {
|
||||
jwt { }
|
||||
}
|
||||
}
|
||||
|
||||
return httpSecurity.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = ["keycloak.jwk-set-uri"])
|
||||
fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri)
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Explizite CORS-Konfigurationsquelle.
|
||||
*
|
||||
* Dieser Bean bietet eine detaillierte Steuerung der Cross-Origin-Resource-Sharing-Einstellungen
|
||||
* und ersetzt die leere Standard-CORS-Konfiguration durch explizite, konfigurierbare Einstellungen.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Die Konfiguration ist darauf ausgelegt, mit typischen Webanwendungs-Architekturen zu funktionieren,
|
||||
* bei denen ein JavaScript-Frontend API-Aufrufe an das Gateway sendet.
|
||||
* Definiert die zentrale und einzige CORS-Konfiguration für das Gateway.
|
||||
*/
|
||||
@Bean
|
||||
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
val configuration = CorsConfiguration().apply {
|
||||
// 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()
|
||||
|
||||
|
||||
// Erlaubte HTTP-Methoden – umfassende REST-API-Unterstützung
|
||||
// Enthält alle Standardmethoden plus OPTIONS für Preflight-Anfragen
|
||||
allowedOriginPatterns = securityProperties.cors.allowedOriginPatterns.toList()
|
||||
allowedMethods = securityProperties.cors.allowedMethods.toList()
|
||||
|
||||
// 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()
|
||||
|
||||
// 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()
|
||||
|
||||
// Credentials erlauben – erforderlich für JWT-Authentifizierung
|
||||
// Aktiviert Cookies und Authorization-Header in Cross-Origin-Anfragen
|
||||
allowCredentials = securityProperties.cors.allowCredentials
|
||||
|
||||
// Preflight-Cache-Dauer – Performance-Optimierung
|
||||
// Reduziert die Anzahl an OPTIONS-Anfragen für wiederholte API-Aufrufe
|
||||
maxAge = securityProperties.cors.maxAge.seconds
|
||||
}
|
||||
|
||||
return UrlBasedCorsConfigurationSource().apply {
|
||||
// CORS-Konfiguration auf alle Gateway-Routen anwenden
|
||||
registerCorsConfiguration("/**", configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Konfigurationseigenschaften für die Sicherheits-Einstellungen des Gateways.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Beispielkonfiguration in application.yml:
|
||||
* ```yaml
|
||||
* gateway:
|
||||
* security:
|
||||
* cors:
|
||||
* allowed-origins:
|
||||
* - http://localhost:3000
|
||||
* - https://app.meldestelle.at
|
||||
* allowed-methods:
|
||||
* - GET
|
||||
* - POST
|
||||
* - PUT
|
||||
* - DELETE
|
||||
* allow-credentials: true
|
||||
* max-age: PT2H
|
||||
* ```
|
||||
* Konfigurations-Properties für alle sicherheitsrelevanten Einstellungen des Gateways.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "gateway.security")
|
||||
data class GatewaySecurityProperties(
|
||||
val cors: CorsProperties = CorsProperties()
|
||||
val cors: CorsProperties = CorsProperties(),
|
||||
val publicPaths: List<String> = listOf(
|
||||
"/",
|
||||
"/fallback/**",
|
||||
"/actuator/**",
|
||||
"/webjars/**",
|
||||
"/v3/api-docs/**",
|
||||
"/api/auth/**" // Alle Auth-Endpunkte
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* CORS-spezifische Konfigurationseigenschaften mit sinnvollen Defaults.
|
||||
*
|
||||
* 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
|
||||
* DTO für CORS-Properties mit sinnvollen Standardwerten.
|
||||
*/
|
||||
data class CorsProperties(
|
||||
/**
|
||||
* Erlaubte Ursprünge (Allowed Origins) für CORS-Anfragen.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Kann je Umgebung bei Bedarf überschrieben werden.
|
||||
*/
|
||||
val allowedOrigins: Set<String> = setOf(
|
||||
"http://localhost:3000",
|
||||
"http://localhost:8080",
|
||||
"http://localhost:4200",
|
||||
"https://app.meldestelle.at",
|
||||
"https://frontend.meldestelle.at",
|
||||
"https://www.meldestelle.at"
|
||||
),
|
||||
|
||||
|
||||
/**
|
||||
* Erlaubte HTTP-Methoden für CORS-Anfragen.
|
||||
*
|
||||
* 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"
|
||||
),
|
||||
|
||||
/**
|
||||
* Erlaubte Request-Header für CORS-Anfragen.
|
||||
*
|
||||
* 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",
|
||||
"Content-Type",
|
||||
"X-Requested-With",
|
||||
"X-Correlation-ID",
|
||||
"Accept",
|
||||
"Origin",
|
||||
"Cache-Control",
|
||||
"Pragma"
|
||||
),
|
||||
|
||||
/**
|
||||
* Sichtbare Response-Header für CORS-Anfragen.
|
||||
*
|
||||
* 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",
|
||||
"X-RateLimit-Limit",
|
||||
"X-RateLimit-Remaining",
|
||||
"X-RateLimit-Enabled",
|
||||
"Content-Length",
|
||||
"Date"
|
||||
),
|
||||
|
||||
/**
|
||||
* Credentials in CORS-Anfragen erlauben.
|
||||
*
|
||||
* Auf true setzen, um zu unterstützen:
|
||||
* - JWT Bearer Tokens im Authorization-Header
|
||||
* - Cookies (falls verwendet)
|
||||
* - Client-Zertifikate (falls verwendet)
|
||||
*/
|
||||
val allowedOriginPatterns: Set<String> = setOf("http://localhost:[*]", "https://*.meldestelle.at"),
|
||||
val allowedMethods: Set<String> = setOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"),
|
||||
val allowedHeaders: Set<String> = setOf("*"),
|
||||
val exposedHeaders: Set<String> = setOf("X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"),
|
||||
val allowCredentials: Boolean = true,
|
||||
|
||||
/**
|
||||
* Maximales Alter für das Caching von Preflight-Anfragen.
|
||||
*
|
||||
* 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)
|
||||
)
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
# ===================================================================
|
||||
# Meldestelle API Gateway Configuration
|
||||
# ===================================================================
|
||||
|
||||
server:
|
||||
port: ${GATEWAY_PORT:8081}
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: meldestelle-api-gateway
|
||||
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:dev,keycloak}
|
||||
|
||||
# Security Configuration
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
issuer-uri: ${KEYCLOAK_ISSUER_URI:http://keycloak:8080/realms/meldestelle}
|
||||
jwk-set-uri: ${KEYCLOAK_JWK_SET_URI:http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs}
|
||||
|
||||
# Spring Cloud Gateway Configuration
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
# Ping Service Route
|
||||
- id: ping-service
|
||||
uri: lb://ping-service
|
||||
predicates:
|
||||
- Path=/api/ping/**
|
||||
filters:
|
||||
- StripPrefix=2
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: ping-service-cb
|
||||
fallbackUri: forward:/fallback/ping
|
||||
|
||||
# Auth Service Route
|
||||
- id: auth-service
|
||||
uri: lb://auth-server
|
||||
predicates:
|
||||
- Path=/api/auth/**
|
||||
filters:
|
||||
- StripPrefix=2
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: auth-service-cb
|
||||
fallbackUri: forward:/fallback/auth
|
||||
|
||||
# Global CORS Configuration
|
||||
globalcors:
|
||||
corsConfigurations:
|
||||
'[/**]':
|
||||
allowedOriginPatterns:
|
||||
- "http://localhost:*"
|
||||
- "http://127.0.0.1:*"
|
||||
- "http://web-app:*"
|
||||
allowedMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- OPTIONS
|
||||
- PATCH
|
||||
allowedHeaders: "*"
|
||||
allowCredentials: true
|
||||
exposedHeaders:
|
||||
- Authorization
|
||||
- X-User-ID
|
||||
- X-User-Name
|
||||
- X-User-Role
|
||||
|
||||
# Keycloak Integration Settings
|
||||
keycloak:
|
||||
server-url: ${KEYCLOAK_SERVER_URL:http://keycloak:8080}
|
||||
realm: ${KEYCLOAK_REALM:meldestelle}
|
||||
client-id: ${KEYCLOAK_CLIENT_ID:api-gateway}
|
||||
client-secret: ${KEYCLOAK_CLIENT_SECRET:api-gateway-secret-key-change-in-production}
|
||||
issuer-uri: ${KEYCLOAK_ISSUER_URI:http://keycloak:8080/realms/meldestelle}
|
||||
jwk-set-uri: ${KEYCLOAK_JWK_SET_URI:http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs}
|
||||
|
||||
# Gateway Security Settings
|
||||
gateway:
|
||||
security:
|
||||
keycloak:
|
||||
enabled: ${GATEWAY_SECURITY_KEYCLOAK_ENABLED:true}
|
||||
public-paths: >
|
||||
/,
|
||||
/health/**,
|
||||
/actuator/**,
|
||||
/api/ping/health,
|
||||
/api/auth/login,
|
||||
/api/auth/register,
|
||||
/api/auth/refresh,
|
||||
/fallback/**,
|
||||
/docs/**,
|
||||
/swagger-ui/**
|
||||
|
||||
# Consul Service Discovery
|
||||
consul:
|
||||
host: ${CONSUL_HOST:consul}
|
||||
port: ${CONSUL_PORT:8500}
|
||||
enabled: ${CONSUL_ENABLED:true}
|
||||
|
||||
# Circuit Breaker Configuration
|
||||
resilience4j:
|
||||
circuitbreaker:
|
||||
instances:
|
||||
ping-service-cb:
|
||||
registerHealthIndicator: true
|
||||
slidingWindowSize: 10
|
||||
minimumNumberOfCalls: 5
|
||||
permittedNumberOfCallsInHalfOpenState: 3
|
||||
automaticTransitionFromOpenToHalfOpenEnabled: true
|
||||
waitDurationInOpenState: 10s
|
||||
failureRateThreshold: 50
|
||||
auth-service-cb:
|
||||
registerHealthIndicator: true
|
||||
slidingWindowSize: 10
|
||||
minimumNumberOfCalls: 5
|
||||
permittedNumberOfCallsInHalfOpenState: 3
|
||||
automaticTransitionFromOpenToHalfOpenEnabled: true
|
||||
waitDurationInOpenState: 10s
|
||||
failureRateThreshold: 50
|
||||
|
||||
# Actuator Management
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
|
||||
# Logging Configuration
|
||||
logging:
|
||||
level:
|
||||
at.mocode.infrastructure.gateway: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
org.springframework.cloud.gateway: DEBUG
|
||||
pattern:
|
||||
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
---
|
||||
# Development Profile
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
|
||||
keycloak:
|
||||
server-url: http://localhost:8180
|
||||
issuer-uri: http://localhost:8180/realms/meldestelle
|
||||
jwk-set-uri: http://localhost:8180/realms/meldestelle/protocol/openid-connect/certs
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
at.mocode: DEBUG
|
||||
|
||||
---
|
||||
# Production Profile
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: prod
|
||||
|
||||
keycloak:
|
||||
server-url: ${KEYCLOAK_SERVER_URL}
|
||||
issuer-uri: ${KEYCLOAK_ISSUER_URI}
|
||||
jwk-set-uri: ${KEYCLOAK_JWK_SET_URI}
|
||||
client-secret: ${KEYCLOAK_CLIENT_SECRET}
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
at.mocode: INFO
|
||||
|
||||
gateway:
|
||||
security:
|
||||
public-paths: >
|
||||
/,
|
||||
/health,
|
||||
/api/auth/login,
|
||||
/api/auth/register
|
||||
@@ -36,23 +36,6 @@ spring:
|
||||
pool:
|
||||
max-idle-time: 15s
|
||||
max-life-time: 60s
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowedOriginPatterns:
|
||||
- "https://*.meldestelle.at"
|
||||
- "http://localhost:*"
|
||||
allowedMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
- OPTIONS
|
||||
allowedHeaders:
|
||||
- "*"
|
||||
allowCredentials: true
|
||||
maxAge: 3600
|
||||
default-filters:
|
||||
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
|
||||
- name: CircuitBreaker
|
||||
@@ -282,20 +265,3 @@ logging:
|
||||
max-file-size: 100MB
|
||||
total-size-cap: 1GB
|
||||
max-history: 30
|
||||
|
||||
# Gateway Security Configuration - JWT Authentication with auth-client
|
||||
gateway:
|
||||
security:
|
||||
jwt:
|
||||
# Enable JWT authentication via auth-client
|
||||
enabled: ${GATEWAY_JWT_ENABLED:true}
|
||||
# JWT secret key for token validation (must match auth-server secret)
|
||||
secret: ${JWT_SECRET:default-secret-for-development-only-please-change-in-production}
|
||||
# JWT issuer (must match auth-server issuer)
|
||||
issuer: ${JWT_ISSUER:meldestelle-auth-server}
|
||||
# JWT audience (must match auth-server audience)
|
||||
audience: ${JWT_AUDIENCE:meldestelle-services}
|
||||
# JWT expiration in minutes
|
||||
expiration: ${JWT_EXPIRATION:60}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user