mirror of
https://github.com/2martens/tsw-timetable.git
synced 2026-10-17 16:06:51 +02:00
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:
@@ -5,6 +5,7 @@ import de.twomartens.support.model.dto.ErrorMessage
|
||||
import mu.KotlinLogging
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
@@ -23,6 +24,12 @@ class ExceptionController : ResponseEntityExceptionHandler() {
|
||||
.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)
|
||||
fun handleRuntimeException(e: RuntimeException): ResponseEntity<ErrorMessage> {
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
-71
@@ -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 {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user