From 188738e28b4d11c07d0e6c299f144032124185c0 Mon Sep 17 00:00:00 2001 From: androidlover5842 Date: Tue, 27 Jan 2026 02:52:08 +0530 Subject: [PATCH] Return JSON error bodies for auth and exceptions --- .../config/ApiExceptionHandler.kt | 71 +++++++++++++++++++ .../security/SecurityConfig.kt | 32 +++++++-- 2 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/com/android/trisolarisserver/config/ApiExceptionHandler.kt diff --git a/src/main/kotlin/com/android/trisolarisserver/config/ApiExceptionHandler.kt b/src/main/kotlin/com/android/trisolarisserver/config/ApiExceptionHandler.kt new file mode 100644 index 0000000..adce821 --- /dev/null +++ b/src/main/kotlin/com/android/trisolarisserver/config/ApiExceptionHandler.kt @@ -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 { + 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 { + 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 { + 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 +) diff --git a/src/main/kotlin/com/android/trisolarisserver/security/SecurityConfig.kt b/src/main/kotlin/com/android/trisolarisserver/security/SecurityConfig.kt index c9e55a7..5e466fb 100644 --- a/src/main/kotlin/com/android/trisolarisserver/security/SecurityConfig.kt +++ b/src/main/kotlin/com/android/trisolarisserver/security/SecurityConfig.kt @@ -9,11 +9,15 @@ import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.HttpStatusEntryPoint import org.springframework.http.HttpStatus +import com.fasterxml.jackson.databind.ObjectMapper +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse @Configuration(proxyBeanMethods = false) @EnableMethodSecurity class SecurityConfig( - private val firebaseAuthFilter: FirebaseAuthFilter + private val firebaseAuthFilter: FirebaseAuthFilter, + private val objectMapper: ObjectMapper ) { @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { @@ -25,9 +29,11 @@ class SecurityConfig( it.anyRequest().authenticated() } .exceptionHandling { - it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) - it.accessDeniedHandler { _, response, _ -> - response.sendError(HttpStatus.FORBIDDEN.value(), "Forbidden") + it.authenticationEntryPoint { request, response, _ -> + writeError(response, request, HttpStatus.UNAUTHORIZED, "Unauthorized") + } + it.accessDeniedHandler { request, response, _ -> + writeError(response, request, HttpStatus.FORBIDDEN, "Forbidden") } } .httpBasic { it.disable() } @@ -35,4 +41,22 @@ class SecurityConfig( .addFilterBefore(firebaseAuthFilter, UsernamePasswordAuthenticationFilter::class.java) 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)) } + } }