chore(ping-service, security): integrate centralized security module and enhance Ping-Service

- Replaced local `SecurityConfig` in `ping-service` with the shared `infrastructure:security` module.
- Added `GlobalSecurityConfig` to standardize OAuth2, JWT validation, and CORS for all services.
- Introduced new endpoints (`/ping/public`, `/ping/secure`) with role-based access control.
- Updated database schema with Flyway migration (`V1__init_ping.sql`) and refactored persistence layer to align with the standardized approach (`createdAt` field).
- Enhanced application configuration (`application.yaml`) to use shared security and Flyway settings.
This commit is contained in:
2026-01-16 19:11:48 +01:00
parent 9456f28562
commit 05962487e7
14 changed files with 234 additions and 124 deletions
@@ -25,7 +25,12 @@ spring:
response-timeout: 5s
routes:
- id: ping-service
uri: http://ping-service:8080
# Nutze lb:// wenn Service Discovery aktiv ist, sonst http://hostname:port
# Da wir Consul nutzen, ist lb://ping-service besser, aber für Tracer Bullet
# und direkte Docker-Kommunikation ist http://ping-service:8082 sicherer,
# falls Consul noch nicht 100% stabil ist.
# Wir nutzen hier den Docker Alias und den konfigurierten Port.
uri: http://ping-service:8082
predicates:
- Path=/api/ping/**
filters:
@@ -42,13 +47,12 @@ management:
include: health,info,prometheus
tracing:
sampling:
probability: 1.0 # 100% der Requests tracen (für Dev/Test sinnvoll, in Prod reduzieren)
probability: 1.0
propagation:
type: w3c # Standard W3C Trace Context (kompatibel mit OpenTelemetry)
type: w3c
# Gateway-spezifische Einstellungen
gateway:
ratelimit:
enabled: false # Start: ausgeschaltet; zum Aktivieren default-filters plus RequestRateLimiter in YAML hinzufügen
enabled: false
replenish-rate: 10
burst-capacity: 20
@@ -0,0 +1,64 @@
package at.mocode.infrastructure.security
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
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.server.resource.authentication.JwtAuthenticationConverter
import org.springframework.security.web.SecurityFilterChain
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // Erlaubt @PreAuthorize in Services/Controllern
class GlobalSecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { it.disable() } // CSRF nicht nötig für Stateless REST APIs
.cors { it.configurationSource(corsConfigurationSource()) }
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.authorizeHttpRequests { auth ->
// Explizite Freigaben (Health, Info, Public Endpoints)
auth.requestMatchers("/actuator/**").permitAll()
auth.requestMatchers("/ping/public").permitAll()
auth.requestMatchers("/error").permitAll()
// Alles andere muss authentifiziert sein
auth.anyRequest().authenticated()
}
.oauth2ResourceServer { oauth2 ->
oauth2.jwt { jwt ->
jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
}
}
return http.build()
}
@Bean
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
val converter = JwtAuthenticationConverter()
converter.setJwtGrantedAuthoritiesConverter(KeycloakRoleConverter())
return converter
}
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
// Erlaube Frontend (localhost, docker host)
configuration.allowedOriginPatterns = listOf("*") // Für Dev; in Prod einschränken!
configuration.allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS")
configuration.allowedHeaders = listOf("*")
configuration.allowCredentials = true
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return source
}
}
@@ -0,0 +1,40 @@
package at.mocode.infrastructure.security
import org.springframework.core.convert.converter.Converter
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.jwt.Jwt
/**
* Konvertiert Keycloak-Rollen aus dem JWT (Realm Access & Resource Access)
* in Spring Security GrantedAuthorities.
*
* Erwartetes Format im Token:
* "realm_access": { "roles": ["admin", "user"] }
* "resource_access": { "my-client": { "roles": ["client-role"] } }
*/
class KeycloakRoleConverter : Converter<Jwt, Collection<GrantedAuthority>> {
override fun convert(jwt: Jwt): Collection<GrantedAuthority> {
val roles = mutableSetOf<String>()
// 1. Realm Roles extrahieren
val realmAccess = jwt.claims["realm_access"] as? Map<*, *>
if (realmAccess != null) {
(realmAccess["roles"] as? List<*>)?.forEach { role ->
if (role is String) {
roles.add(role)
}
}
}
// 2. Resource (Client) Roles extrahieren
// Optional: Falls wir Client-spezifische Rollen brauchen.
// Hier mappen wir vorerst nur Realm-Rollen global.
// 3. Mapping zu GrantedAuthority (Prefix "ROLE_" ist Standard in Spring Security)
return roles.map { role ->
SimpleGrantedAuthority("ROLE_${role.uppercase()}")
}
}
}