feat: flexibilisiere JWT-Validierung durch benutzerdefinierte Decoder und verbessere CORS-Konfiguration
This commit is contained in:
+20
-10
@@ -11,9 +11,8 @@ import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator
|
||||
import org.springframework.security.oauth2.jwt.*
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
@@ -38,7 +37,6 @@ class SecurityConfig(
|
||||
.authorizeExchange { exchanges ->
|
||||
exchanges
|
||||
.pathMatchers(*securityProperties.publicPaths.toTypedArray()).permitAll()
|
||||
.pathMatchers("/api/ping/**").permitAll() // TEMPORAER fuer Debugging
|
||||
.pathMatchers("/api/v1/import/zns", "/api/v1/import/zns/**").permitAll() // TEMPORAER fuer Debugging
|
||||
.anyExchange().authenticated()
|
||||
}
|
||||
@@ -67,16 +65,28 @@ class SecurityConfig(
|
||||
if (delegate == null) {
|
||||
if (jwkSetUri.isBlank()) {
|
||||
logger.error("JWK Set URI is missing – all authenticated requests will be rejected.")
|
||||
return Mono.error(org.springframework.security.oauth2.jwt.BadJwtException("Identity Provider not configured"))
|
||||
return Mono.error(BadJwtException("Identity Provider not configured"))
|
||||
}
|
||||
try {
|
||||
logger.info("Attempting to initialize JWT Decoder with URI: {}", jwkSetUri)
|
||||
delegate = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
|
||||
logger.info("JWT Decoder successfully initialized.")
|
||||
// Wir deaktivieren die Issuer-Validierung, da Keycloak intern "keycloak:8080"
|
||||
// und extern "localhost:8180" verwendet, was zu Mismatches führt.
|
||||
val nimbusDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
|
||||
nimbusDecoder.setJwtValidator(JwtValidators.createDefault()) // Standard-Validierung (ohne Issuer-Zwang falls nicht explizit konfiguriert)
|
||||
|
||||
// Da createDefault() den Issuer-Check einbaut, wenn spring.security.oauth2.resourceserver.jwt.issuer-uri gesetzt ist,
|
||||
// nutzen wir einen Custom Validator der den Issuer ignoriert oder flexibel ist.
|
||||
val withAudience = DelegatingOAuth2TokenValidator<Jwt>(
|
||||
JwtTimestampValidator(),
|
||||
// Hier koennte man weitere Validatoren hinzufuegen, aber wir lassen den Issuer weg
|
||||
)
|
||||
nimbusDecoder.setJwtValidator(withAudience)
|
||||
|
||||
delegate = nimbusDecoder
|
||||
logger.info("JWT Decoder successfully initialized (Issuer check disabled for environment flexibility).")
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Could not initialize JWT Decoder: {}", e.message)
|
||||
// Throw BadJwtException so Spring Security returns 401, not 500 or passthrough
|
||||
return Mono.error(org.springframework.security.oauth2.jwt.BadJwtException("Identity Provider unavailable: ${e.message}"))
|
||||
return Mono.error(BadJwtException("Identity Provider unavailable: ${e.message}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +117,7 @@ class SecurityConfig(
|
||||
val configuration = CorsConfiguration().apply {
|
||||
allowedOriginPatterns = securityProperties.cors.allowedOriginPatterns.toList()
|
||||
allowedMethods = securityProperties.cors.allowedMethods.toList()
|
||||
allowedHeaders = securityProperties.cors.allowedHeaders.toList()
|
||||
allowedHeaders = listOf("*") // Alles erlauben fuer Postman/Frontend
|
||||
exposedHeaders = securityProperties.cors.exposedHeaders.toList()
|
||||
allowCredentials = securityProperties.cors.allowCredentials
|
||||
maxAge = securityProperties.cors.maxAge.seconds
|
||||
|
||||
@@ -44,6 +44,27 @@ spring:
|
||||
issuer-uri: ${SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI:http://localhost:8180/realms/meldestelle}
|
||||
jwk-set-uri: ${SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI:http://localhost:8180/realms/meldestelle/protocol/openid-connect/certs}
|
||||
|
||||
gateway:
|
||||
security:
|
||||
cors:
|
||||
allowed-origin-patterns:
|
||||
- "http://localhost:*"
|
||||
- "https://*.meldestelle.at"
|
||||
- "https://*.mo-code.at"
|
||||
- "https://*.postman.co"
|
||||
- "postman://*"
|
||||
allowed-methods:
|
||||
- "GET"
|
||||
- "POST"
|
||||
- "PUT"
|
||||
- "DELETE"
|
||||
- "OPTIONS"
|
||||
- "PATCH"
|
||||
allowed-headers:
|
||||
- "*"
|
||||
allow-credentials: true
|
||||
max-age: 3600s
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
|
||||
+18
-2
@@ -6,6 +6,8 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator
|
||||
import org.springframework.security.oauth2.jwt.*
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
@@ -26,12 +28,11 @@ class GlobalSecurityConfig {
|
||||
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
|
||||
.addFilterBefore(DeviceSecurityFilter(), UsernamePasswordAuthenticationFilter::class.java)
|
||||
.authorizeHttpRequests { auth ->
|
||||
// Explizite Freigaben (Health, Info, Public Endpoints)
|
||||
// Explizite Freigaben (Health, Information, Public-Endpoints)
|
||||
auth.requestMatchers("/actuator/**").permitAll()
|
||||
auth.requestMatchers("/api/v1/devices/register").permitAll() // Onboarding erlauben
|
||||
auth.requestMatchers("/ping/public").permitAll()
|
||||
auth.requestMatchers("/ping/simple").permitAll()
|
||||
auth.requestMatchers("/ping/enhanced").permitAll()
|
||||
auth.requestMatchers("/ping/health").permitAll()
|
||||
auth.requestMatchers("/error").permitAll()
|
||||
|
||||
@@ -41,12 +42,27 @@ class GlobalSecurityConfig {
|
||||
.oauth2ResourceServer { oauth2 ->
|
||||
oauth2.jwt { jwt ->
|
||||
jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
|
||||
// Auch hier den Issuer-Check entspannen, da der Service intern validiert
|
||||
jwt.decoder(jwtDecoder())
|
||||
}
|
||||
}
|
||||
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
// Wenn jwk-set-uri gesetzt ist, nutzen wir sie.
|
||||
// Wir verzichten auf den Issuer-Check für maximale Flexibilität zwischen Docker/Host.
|
||||
val jwkSetUri = System.getenv("SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI")
|
||||
?: "http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs"
|
||||
|
||||
val decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
|
||||
val validator = DelegatingOAuth2TokenValidator<Jwt>(JwtTimestampValidator())
|
||||
decoder.setJwtValidator(validator)
|
||||
return decoder
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
|
||||
val converter = JwtAuthenticationConverter()
|
||||
|
||||
Reference in New Issue
Block a user