All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.infobip.kafkistry.webapp.security.UnauthorizedEntryPoint.kt Maven / Gradle / Ivy

The newest version!
package com.infobip.kafkistry.webapp.security

import com.fasterxml.jackson.databind.ObjectMapper
import com.infobip.kafkistry.api.exception.ApiError
import com.infobip.kafkistry.utils.deepToString
import com.infobip.kafkistry.webapp.WebHttpProperties
import org.springframework.http.HttpStatus
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.authentication.InsufficientAuthenticationException
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.csrf.InvalidCsrfTokenException
import org.springframework.stereotype.Component
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpMethod
import org.springframework.security.web.access.AccessDeniedHandlerImpl
import org.springframework.security.web.util.matcher.*

@Component
class UnauthorizedEntryPoint(
    httpProperties: WebHttpProperties,
    private val objectMapper: ObjectMapper,
) : AuthenticationEntryPoint, AccessDeniedHandler {

    private val errorPage: String = "${httpProperties.rootPath}/error"
    private val loginPage: String = "${httpProperties.rootPath}/login"

    private val loginCallMatcher = AntPathRequestMatcher.antMatcher(HttpMethod.POST, loginPage)
    private val errorCallMatcher = AntPathRequestMatcher.antMatcher(errorPage)

    private val apiCallMatcher: RequestMatcher = OrRequestMatcher(
        //don't redirect to /login when not authenticated using rest /api/** or rendered ajax calls
        AntPathRequestMatcher("${httpProperties.rootPath}/api/**"),
        ELRequestMatcher("hasHeader('ajax', 'true')"),
    )

    private val defaultDeniedHandler = AccessDeniedHandlerImpl().apply {
        setErrorPage(errorPage)
    }

    fun apiCallMatcher(): RequestMatcher = apiCallMatcher

    override fun commence(
        request: HttpServletRequest,
        response: HttpServletResponse,
        authException: AuthenticationException
    ) = sendErrorResponse(HttpStatus.UNAUTHORIZED, response, authException)

    override fun handle(
        request: HttpServletRequest,
        response: HttpServletResponse,
        accessDeniedException: AccessDeniedException
    ) {
        if (apiCallMatcher.matches(request) || loginCallMatcher.matches(request) || errorCallMatcher.matches(request)) {
            sendErrorResponse(HttpStatus.FORBIDDEN, response, accessDeniedException)
        } else {
            defaultDeniedHandler.handle(request, response, accessDeniedException)
        }
    }

    private fun sendErrorResponse(
        status: HttpStatus,
        response: HttpServletResponse,
        cause: Exception
    ) {
        response.status = status.value()
        val causeMsg = when (cause) {
            is InsufficientAuthenticationException -> "You need to logged-in to do this action, please refresh page"
            is InvalidCsrfTokenException -> "Invalid csrf token, either there was CSRF attack or you have page loaded with old session, please refresh page"
            else -> cause.deepToString()
        }
        val apiError = ApiError(
            status = status,
            message = "${status.name}\nCause: $causeMsg"
        )
        response.contentType = "application/json"
        objectMapper.writeValue(response.outputStream, apiError)
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy