Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

예외 핸들러를 작성했습니다. #2

Merged
merged 6 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ repositories {

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.springframework.boot:spring-boot-starter-test")
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/com/ondosee/common/error/ErrorStatus.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ondosee.common.error

object ErrorStatus {
const val BAD_REQUEST = 400
const val UNAUTHORIZED = 401
const val FORBIDDEN = 403
const val NOT_FOUND = 404
const val CONFLICT = 409
const val INTERNAL_SERVER_ERROR = 500
}
9 changes: 9 additions & 0 deletions src/main/kotlin/com/ondosee/common/logger/LoggerDelegator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ondosee.common.logger

import org.slf4j.Logger
import org.slf4j.LoggerFactory

class LoggerDelegator {
private var logger: Logger? = null
operator fun getValue(thisRef: Any?, property: Any?) = logger ?: LoggerFactory.getLogger(thisRef?.javaClass)!!
}
13 changes: 13 additions & 0 deletions src/main/kotlin/com/ondosee/global/config/FilterConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.ondosee.global.config

import com.ondosee.global.filter.ExceptionFilter
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

class FilterConfig : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {
override fun configure(builder: HttpSecurity) {
builder.addFilterBefore(ExceptionFilter(), UsernamePasswordAuthenticationFilter::class.java)
}
}
47 changes: 47 additions & 0 deletions src/main/kotlin/com/ondosee/global/error/ErrorResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.ondosee.global.error

import com.ondosee.common.error.ErrorStatus
import com.ondosee.global.error.exception.OndoseeException
import com.ondosee.global.error.exception.ThirdPartyException
import org.springframework.validation.BindingResult
import org.springframework.web.servlet.NoHandlerFoundException

data class ErrorResponse(
val message: String,
val status: Int
) {
companion object {
fun of(e: OndoseeException) = ErrorResponse(
message = e.message,
status = e.status
)

fun of(e: ThirdPartyException) = ErrorResponse(
message = e.message,
status = e.status
)

fun of(e: BindingResult): ValidationErrorResponse {
val fieldErrorMap = e.fieldErrors.associateBy({ it.field }, { it.defaultMessage })

return ValidationErrorResponse(
fieldError = fieldErrorMap,
status = ErrorStatus.BAD_REQUEST
)
}

fun of(e: NoHandlerFoundException) = NoHandlerErrorResponse(
message = e.message.toString(),
status = ErrorStatus.BAD_REQUEST
)
}
}
data class ValidationErrorResponse(
val fieldError: Map<String, String?>,
val status: Int
)

data class NoHandlerErrorResponse(
val message: String,
val status: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ondosee.global.error.exception

open class OndoseeException(
override val message: String,
val info: String,
val status: Int
) : RuntimeException()
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.ondosee.global.error.exception

open class ThirdPartyException(
override val message: String,
val status: Int
) : RuntimeException()
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.ondosee.global.error.handler

import com.ondosee.global.error.ErrorResponse
import com.ondosee.global.error.NoHandlerErrorResponse
import com.ondosee.global.error.ValidationErrorResponse
import com.ondosee.global.error.exception.OndoseeException
import com.ondosee.global.error.exception.ThirdPartyException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.NoHandlerFoundException

@RestControllerAdvice
class GlobalExceptionHandler {

@ExceptionHandler(OndoseeException::class)
fun ondoseeExceptionHandler(e: OndoseeException): ResponseEntity<ErrorResponse> =
ResponseEntity(ErrorResponse.of(e), HttpStatus.valueOf(e.status))

@ExceptionHandler(ThirdPartyException::class)
fun thirdPartyExceptionHandler(e: ThirdPartyException): ResponseEntity<ErrorResponse> =
ResponseEntity(ErrorResponse.of(e), HttpStatus.valueOf(e.status))

@ExceptionHandler(NoHandlerFoundException::class)
fun noHandlerFoundException(e: NoHandlerFoundException): ResponseEntity<NoHandlerErrorResponse> =
ResponseEntity(ErrorResponse.of(e), HttpStatus.NOT_FOUND)

@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleMethodArgumentNotValidException(e: MethodArgumentNotValidException): ResponseEntity<ValidationErrorResponse> =
ResponseEntity(ErrorResponse.of(e), HttpStatus.BAD_REQUEST)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.ondosee.global.exception

import com.ondosee.common.error.ErrorStatus
import com.ondosee.global.error.exception.OndoseeException

class InternalServerErrorException(
message: String,
info: String
) : OndoseeException(
message = message,
info = info,
status = ErrorStatus.INTERNAL_SERVER_ERROR
)
54 changes: 54 additions & 0 deletions src/main/kotlin/com/ondosee/global/filter/ExceptionFilter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.ondosee.global.filter

import com.fasterxml.jackson.databind.ObjectMapper
import com.ondosee.global.error.exception.OndoseeException
import com.ondosee.global.error.exception.ThirdPartyException
import com.ondosee.global.error.ErrorResponse
import com.ondosee.common.error.ErrorStatus
import com.ondosee.global.exception.InternalServerErrorException
import com.ondosee.common.logger.LoggerDelegator
import org.springframework.http.MediaType
import org.springframework.web.filter.OncePerRequestFilter
import java.nio.charset.StandardCharsets
import javax.servlet.FilterChain
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class ExceptionFilter : OncePerRequestFilter() {

private val log by LoggerDelegator()

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
runCatching {
filterChain.doFilter(request, response)
}.onFailure { e ->
when (e) {
is OndoseeException -> {
log.error("Ondosee Exception :: message = {}, info = {}, status = {}", e.message, e.info, e.status)
sendError(response, ErrorResponse.of(e))
}
is ThirdPartyException -> {
log.error("ThirdParty Exception :: message = {}, status = {}", e.message, e.status)
sendError(response, ErrorResponse.of(e))

}
else -> {
log.error("Internal Exception :: message = {}, Status = {}", e.message, ErrorStatus.INTERNAL_SERVER_ERROR)
sendError(response, ErrorResponse.of(InternalServerErrorException(e.message.toString(), "서버 에러")))
}
}
}
}

private fun sendError(response: HttpServletResponse, errorResponse: ErrorResponse) {
response.status = errorResponse.status
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = StandardCharsets.UTF_8.name()

ObjectMapper().writeValueAsString(errorResponse).run(response.writer::write)
}
}
35 changes: 35 additions & 0 deletions src/main/kotlin/com/ondosee/global/security/SecurityConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.ondosee.global.security

import com.ondosee.global.config.FilterConfig
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.web.cors.CorsUtils

@EnableWebSecurity
class SecurityConfig {

@Bean
protected fun filterChain(http: HttpSecurity): SecurityFilterChain =
http
.cors()
.and()
.csrf().disable()

.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.requestMatchers(RequestMatcher { request ->
CorsUtils.isPreFlightRequest(request)
}).permitAll()
.and()

.apply(FilterConfig())
.and()

.build()
}
Loading