Return JSON error bodies for auth and exceptions
All checks were successful
build-and-deploy / build-deploy (push) Successful in 27s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 27s
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
package com.android.trisolarisserver.config
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.security.access.AccessDeniedException
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
class ApiExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(ResponseStatusException::class)
|
||||||
|
fun handleResponseStatus(
|
||||||
|
ex: ResponseStatusException,
|
||||||
|
request: HttpServletRequest
|
||||||
|
): ResponseEntity<ApiError> {
|
||||||
|
val status = ex.statusCode as HttpStatus
|
||||||
|
return ResponseEntity.status(status).body(
|
||||||
|
ApiError(
|
||||||
|
timestamp = OffsetDateTime.now().toString(),
|
||||||
|
status = status.value(),
|
||||||
|
error = status.reasonPhrase,
|
||||||
|
message = ex.reason ?: "Request failed",
|
||||||
|
path = request.requestURI
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(AccessDeniedException::class)
|
||||||
|
fun handleAccessDenied(
|
||||||
|
ex: AccessDeniedException,
|
||||||
|
request: HttpServletRequest
|
||||||
|
): ResponseEntity<ApiError> {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(
|
||||||
|
ApiError(
|
||||||
|
timestamp = OffsetDateTime.now().toString(),
|
||||||
|
status = HttpStatus.FORBIDDEN.value(),
|
||||||
|
error = HttpStatus.FORBIDDEN.reasonPhrase,
|
||||||
|
message = ex.message ?: "Forbidden",
|
||||||
|
path = request.requestURI
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception::class)
|
||||||
|
fun handleGeneric(
|
||||||
|
ex: Exception,
|
||||||
|
request: HttpServletRequest
|
||||||
|
): ResponseEntity<ApiError> {
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
|
||||||
|
ApiError(
|
||||||
|
timestamp = OffsetDateTime.now().toString(),
|
||||||
|
status = HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||||
|
error = HttpStatus.INTERNAL_SERVER_ERROR.reasonPhrase,
|
||||||
|
message = ex.message ?: "Internal server error",
|
||||||
|
path = request.requestURI
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ApiError(
|
||||||
|
val timestamp: String,
|
||||||
|
val status: Int,
|
||||||
|
val error: String,
|
||||||
|
val message: String,
|
||||||
|
val path: String
|
||||||
|
)
|
||||||
@@ -9,11 +9,15 @@ import org.springframework.security.web.SecurityFilterChain
|
|||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
class SecurityConfig(
|
class SecurityConfig(
|
||||||
private val firebaseAuthFilter: FirebaseAuthFilter
|
private val firebaseAuthFilter: FirebaseAuthFilter,
|
||||||
|
private val objectMapper: ObjectMapper
|
||||||
) {
|
) {
|
||||||
@Bean
|
@Bean
|
||||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
@@ -25,9 +29,11 @@ class SecurityConfig(
|
|||||||
it.anyRequest().authenticated()
|
it.anyRequest().authenticated()
|
||||||
}
|
}
|
||||||
.exceptionHandling {
|
.exceptionHandling {
|
||||||
it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
|
it.authenticationEntryPoint { request, response, _ ->
|
||||||
it.accessDeniedHandler { _, response, _ ->
|
writeError(response, request, HttpStatus.UNAUTHORIZED, "Unauthorized")
|
||||||
response.sendError(HttpStatus.FORBIDDEN.value(), "Forbidden")
|
}
|
||||||
|
it.accessDeniedHandler { request, response, _ ->
|
||||||
|
writeError(response, request, HttpStatus.FORBIDDEN, "Forbidden")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.httpBasic { it.disable() }
|
.httpBasic { it.disable() }
|
||||||
@@ -35,4 +41,22 @@ class SecurityConfig(
|
|||||||
.addFilterBefore(firebaseAuthFilter, UsernamePasswordAuthenticationFilter::class.java)
|
.addFilterBefore(firebaseAuthFilter, UsernamePasswordAuthenticationFilter::class.java)
|
||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun writeError(
|
||||||
|
response: HttpServletResponse,
|
||||||
|
request: HttpServletRequest,
|
||||||
|
status: HttpStatus,
|
||||||
|
message: String
|
||||||
|
) {
|
||||||
|
if (response.isCommitted) return
|
||||||
|
response.status = status.value()
|
||||||
|
response.contentType = "application/json"
|
||||||
|
val body = mapOf(
|
||||||
|
"status" to status.value(),
|
||||||
|
"error" to status.reasonPhrase,
|
||||||
|
"message" to message,
|
||||||
|
"path" to request.requestURI
|
||||||
|
)
|
||||||
|
response.writer.use { it.write(objectMapper.writeValueAsString(body)) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user