Versuche
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
package org.springframework.boot.data.redis.autoconfigure;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Dummy class to satisfy Spring Cloud Gateway 2025.1.0 imports which expect this class
|
||||
* to be present at this location, even though Spring Boot 3.5.9 moved it.
|
||||
*/
|
||||
@Configuration
|
||||
public class DataRedisReactiveAutoConfiguration {
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.springframework.boot.webflux.autoconfigure;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Dummy class to satisfy Spring Cloud Gateway 2025.1.0 imports which expect this class
|
||||
* to be present at this location, even though Spring Boot 3.5.9 moved it.
|
||||
*/
|
||||
@Configuration
|
||||
public class HttpHandlerAutoConfiguration {
|
||||
}
|
||||
+14
-1
@@ -1,11 +1,24 @@
|
||||
package at.mocode.infrastructure.gateway
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.core.env.Environment
|
||||
|
||||
@SpringBootApplication
|
||||
class GatewayApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<GatewayApplication>(*args)
|
||||
val context = runApplication<GatewayApplication>(*args)
|
||||
val logger = LoggerFactory.getLogger(GatewayApplication::class.java)
|
||||
val env = context.getBean(Environment::class.java)
|
||||
val port = env.getProperty("server.port") ?: "8081"
|
||||
|
||||
logger.info("""
|
||||
----------------------------------------------------------
|
||||
Application 'Gateway' is running!
|
||||
Port: $port
|
||||
Profiles: ${env.activeProfiles.joinToString(", ").ifEmpty { "default" }}
|
||||
----------------------------------------------------------
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
+6
@@ -1,5 +1,6 @@
|
||||
package at.mocode.infrastructure.gateway.config
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter
|
||||
import org.springframework.core.Ordered
|
||||
@@ -18,6 +19,8 @@ import java.util.*
|
||||
@Component
|
||||
class CorrelationIdFilter : GlobalFilter, Ordered {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(CorrelationIdFilter::class.java)
|
||||
|
||||
companion object {
|
||||
const val CORRELATION_ID_HEADER = "X-Correlation-ID"
|
||||
}
|
||||
@@ -39,6 +42,9 @@ class CorrelationIdFilter : GlobalFilter, Ordered {
|
||||
mutatedExchange.response.headers.add(CORRELATION_ID_HEADER, correlationId)
|
||||
|
||||
return chain.filter(mutatedExchange)
|
||||
.doOnError { ex ->
|
||||
logger.error("Error in CorrelationIdFilter for request {}: {}", request.uri, ex.message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE
|
||||
|
||||
+6
@@ -1,6 +1,7 @@
|
||||
package at.mocode.infrastructure.gateway.config
|
||||
|
||||
import at.mocode.infrastructure.gateway.config.CorrelationIdFilter.Companion.CORRELATION_ID_HEADER
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.MDC
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter
|
||||
@@ -19,6 +20,8 @@ import reactor.core.publisher.Mono
|
||||
@Component
|
||||
class MdcCorrelationFilter : GlobalFilter, Ordered {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(MdcCorrelationFilter::class.java)
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
|
||||
val correlationId = exchange.request.headers.getFirst(CORRELATION_ID_HEADER)
|
||||
if (correlationId != null) {
|
||||
@@ -26,6 +29,9 @@ class MdcCorrelationFilter : GlobalFilter, Ordered {
|
||||
}
|
||||
|
||||
return chain.filter(exchange)
|
||||
.doOnError { ex ->
|
||||
logger.error("Error in MdcCorrelationFilter: {}", ex.message)
|
||||
}
|
||||
// Bei Abschluss säubern, um Leaks über Thread-Grenzen zu vermeiden
|
||||
.doFinally { MDC.remove(CORRELATION_ID_HEADER) }
|
||||
}
|
||||
|
||||
+5
@@ -1,6 +1,7 @@
|
||||
package at.mocode.infrastructure.gateway.error
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
@@ -15,12 +16,16 @@ import reactor.core.publisher.Mono
|
||||
@Component
|
||||
class ProblemDetailsExceptionHandler : ErrorWebExceptionHandler {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(ProblemDetailsExceptionHandler::class.java)
|
||||
private val mapper = ObjectMapper()
|
||||
|
||||
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
|
||||
// Versuche, Status aus Attributen zu lesen, ansonsten 500
|
||||
val status = exchange.response.statusCode?.value() ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
|
||||
val traceId = exchange.request.headers.getFirst("X-Correlation-ID")
|
||||
|
||||
logger.error("Gateway error [{}]: {} (TraceId: {})", status, ex.message, traceId, ex)
|
||||
|
||||
val body = mapOf(
|
||||
"type" to "about:blank",
|
||||
"title" to (ex.message ?: "Unexpected error"),
|
||||
|
||||
+4
-3
@@ -20,10 +20,12 @@ import java.time.Duration
|
||||
@Component
|
||||
class GatewayHealthIndicator(
|
||||
private val discoveryClient: DiscoveryClient,
|
||||
private val webClient: WebClient.Builder,
|
||||
webClientBuilder: WebClient.Builder,
|
||||
private val environment: Environment
|
||||
) : ReactiveHealthIndicator {
|
||||
|
||||
private val webClient = webClientBuilder.build()
|
||||
|
||||
companion object {
|
||||
private val CRITICAL_SERVICES = setOf(
|
||||
"ping-service"
|
||||
@@ -120,8 +122,7 @@ class GatewayHealthIndicator(
|
||||
} else {
|
||||
val instance = instances.first()
|
||||
val healthUrl = "http://${instance.host}:${instance.port}/actuator/health"
|
||||
val client = webClient.build()
|
||||
client.get()
|
||||
webClient.get()
|
||||
.uri(healthUrl)
|
||||
.retrieve()
|
||||
.bodyToMono(Map::class.java)
|
||||
|
||||
+9
-5
@@ -1,5 +1,6 @@
|
||||
package at.mocode.infrastructure.gateway.security
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
@@ -24,6 +25,8 @@ class SecurityConfig(
|
||||
private val securityProperties: GatewaySecurityProperties
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(SecurityConfig::class.java)
|
||||
|
||||
/**
|
||||
* Konfiguriert die zentrale Security-Filter-Kette für das Gateway.
|
||||
*
|
||||
@@ -78,12 +81,12 @@ class SecurityConfig(
|
||||
NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
|
||||
} catch (e: Exception) {
|
||||
// Log warning and return a no-op decoder to allow startup
|
||||
println("WARN: Failed to configure JWT decoder with JWK Set URI: $jwkSetUri - ${e.message}")
|
||||
println("WARN: JWT authentication will not work until Keycloak is available")
|
||||
logger.warn("Failed to configure JWT decoder with JWK Set URI: {} - {}", jwkSetUri, e.message)
|
||||
logger.warn("JWT authentication will not work until Keycloak is available")
|
||||
createNoOpJwtDecoder()
|
||||
}
|
||||
} else {
|
||||
println("INFO: No JWK Set URI configured, using no-op JWT decoder")
|
||||
logger.info("No JWK Set URI configured, using no-op JWT decoder")
|
||||
createNoOpJwtDecoder()
|
||||
}
|
||||
}
|
||||
@@ -106,10 +109,11 @@ class SecurityConfig(
|
||||
fun realmRolesJwtAuthenticationConverter(): org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter {
|
||||
val converter = org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter()
|
||||
converter.setJwtGrantedAuthoritiesConverter { jwt ->
|
||||
val roles = (jwt.claims["realm_access"] as? Map<*, *>)?.get("roles") as? Collection<*> ?: emptyList<Any>()
|
||||
val realmAccess = jwt.claims["realm_access"] as? Map<*, *>
|
||||
val roles = realmAccess?.get("roles") as? Collection<*> ?: emptyList<Any>()
|
||||
roles
|
||||
.filterIsInstance<String>()
|
||||
.map { role -> org.springframework.security.core.authority.SimpleGrantedAuthority("ROLE_" + role.lowercase()) }
|
||||
.map { role -> org.springframework.security.core.authority.SimpleGrantedAuthority("ROLE_${role.uppercase()}") }
|
||||
}
|
||||
return org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter(converter)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ spring:
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration
|
||||
- org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
|
||||
cloud:
|
||||
gateway:
|
||||
httpclient:
|
||||
|
||||
Reference in New Issue
Block a user