security: Users can only access their own resources

fix: Make sure JWTs from Keycloak are understood and CORS is respected
This commit is contained in:
2025-08-10 23:47:48 +02:00
parent 18aa9af708
commit 7439bf999b
14 changed files with 144 additions and 200 deletions
-2
View File
@@ -126,8 +126,6 @@ spring-boot-server = [
"spring-ui", "spring-ui",
] ]
spring-boot-security = [ spring-boot-security = [
"keycloak-core",
"keycloak-policy-enforcer",
"spring-boot-oauth2-resource-server", "spring-boot-oauth2-resource-server",
"spring-boot-security", "spring-boot-security",
] ]
@@ -14,6 +14,9 @@ import io.swagger.v3.oas.annotations.tags.Tag
import org.mapstruct.factory.Mappers import org.mapstruct.factory.Mappers
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.security.authorization.AuthorizationDeniedException
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@RestController @RestController
@@ -60,6 +63,9 @@ class UserController(
) )
]) body: User ]) body: User
): ResponseEntity<User> { ): ResponseEntity<User> {
val authentication = SecurityContextHolder.getContext().authentication
val jwt = authentication as Jwt
val authorizedParty = jwt.getClaimAsString("azp")
var created = false var created = false
val userIdConverted = UserId.of(NonEmptyString(userId)) val userIdConverted = UserId.of(NonEmptyString(userId))
@@ -67,9 +73,11 @@ class UserController(
if (user == null) { if (user == null) {
created = true created = true
user = mapper.mapToDB(body) user = mapper.mapToDB(body)
} else { } else if (authorizedParty == userId) {
user.name = NonEmptyString(body.name) user.name = NonEmptyString(body.name)
user.email = Email.of(NonEmptyString(body.email)) user.email = Email.of(NonEmptyString(body.email))
} else {
throw AuthorizationDeniedException("The user is not authorized to update this user")
} }
userRepository.save(user) userRepository.save(user)
@@ -0,0 +1,24 @@
package de.twomartens.timetable.configuration
import de.twomartens.timetable.configuration.roles.CheckParty
import de.twomartens.timetable.configuration.roles.PartyAuthorizationManager
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut
import org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Role
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor
@Configuration
class AuthorizationManagerConfiguration {
@Bean
@Role(ROLE_INFRASTRUCTURE)
fun checkPartyInterceptor(): AuthorizationManagerBeforeMethodInterceptor {
val pointcut = AnnotationMatchingPointcut(null, CheckParty::class.java, true)
val interceptor = AuthorizationManagerBeforeMethodInterceptor(pointcut, PartyAuthorizationManager())
interceptor.order = AuthorizationInterceptorsOrder.PRE_AUTHORIZE.order
return interceptor
}
}
@@ -0,0 +1,26 @@
package de.twomartens.timetable.configuration
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Configuration
class CorsConfig {
@Bean
@Qualifier("corsApplication")
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("http://localhost:8100", "https://timetable.2martens.de") // frontend origin
configuration.allowedMethods = listOf("GET", "POST", "PUT", "HEAD", "DELETE", "OPTIONS")
configuration.allowedHeaders = listOf("*")
configuration.allowCredentials = true
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return source
}
}
@@ -1,73 +1,36 @@
package de.twomartens.timetable.configuration package de.twomartens.timetable.configuration
import de.twomartens.support.security.SpringPolicyEnforcerFilter import org.springframework.beans.factory.annotation.Qualifier
import org.keycloak.adapters.authorization.spi.ConfigurationResolver
import org.keycloak.adapters.authorization.spi.HttpRequest
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig
import org.keycloak.util.JsonSerialization
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod
import org.springframework.security.config.Customizer import org.springframework.security.config.Customizer
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.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder
import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.SecurityFilterChain
import java.io.IOException import org.springframework.web.cors.CorsConfigurationSource
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity
open class WebSecurityConfiguration { open class WebSecurityConfiguration {
@Value("\${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
private lateinit var jwkSetUri: String
@Value("#{environment.CLIENT_SECRET}")
private lateinit var clientSecret: String
@Bean @Bean
@Throws(Exception::class) @Throws(Exception::class)
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { open fun securityFilterChain(http: HttpSecurity,
@Qualifier("corsApplication") corsConfiguration: CorsConfigurationSource): SecurityFilterChain {
http http
.cors { it.configurationSource(corsConfiguration) }
.csrf { it.disable() } .csrf { it.disable() }
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.authorizeHttpRequests { it.requestMatchers(*PERMITTED_PATHS.toTypedArray<String>()).permitAll() } .authorizeHttpRequests { it.requestMatchers(*PERMITTED_PATHS.toTypedArray<String>()).permitAll() }
.authorizeHttpRequests { it.requestMatchers(HttpMethod.OPTIONS).permitAll() } .authorizeHttpRequests { it.requestMatchers(HttpMethod.OPTIONS).permitAll() }
.authorizeHttpRequests { it.anyRequest().authenticated() } .authorizeHttpRequests { it.anyRequest().authenticated() }
.oauth2ResourceServer { obj: OAuth2ResourceServerConfigurer<HttpSecurity?> -> obj.jwt(Customizer.withDefaults()) } .oauth2ResourceServer { it.jwt(Customizer.withDefaults()) }
return http.build() return http.build()
} }
@Bean
open fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
}
private fun createPolicyEnforcerFilter(): SpringPolicyEnforcerFilter {
return SpringPolicyEnforcerFilter(object : ConfigurationResolver {
override fun resolve(request: HttpRequest): PolicyEnforcerConfig {
return try {
val policyEnforcerConfig = JsonSerialization.readValue(
javaClass.getResourceAsStream("/policy-enforcer.json"), PolicyEnforcerConfig::class.java
)
policyEnforcerConfig.credentials = mapOf(Pair("secret", clientSecret))
if (request.method == HttpMethod.OPTIONS.name()) {
// always allow options request
policyEnforcerConfig.enforcementMode = EnforcementMode.DISABLED
} else {
policyEnforcerConfig.paths = PATHS
}
policyEnforcerConfig
} catch (e: IOException) {
throw RuntimeException(e)
}
}
})
}
companion object { companion object {
private val PERMITTED_PATHS: Collection<String> = listOf( private val PERMITTED_PATHS: Collection<String> = listOf(
"/timetable/healthCheck", "/timetable/healthCheck",
@@ -77,17 +40,5 @@ open class WebSecurityConfiguration {
"/error", "/error",
"/timetable/version", "/timetable/version",
) )
private val PATHS = buildPathConfigs()
private fun buildPathConfigs(): List<PathConfig> {
val paths: MutableList<PathConfig> = mutableListOf()
for (path in PERMITTED_PATHS) {
val pathConfig = PathConfig()
pathConfig.path = path.replace("**", "*")
pathConfig.enforcementMode = EnforcementMode.DISABLED
paths.add(pathConfig)
}
return paths
}
} }
} }
@@ -0,0 +1,11 @@
package de.twomartens.timetable.configuration.roles
import java.lang.annotation.Inherited
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Inherited
@MustBeDocumented
annotation class CheckParty(
val partyParamName: String // Name of method argument holding the party ID
)
@@ -0,0 +1,43 @@
package de.twomartens.timetable.configuration.roles
import org.aopalliance.intercept.MethodInvocation
import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.authorization.AuthorizationDeniedException
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.authorization.AuthorizationResult
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler
import org.springframework.security.core.Authentication
import java.util.function.Supplier
class PartyAuthorizationManager : AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
@Deprecated("Deprecated in Spring Security")
override fun check(authentication: Supplier<Authentication?>?, invocation: MethodInvocation?): AuthorizationDecision {
if (authentication == null || authentication.get() == null || !authentication.get()!!.isAuthenticated) {
return AuthorizationDecision(false)
}
val annotation = invocation!!.method.getAnnotation(CheckParty::class.java)
?: invocation.method.declaringClass.getAnnotation(CheckParty::class.java)
?: return AuthorizationDecision(true) // No annotation, allow
val authentication = authentication.get()!!
val partyParamName = annotation.partyParamName
// 1. Authorized party check
val methodParams = invocation.method.parameters
val argIndex = methodParams.indexOfFirst { it.name == partyParamName }
if (argIndex == -1) {
return AuthorizationDecision(false)
}
val requestedPartyId = invocation.arguments[argIndex]?.toString()
val tokenPartyId = (authentication.details as? Map<*, *>)?.get("azp")?.toString()
?: authentication.name // fallback
return AuthorizationDecision(requestedPartyId == tokenPartyId)
}
override fun handleDeniedInvocation(methodInvocation: MethodInvocation?, authorizationResult: AuthorizationResult?): Any? {
throw AuthorizationDeniedException("The requested resources are not accessible to the authorized party")
}
}
@@ -1,6 +1,7 @@
package de.twomartens.timetable.formation package de.twomartens.timetable.formation
import de.twomartens.timetable.auth.UserRepository import de.twomartens.timetable.auth.UserRepository
import de.twomartens.timetable.configuration.roles.CheckParty
import de.twomartens.timetable.model.common.FormationId import de.twomartens.timetable.model.common.FormationId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.model.dto.Formation import de.twomartens.timetable.model.dto.Formation
@@ -44,6 +45,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@GetMapping("/{userId}/") @GetMapping("/{userId}/")
fun getFormations( fun getFormations(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -96,6 +98,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@GetMapping("/{userId}/{id}") @GetMapping("/{userId}/{id}")
fun getFormation( fun getFormation(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -144,6 +147,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@PutMapping("/{userId}/{id}") @PutMapping("/{userId}/{id}")
fun putFormation( fun putFormation(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -236,6 +240,7 @@ class FormationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@DeleteMapping("/{userId}/{id}") @DeleteMapping("/{userId}/{id}")
fun deleteFormation( fun deleteFormation(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -1,6 +1,7 @@
package de.twomartens.timetable.route package de.twomartens.timetable.route
import de.twomartens.timetable.auth.UserRepository import de.twomartens.timetable.auth.UserRepository
import de.twomartens.timetable.configuration.roles.CheckParty
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.model.dto.TswRoute import de.twomartens.timetable.model.dto.TswRoute
@@ -43,6 +44,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@GetMapping("/{userId}/") @GetMapping("/{userId}/")
fun getRoutes( fun getRoutes(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -87,6 +89,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@GetMapping("/{userId}/{id}") @GetMapping("/{userId}/{id}")
fun getRoute( fun getRoute(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -130,6 +133,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@PutMapping("/{userId}/{id}") @PutMapping("/{userId}/{id}")
fun putRoute( fun putRoute(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -195,6 +199,7 @@ class RouteController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@CheckParty(partyParamName = "userId")
@DeleteMapping("/{userId}/{id}") @DeleteMapping("/{userId}/{id}")
fun deleteRoute( fun deleteRoute(
@PathVariable @Parameter(description = "The id of the user", @PathVariable @Parameter(description = "The id of the user",
@@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.tags.Tag
import org.mapstruct.factory.Mappers import org.mapstruct.factory.Mappers
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@RestController @RestController
@@ -87,6 +88,7 @@ class StationController(
) )
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/update") @PostMapping("/update")
fun updateStations(): ResponseEntity<Void> { fun updateStations(): ResponseEntity<Void> {
val stations = bahnApiService.fetchStations("*") val stations = bahnApiService.fetchStations("*")
@@ -38,7 +38,9 @@ spring:
oauth2: oauth2:
resourceserver: resourceserver:
jwt: jwt:
jws-algorithms: ES256
jwk-set-uri: ${KEYCLOAK_URL:https://id.2martens.de}/realms/${KEYCLOAK_REALM:twomartens}/protocol/openid-connect/certs jwk-set-uri: ${KEYCLOAK_URL:https://id.2martens.de}/realms/${KEYCLOAK_REALM:twomartens}/protocol/openid-connect/certs
issuer-uri: ${KEYCLOAK_URL:https://id.2martens.de}/realms/${KEYCLOAK_REALM:twomartens}
application: application:
name: timetable name: timetable
@@ -79,10 +81,8 @@ de.twomartens.timetable:
bahn-api: bahn-api:
client-id: ${DB_CLIENT_ID} client-id: ${DB_CLIENT_ID}
client-secret: ${DB_CLIENT_SECRET} client-secret: ${DB_CLIENT_SECRET}
greeting: "Hello %s, on millenium"
statusprobe: statusprobe:
scheduleDuration: 60 scheduleDuration: 60
maxKafkaFailureDuration: 5
maxBlobFailureCount: 5 maxBlobFailureCount: 5
maxFailurePercent: 50 maxFailurePercent: 50
de.twomartens.support.health.greeting: "Good morning" de.twomartens.support.health.greeting: "Good morning"
@@ -5,6 +5,7 @@ import de.twomartens.support.model.dto.ErrorMessage
import mu.KotlinLogging import mu.KotlinLogging
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.security.authorization.AuthorizationDeniedException
import org.springframework.web.bind.annotation.ControllerAdvice import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@@ -23,6 +24,12 @@ class ExceptionController : ResponseEntityExceptionHandler() {
.body(ErrorMessage(e.message!!)) .body(ErrorMessage(e.message!!))
} }
@ExceptionHandler(AuthorizationDeniedException::class)
fun handleDeniedException(e: AuthorizationDeniedException): ResponseEntity<ErrorMessage> {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ErrorMessage(e.message!!))
}
@ExceptionHandler(RuntimeException::class) @ExceptionHandler(RuntimeException::class)
fun handleRuntimeException(e: RuntimeException): ResponseEntity<ErrorMessage> { fun handleRuntimeException(e: RuntimeException): ResponseEntity<ErrorMessage> {
log.error("unexpected exception occurred", e) log.error("unexpected exception occurred", e)
@@ -1,65 +0,0 @@
package de.twomartens.support.security
import mu.KotlinLogging
import org.keycloak.AuthorizationContext
import org.keycloak.adapters.authorization.PolicyEnforcer
import org.keycloak.adapters.authorization.spi.HttpRequest
import org.keycloak.adapters.authorization.spi.HttpResponse
import org.keycloak.authorization.client.ClientAuthorizationContext
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode
import org.keycloak.representations.idm.authorization.Permission
class SpringPolicyEnforcer(private val policyEnforcer: PolicyEnforcer,
private val policyEnforcerConfig: PolicyEnforcerConfig) {
fun enforce(request: HttpRequest, response: HttpResponse): AuthorizationContext {
if (log.isDebugEnabled) {
log.debug("Policy enforcement is enabled. Enforcing policy decisions for path [{}].", request.uri)
}
val context = authorize(request, response)
if (log.isDebugEnabled) {
log.debug("Policy enforcement result for path [{}] is : {}", request.uri, if (context.isGranted) "GRANTED" else "DENIED")
log.debug("Returning authorization context with permissions:")
for (permission in context.permissions) {
log.debug(permission.toString())
}
}
return context
}
private fun authorize(request: HttpRequest, response: HttpResponse): AuthorizationContext {
val enforcementMode = policyEnforcerConfig.enforcementMode
return if (EnforcementMode.DISABLED == enforcementMode) {
createAuthorizedContext()
} else policyEnforcer.enforce(request, response)
}
private fun createAuthorizedContext(): AuthorizationContext {
return object : ClientAuthorizationContext(policyEnforcer.authzClient) {
override fun hasPermission(resourceName: String, scopeName: String): Boolean {
return true
}
override fun hasResourcePermission(resourceName: String): Boolean {
return true
}
override fun hasScopePermission(scopeName: String): Boolean {
return true
}
override fun getPermissions(): List<Permission> {
return emptyList()
}
override fun isGranted(): Boolean {
return true
}
}
}
companion object {
private val log = KotlinLogging.logger {}
}
}
@@ -1,71 +0,0 @@
package de.twomartens.support.security
import jakarta.servlet.*
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import mu.KotlinLogging
import org.keycloak.AuthorizationContext
import org.keycloak.adapters.authorization.PolicyEnforcer
import org.keycloak.adapters.authorization.integration.elytron.ServletHttpRequest
import org.keycloak.adapters.authorization.integration.elytron.ServletHttpResponse
import org.keycloak.adapters.authorization.spi.ConfigurationResolver
import org.keycloak.adapters.authorization.spi.HttpRequest
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig
import java.io.IOException
class SpringPolicyEnforcerFilter(private val configResolver: ConfigurationResolver) : Filter {
@Throws(IOException::class, ServletException::class)
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse?, filterChain: FilterChain) {
val request = servletRequest as HttpServletRequest
val response = servletResponse as HttpServletResponse?
val httpRequest = ServletHttpRequest(request) { extractBearerToken(request) }
val policyEnforcer = createPolicyEnforcer(httpRequest)
val authzContext = policyEnforcer.enforce(httpRequest, ServletHttpResponse(response))
request.setAttribute(AuthorizationContext::class.java.name, authzContext)
if (authzContext.isGranted) {
log.debug("Request authorized, continuing the filter chain")
filterChain.doFilter(servletRequest, servletResponse)
} else {
log.debug("Unauthorized request to path [{}], aborting the filter chain", request.requestURI)
}
}
private fun extractBearerToken(request: HttpServletRequest): String? {
val authorizationHeaderValues = request.getHeaders("Authorization")
while (authorizationHeaderValues.hasMoreElements()) {
val value = authorizationHeaderValues.nextElement()
val parts = value.trim()
.split("\\s+".toRegex())
.dropLastWhile { it.isEmpty() }
.toTypedArray()
if (parts.size != 2) {
continue
}
val bearer = parts[0]
if (bearer.equals("Bearer", ignoreCase = true)) {
return parts[1]
}
}
return null
}
private fun createPolicyEnforcer(request: HttpRequest): SpringPolicyEnforcer {
return createPolicyEnforcer(configResolver.resolve(request))
}
private fun createPolicyEnforcer(enforcerConfig: PolicyEnforcerConfig): SpringPolicyEnforcer {
val authServerUrl = enforcerConfig.authServerUrl
return SpringPolicyEnforcer(PolicyEnforcer.builder()
.authServerUrl(authServerUrl)
.realm(enforcerConfig.realm)
.clientId(enforcerConfig.resource)
.credentials(enforcerConfig.credentials)
.bearerOnly(false)
.enforcerConfig(enforcerConfig).build(), enforcerConfig)
}
companion object {
private val log = KotlinLogging.logger {}
}
}