Repository: EntryDSM/Casper-Recruitment
Pull request #53: Feature/52 fix security + oAuth setting
Author: login: qkrwndnjs1075
Labels: name: refactor
Created: 2025-03-28T13:26:16Z | Merged: 2025-03-31T06:11:18Z

## #️⃣연관된 이슈

> ex) #52

## 📝작업 내용

> oAuth 이메일 에러 처리
> oAuth jwt 반환 로직 리펙토링 

## 💬리뷰 요구사항(선택)
> 에러 처리를 failure handler에서 하드 코딩으로 하였습니다. 만약에 이런 방식이 별로라면 수정하겠습니다.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **새로운 기능**
	- 인증 및 서버 오류 발생 시, 일관된 JSON 에러 응답을 제공하여 사용자에게 명확한 피드백을 전달합니다.
	- 통합 오류 처리 컨트롤러와 신규 토큰 발급 서비스가 도입되어 인증 성공 후 일관된 토큰 응답을 제공합니다.
	- 구체적인 오류 코드와 예외 타입이 추가되어 오류 진단과 대응이 용이해졌습니다.
	- Google OAuth 설정에 새로운 리다이렉트 URL이 추가되었습니다.
	- 전역 예외 필터가 도입되어 HTTP 요청 처리 시 일관된 오류 응답을 제공합니다.

- **버그 수정**
	- 이메일 도메인 검증 로직에서 새로운 예외 타입으로 변경하여 오류 처리의 일관성을 높였습니다.

- **리팩토링**
	- 인증 성공 및 실패 처리 로직과 종속성 주입 방식이 간소화되어 전체 흐름이 단순해졌습니다.
	- 기존의 불필요한 토큰 생성 로직이 제거되었습니다.
	- 보고서 생성 메서드의 접근 제어가 변경되어 사용자 범위가 확대되었습니다.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/report/presentation/ReportController.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/report/presentation/ReportController.kt
index a0e6ce2..9b128eb 100644
--- a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/report/presentation/ReportController.kt
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/report/presentation/ReportController.kt
@@ -32,7 +32,6 @@ class ReportController(
 ) {
     // Create a new report
     @PostMapping
-    @OnlyUser
     @ResponseStatus(HttpStatus.CREATED)
     fun createReport(
         @RequestBody command: CreateReportCommand,
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt
index 79ff464..50c7edb 100644
--- a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt
@@ -1,6 +1,5 @@
 package entry.dsm.gitauth.equusgithubauth.global.config
 
-import com.fasterxml.jackson.databind.ObjectMapper
 import entry.dsm.gitauth.equusgithubauth.global.oauth.handler.CustomOAuth2AuthenticationFailureHandler
 import entry.dsm.gitauth.equusgithubauth.global.oauth.handler.CustomOAuth2AuthenticationSuccessHandler
 import entry.dsm.gitauth.equusgithubauth.global.oauth.service.GoogleOauthService
@@ -24,15 +23,14 @@ class SecurityConfig(
     private val corsConfig: CorsConfig,
     private val jwtTokenProvider: JwtTokenProvider,
     private val customOauth2UserService: GoogleOauthService,
-    private val objectMapper: ObjectMapper,
     private val oAuth2LoginConfig: OAuth2LoginConfig,
+    private val customOAuth2AuthenticationFailureHandler: CustomOAuth2AuthenticationFailureHandler,
+    private val customOAuth2AuthenticationSuccessHandler: CustomOAuth2AuthenticationSuccessHandler,
 ) {
     @Bean
     fun webSecurityCustomizer(): WebSecurityCustomizer {
-        // security를 적용하지 않을 리소스
         return WebSecurityCustomizer { web ->
             web.ignoring()
-                // error endpoint를 열어줘야 함, favicon.ico 추가!
                 .requestMatchers("/error", "/favicon.ico")
         }
     }
@@ -47,8 +45,13 @@ class SecurityConfig(
             .authorizeHttpRequests { auth ->
                 auth
                     .requestMatchers(
-                        "/", "/login", "/oauth2/**", "/api/github/auth", "/api/github/auth/**",
-                        "/oauth2/authorize/**", "/error", "/notice/**", "/notice", "/reports",
+                        "/",
+                        "/login",
+                        "/oauth2/**",
+                        "/api/github/auth",
+                        "/api/github/auth/**",
+                        "/oauth2/authorize/**",
+                        "/error",
                     ).permitAll()
                     .requestMatchers(HttpMethod.GET, "reports", "notice").permitAll()
                     .requestMatchers("/api/**").permitAll()
@@ -66,12 +69,11 @@ class SecurityConfig(
                     .userInfoEndpoint { userInfo ->
                         userInfo.userService(customOauth2UserService)
                     }
-                    .successHandler(CustomOAuth2AuthenticationSuccessHandler(objectMapper))
-                    .failureHandler(CustomOAuth2AuthenticationFailureHandler())
+                    .successHandler(customOAuth2AuthenticationSuccessHandler)
+                    .failureHandler(customOAuth2AuthenticationFailureHandler)
                     .permitAll()
             }
 
-        // JwtTokenFilter를 OAuth 설정과 별도로 등록
         http.addFilterBefore(JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter::class.java)
 
         return http.build()
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt
new file mode 100644
index 0000000..97ec6f4
--- /dev/null
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt
@@ -0,0 +1,39 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import jakarta.servlet.http.HttpServletRequest
+import org.springframework.boot.web.servlet.error.ErrorController
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class CustomErrorController : ErrorController {
+
+    companion object {
+        private const val DEFAULT_ERROR_MESSAGE = "알 수 없는 오류가 발생했습니다."
+        private const val ERROR_TITLE = "서버 오류"
+        private const val ERROR_ATTRIBUTE_STATUS = "jakarta.servlet.error.status_code"
+        private const val ERROR_ATTRIBUTE_MESSAGE = "jakarta.servlet.error.message"
+    }
+
+    @RequestMapping("/error")
+    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
+        val status =
+            request.getAttribute(ERROR_ATTRIBUTE_STATUS) as? Int
+                ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
+        val message =
+            request.getAttribute(ERROR_ATTRIBUTE_MESSAGE)?.toString()
+                ?: DEFAULT_ERROR_MESSAGE
+
+        val errorResponse =
+            mapOf(
+                "error" to ERROR_TITLE,
+                "message" to message,
+                "status" to status,
+            )
+
+        return ResponseEntity(errorResponse, HttpStatus.valueOf(status))
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomOauth2AuthenticationException.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomOauth2AuthenticationException.kt
new file mode 100644
index 0000000..cf517d8
--- /dev/null
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomOauth2AuthenticationException.kt
@@ -0,0 +1,8 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import org.springframework.security.core.AuthenticationException
+
+class CustomOauth2AuthenticationException(
+    message: String?,
+    cause: Throwable?,
+) : AuthenticationException(message, cause)
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/ErrorCode.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/ErrorCode.kt
index af06a85..ebb7b0e 100644
--- a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/ErrorCode.kt
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/ErrorCode.kt
@@ -30,4 +30,8 @@ enum class ErrorCode(
     EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION(401, "허용되지 않은 이메일 도메인입니다."),
 
     NOT_FOUND_OAUTH_PROVIDER(404, "Oauth Provider을 찾을 수 없습니다."),
+
+    INTERNAL_SERVER_ERROR(500, "서버에 에러가 발생하였습니다."),
+
+    AUTHENTICATION_PRINCIPAL_MISMATCH(403, "인증 주체 불일치"),
 }
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/GlobalExceptionFilter.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/GlobalExceptionFilter.kt
new file mode 100644
index 0000000..c7a215a
--- /dev/null
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/GlobalExceptionFilter.kt
@@ -0,0 +1,44 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import jakarta.servlet.FilterChain
+import jakarta.servlet.http.HttpServletRequest
+import jakarta.servlet.http.HttpServletResponse
+import org.springframework.web.filter.OncePerRequestFilter
+import java.io.IOException
+import java.time.LocalDateTime
+
+class GlobalExceptionFilter(
+    private val objectMapper: ObjectMapper
+) : OncePerRequestFilter() {
+    override fun doFilterInternal(
+        request: HttpServletRequest,
+        response: HttpServletResponse,
+        filterChain: FilterChain
+    ) {
+        try {
+            filterChain.doFilter(request, response)
+        } catch (e: CustomException) {
+            val errorResponse = ErrorResponse(
+                statusCode = e.statusCode,
+                message = e.message,
+                timestamp = LocalDateTime.now(),
+                path = request.requestURI,
+                method = request.method
+            )
+            writeErrorResponse(response, e.statusCode, errorResponse)
+        }
+    }
+
+    @Throws(IOException::class)
+    private fun writeErrorResponse(
+        response: HttpServletResponse,
+        status: Int,
+        errorResponse: ErrorResponse
+    ) {
+        response.status = status
+        response.contentType = "application/json"
+        response.characterEncoding = "UTF-8"
+        objectMapper.writeValue(response.writer, errorResponse)
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/AuthenticationPrincipalMismatch.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/AuthenticationPrincipalMismatch.kt
new file mode 100644
index 0000000..c949ee5
--- /dev/null
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/AuthenticationPrincipalMismatch.kt
@@ -0,0 +1,6 @@
+package entry.dsm.gitauth.equusgithubauth.global.oauth.exception
+
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+
+class AuthenticationPrincipalMismatch : CustomException(ErrorCode.AUTHENTICATION_PRINCIPAL_MISMATCH)
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
index 36486de..3f82523 100644
--- a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
@@ -1,20 +1,61 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
+import com.fasterxml.jackson.databind.ObjectMapper
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorResponse
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
+import org.springframework.http.MediaType
 import org.springframework.security.core.AuthenticationException
 import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.stereotype.Component
+import java.nio.charset.StandardCharsets
+import java.time.LocalDateTime
+
+@Component
+class CustomOAuth2AuthenticationFailureHandler(
+    private val objectMapper: ObjectMapper,
+) : AuthenticationFailureHandler {
+
+    companion object {
+        private const val DEFAULT_OAUTH_ERROR_MESSAGE = "OAuth2 인증 실패"
+        private const val UNKNOWN_ERROR_MESSAGE = "Unknown error"
+    }
 
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
     override fun onAuthenticationFailure(
         request: HttpServletRequest,
         response: HttpServletResponse,
         exception: AuthenticationException,
     ) {
-        response.contentType = "application/json"
-        response.status = HttpServletResponse.SC_UNAUTHORIZED
-        response.writer.write(
-            """{error: OAuth2 로그인 실패, message: ${exception.message}}""",
-        )
+        val (statusCode, errorMessage) =
+            when (exception) {
+                is CustomOauth2AuthenticationException -> {
+                    val cause = exception.cause
+                    if (cause is CustomException) {
+                        cause.statusCode to cause.message
+                    } else {
+                        ErrorCode.INTERNAL_SERVER_ERROR.status to DEFAULT_OAUTH_ERROR_MESSAGE
+                    }
+                }
+                is CustomException -> exception.statusCode to exception.message
+                else -> ErrorCode.INTERNAL_SERVER_ERROR.status to DEFAULT_OAUTH_ERROR_MESSAGE
+            }
+
+        val errorResponse =
+            ErrorResponse(
+                statusCode = statusCode,
+                message = errorMessage ?: UNKNOWN_ERROR_MESSAGE,
+                timestamp = LocalDateTime.now(),
+                path = request.requestURI,
+                method = request.method,
+            )
+
+        response.status = statusCode
+        response.contentType = MediaType.APPLICATION_JSON_VALUE
+        response.characterEncoding = StandardCharsets.UTF_8.name()
+        response.writer.write(objectMapper.writeValueAsString(errorResponse))
+        response.writer.flush()
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt
index fbb1e8e..098c5f9 100644
--- a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt
@@ -1,34 +1,54 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
-import com.fasterxml.jackson.databind.ObjectMapper
-import entry.dsm.gitauth.equusgithubauth.global.oauth.JwtConstants
+import entry.dsm.gitauth.equusgithubauth.domain.user.presentation.dto.response.TokenResponse
+import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.AuthenticationPrincipalMismatch
+import entry.dsm.gitauth.equusgithubauth.global.oauth.service.component.OauthTokenService
 import entry.dsm.gitauth.equusgithubauth.global.security.auth.CustomOauth2UserDetails
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
+import org.springframework.beans.factory.annotation.Value
 import org.springframework.security.core.Authentication
 import org.springframework.security.web.authentication.AuthenticationSuccessHandler
+import org.springframework.stereotype.Component
+import java.net.URLEncoder
+import java.nio.charset.StandardCharsets
 
+@Component
 class CustomOAuth2AuthenticationSuccessHandler(
-    private val objectMapper: ObjectMapper,
+    private val oauthTokenService: OauthTokenService,
+    @Value("\${oauth.google.redirect2}") private val frontDirectUrl: String,
 ) : AuthenticationSuccessHandler {
     override fun onAuthenticationSuccess(
         request: HttpServletRequest,
         response: HttpServletResponse,
         authentication: Authentication,
     ) {
-        // CustomOauth2UserDetails에 저장된 attributes에서 토큰 정보 추출
-        val oauthUser = authentication.principal as? CustomOauth2UserDetails
-        val tokenInfo =
-            oauthUser?.attributes?.let { attributes ->
-                mapOf(
-                    JwtConstants.ACCESS_TOKEN to attributes[JwtConstants.ACCESS_TOKEN],
-                    JwtConstants.ACCESS_TOKEN_EXPIRATION to attributes[JwtConstants.ACCESS_TOKEN_EXPIRATION],
-                    JwtConstants.REFRESH_TOKEN to attributes[JwtConstants.REFRESH_TOKEN],
-                    JwtConstants.REFRESH_TOKEN_EXPIRATION to attributes[JwtConstants.REFRESH_TOKEN_EXPIRATION],
-                )
-            }
-        response.contentType = "application/json"
-        response.characterEncoding = "UTF-8"
-        response.writer.write(objectMapper.writeValueAsString(tokenInfo))
+        val oauthUser =
+            authentication.principal as? CustomOauth2UserDetails
+                ?: throw AuthenticationPrincipalMismatch()
+        val loginId = oauthUser.username
+
+        val tokenResponse: TokenResponse = oauthTokenService.generateTokenResponse(loginId)
+
+        val redirectUrl = buildRedirectUrl(tokenResponse)
+
+        response.sendRedirect(redirectUrl)
+    }
+
+    private fun buildRedirectUrl(tokenResponse: TokenResponse): String {
+        val baseUrl = frontDirectUrl
+        val queryParams =
+            mapOf(
+                "accessToken" to tokenResponse.accessToken,
+                "refreshToken" to tokenResponse.refreshToken,
+                "accessTokenExpiration" to tokenResponse.accessTokenExpiration.toString(),
+                "refreshTokenExpiration" to tokenResponse.refreshTokenExpiration.toString(),
+            )
+        val encodedParams =
+            queryParams.map { (key, value) ->
+                "${URLEncoder.encode(key, StandardCharsets.UTF_8.toString())}=" +
+                    URLEncoder.encode(value, StandardCharsets.UTF_8.toString())
+            }.joinToString("&")
+        return "$baseUrl?$encodedParams"
     }
 }
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt
index 58163fd..1522f6e 100644
--- a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt
@@ -2,8 +2,6 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.service
 
 import entry.dsm.gitauth.equusgithubauth.domain.auth.enums.OauthType
 import entry.dsm.gitauth.equusgithubauth.domain.auth.exception.UnsupportedProviderException
-import entry.dsm.gitauth.equusgithubauth.domain.user.presentation.dto.response.TokenResponse
-import entry.dsm.gitauth.equusgithubauth.global.oauth.JwtConstants
 import entry.dsm.gitauth.equusgithubauth.global.oauth.OAuth2UserInfo
 import entry.dsm.gitauth.equusgithubauth.global.oauth.service.component.EmailDomainValidator
 import entry.dsm.gitauth.equusgithubauth.global.oauth.service.component.GoogleOauthUserService
@@ -47,17 +45,6 @@ class GoogleOauthService(
                 providerId = oAuth2UserInfo.getProviderId(),
             )
 
-        val tokenResponse: TokenResponse = jwtTokenProvider.generateToken(loginId)
-
-        // OAuth2User의 attributes에 토큰 정보를 추가하여 클라이언트 전달에 사용
-        val updatedAttributes =
-            oAuth2User.attributes.toMutableMap().apply {
-                put(JwtConstants.ACCESS_TOKEN, tokenResponse.accessToken)
-                put(JwtConstants.ACCESS_TOKEN_EXPIRATION, tokenResponse.accessTokenExpiration.toString())
-                put(JwtConstants.REFRESH_TOKEN, tokenResponse.refreshToken)
-                put(JwtConstants.REFRESH_TOKEN_EXPIRATION, tokenResponse.refreshTokenExpiration.toString())
-            }
-
-        return CustomOauth2UserDetails(user, updatedAttributes)
+        return CustomOauth2UserDetails(user, oAuth2User.attributes)
     }
 }
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
index 647b6e1..d79d221 100644
--- a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
@@ -1,5 +1,6 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
 
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
 import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.EmailDomainNotAllowedException
 import org.springframework.beans.factory.annotation.Value
 import org.springframework.stereotype.Component
@@ -10,7 +11,8 @@ class EmailDomainValidator(
 ) {
     fun isAllowed(email: String): Boolean {
         if (!email.endsWith("@$allowedDomain")) {
-            throw EmailDomainNotAllowedException()
+            // EmailDomainNotAllowedException 대신 CustomOauth2AuthenticationException을 던짐
+            throw CustomOauth2AuthenticationException("허용되지 않은 이메일 도메인입니다.", EmailDomainNotAllowedException())
         }
         return true
     }
diff --git a/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/OauthTokenService.kt b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/OauthTokenService.kt
new file mode 100644
index 0000000..65ab493
--- /dev/null
+++ b/src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/OauthTokenService.kt
@@ -0,0 +1,14 @@
+package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
+
+import entry.dsm.gitauth.equusgithubauth.domain.user.presentation.dto.response.TokenResponse
+import entry.dsm.gitauth.equusgithubauth.global.security.jwt.JwtTokenProvider
+import org.springframework.stereotype.Component
+
+@Component
+class OauthTokenService(
+    private val jwtTokenProvider: JwtTokenProvider,
+) {
+    fun generateTokenResponse(loginId: String): TokenResponse {
+        return jwtTokenProvider.generateToken(loginId)
+    }
+}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index cd292db..90abde9 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -60,6 +60,7 @@ jwt:
 oauth:
   google:
     redirect: ${GOOGLE_DIRECT_URL}
+    redirect2: ${FRONT_DIRECT_URL}
 
 email:
   allowed: ${EMAIL_DOMAIN}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt (1)</summary><blockquote>

`21-26`: **응답 형식 개선 및 사용자 친화적인 오류 메시지**

응답 콘텐츠 타입에 charset을 추가하고 구조화된 JSON 형식으로 오류 응답을 반환하는 것은 좋은 방법입니다. 그러나 하드코딩된 오류 메시지는 국제화에 제한이 있을 수 있습니다.

향후에는 다음과 같이 메시지 소스를 사용하여 국제화를 지원하는 것을 고려해보세요:

```diff
- "error" to "구글 로그인 실패",
- "message" to (exception.message ?: "올바른 이메일 도메인이 아닙니다..")
+ "error" to messageSource.getMessage("oauth.login.failure", null, locale),
+ "message" to (exception.message ?: messageSource.getMessage("email.domain.not.allowed", null, locale))
```

</blockquote></details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt (1)</summary><blockquote>

`20-27`: **에러 응답 구조 및 국제화 고려**

에러 응답 구조가 잘 정의되어 있습니다. 그러나 하드코딩된 한국어 메시지는 국제화에 제한이 있을 수 있습니다. 또한 서버 오류 메시지에는 민감한 정보가 노출되지 않도록 주의해야 합니다.

향후에는 다음과 같은 개선을 고려해보세요:

1. 메시지 소스를 사용하여 국제화 지원
2. 개발 환경과 프로덕션 환경에서 다른 수준의 오류 세부 정보 제공 (프로덕션에서는 최소한의 정보만 노출)

```diff
- val message = request.getAttribute("javax.servlet.error.message")?.toString()
-     ?: "알 수 없는 오류가 발생했습니다."
+ val environment = Environment.getActiveProfiles()
+ val message = if (Arrays.asList(environment).contains("prod")) {
+     messageSource.getMessage("error.generic", null, locale)
+ } else {
+     request.getAttribute("javax.servlet.error.message")?.toString()
+         ?: messageSource.getMessage("error.unknown", null, locale)
+ }
```

</blockquote></details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt (2)</summary><blockquote>

`25-27`: **타입 체크 및 예외 처리 개선**

인증 주체(principal)에 대한 타입 체크를 추가하고 명확한 예외를 던지는 것은 좋은 방법입니다. 그러나 다른 오류 메시지와 일관되게 한국어로 작성하는 것이 좋을 것 같습니다.

예외 메시지를 한국어로 변경하여 일관성을 유지하세요:

```diff
- ?: throw IllegalStateException("Authentication principal must be CustomOauth2UserDetails")
+ ?: throw IllegalStateException("인증 주체는 CustomOauth2UserDetails 타입이어야 합니다")
```

---

`37-41`: **응답 작성 방식 개선**

MediaType 상수를 사용하여 콘텐츠 타입을 설정하는 것은 좋은 방법입니다. 그러나 writer.flush()를 명시적으로 호출하는 것은 불필요할 수 있습니다.

response.writer.close()를 호출하여 자원을 정리하고 자동으로 flush되도록 하는 것이 더 좋은 방법입니다:

```diff
response.writer.write(objectMapper.writeValueAsString(tokenResponse))
- response.writer.flush()
+ response.writer.close()
```

또는 try-with-resources 구문을 사용하여 자원을 안전하게 관리할 수 있습니다.

</blockquote></details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt (1)</summary><blockquote>

`60-67`: **예외 처리 개선**

인증 예외 발생 시 JSON 형식의 표준화된 오류 응답을 제공하도록 개선되었습니다. 이는 클라이언트 측에서 오류를 일관되게 처리할 수 있게 해주는 좋은 변경입니다.

다만, 모든 인증 예외를 BAD_REQUEST(400)로 처리하는 것은 일부 상황에서 부적절할 수 있습니다. 인증 관련 오류는 일반적으로 UNAUTHORIZED(401)나 FORBIDDEN(403)으로 처리합니다.

```diff
-                    response.status = HttpServletResponse.SC_BAD_REQUEST
+                    response.status = HttpServletResponse.SC_UNAUTHORIZED
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 80b5bd0a0609422a12642a1163e3f5affd5dc087 and ef513e1d2051a99921c7fdc73d8467624430526c.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/user/presentation/dto/response/TokenResponse.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt` (4 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/OauthTokenService.kt` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/user/presentation/dto/response/TokenResponse.kt (1)</summary>

`9-9`: **불필요한 후행 쉼표 제거, 코드 스타일 개선**

불필요한 후행 쉼표(trailing comma)를 제거하여 코드 스타일이 개선되었습니다. 클래스의 기능에는 영향을 주지 않는 변경사항입니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt (1)</summary>

`50-50`: **토큰 생성 로직 리팩터링**

기존에 GoogleOauthService 내에 있던 토큰 생성 로직이 제거되고, 단순히 사용자 정보와 원본 OAuth2 속성을 반환하도록 변경되었습니다. 이는 토큰 생성 책임을 새로운 OauthTokenService 컴포넌트로 이동시킨 것으로 보이며, 단일 책임 원칙(SRP)을 더 잘 준수하게 되었습니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/OauthTokenService.kt (2)</summary>

`8-16`: **새로운 OauthTokenService 컴포넌트 추가**

토큰 생성 로직을 담당하는 새로운 컴포넌트가 추가되었습니다. 이 컴포넌트는 JwtTokenProvider를 사용하여 loginId를 기반으로 TokenResponse를 생성합니다. 이런 분리는 관심사의 분리(Separation of Concerns) 원칙을 따르며, 코드의 유지보수성과 테스트 용이성을 향상시킵니다.

---

`13-15`: **generateTokenResponse 메서드 구현**

loginId를 받아서 TokenResponse를 반환하는 간단한 메서드입니다. 현재는 JwtTokenProvider의 generateToken 메서드를 그대로 호출하여 반환하고 있습니다. 이후 요구사항 변경으로 토큰 생성 로직이 확장되어야 할 경우 이 메서드 내에서 처리할 수 있어 확장성이 좋습니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt (2)</summary>

`3-3`: **Component 애노테이션과 ObjectMapper 추가에 대한 개선**

Component 애노테이션과 ObjectMapper를 추가한 것은 좋은 변경입니다. 이를 통해 핸들러를 스프링 컨테이너가 관리할 수 있고, JSON 직렬화를 위한 표준 도구를 사용하게 되었습니다.



Also applies to: 8-10, 13-14

---

`28-28`: **ObjectMapper를 사용한 응답 작성**

ObjectMapper를 사용하여 JSON 응답을 작성하는 것은 적절한 방법입니다. 오류 응답 형식이 일관되게 유지됩니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt (2)</summary>

`4-6`: **OauthTokenService 의존성 주입 개선**

토큰 생성 로직을 OauthTokenService로 추출한 것은 좋은 설계 결정입니다. 이를 통해 코드 재사용성이 향상되고 단일 책임 원칙을 더 잘 준수할 수 있게 되었습니다.



Also applies to: 17-17

---

`30-36`: **토큰 생성 로직 리팩토링**

토큰 생성 로직을 OauthTokenService로 위임하는 것은 책임 분리의 좋은 예입니다. 코드가 더 간결해지고 각 컴포넌트의 역할이 명확해졌습니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt (2)</summary>

`29-30`: **의존성 주입 패턴 적용을 통한 코드 개선**

핸들러를 내부에서 생성하는 대신 의존성 주입을 활용하여 코드의 테스트 용이성과 유지보수성이 향상되었습니다. 이는 스프링의 권장 패턴을 따르는 좋은 변경입니다.

---

`78-79`: **핸들러 주입 방식으로 변경**

내부에서 직접 핸들러를 생성하는 대신 주입된 핸들러를 사용하도록 변경되었습니다. 이는 코드의 결합도를 낮추고 테스트 용이성을 높이는 좋은 변경입니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-28T13:29:11Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T13:30:50Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:31:12Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt (2)</summary><blockquote>

`3-14`: **컴포넌트 주입과 ObjectMapper 초기화에 대한 개선 가능성**

`ObjectMapper`를 직접 생성하는 대신 Spring의 `@Autowired`를 통해 주입받는 것이 더 효율적일 수 있습니다. 이렇게 하면 다른 곳에서 사용하는 ObjectMapper 설정과의 일관성을 유지할 수 있습니다.

```diff
@Component
class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {

-   private val objectMapper = ObjectMapper()
+   @Autowired
+   private lateinit var objectMapper: ObjectMapper

    override fun onAuthenticationFailure(
```
또는 생성자 주입을 사용할 수도 있습니다:
```diff
@Component
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
+class CustomOAuth2AuthenticationFailureHandler(
+   private val objectMapper: ObjectMapper
+) : AuthenticationFailureHandler {

-   private val objectMapper = ObjectMapper()

    override fun onAuthenticationFailure(
```

---

`20-29`: **에러 응답 구조 개선된 점**

에러 응답을 구조화하고 한글 메시지를 포함한 것은 사용자 경험 측면에서 좋은 개선입니다. 다만 아래 내용을 고려해보세요:

1. 하드코딩된 에러 메시지를 상수로 분리하거나 메시지 소스를 사용하는 것이 유지보수에 더 좋을 수 있습니다.
2. 디버깅을 위해 예외 타입이나 추가 정보를 포함하는 것을 고려해보세요.

```diff
    override fun onAuthenticationFailure(
        request: HttpServletRequest,
        response: HttpServletResponse,
        exception: AuthenticationException
    ) {
        response.status = HttpServletResponse.SC_UNAUTHORIZED
        response.contentType = "application/json; charset=UTF-8"

        val errorResponse = mapOf(
            "error" to "구글 로그인 실패",
            "message" to (exception.message ?: "올바른 이메일 도메인이 아닙니다.."),
+           "type" to exception.javaClass.simpleName
        )

        response.writer.write(objectMapper.writeValueAsString(errorResponse))
    }
```

</blockquote></details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt (1)</summary><blockquote>

`20-41`: **인증 성공 로직 리팩토링**

인증 성공 시 토큰 생성 로직을 `OauthTokenService`로 위임한 것은 코드 품질 측면에서 좋은 개선입니다. 몇 가지 제안과 관찰 사항이 있습니다:

1. 주석은 유용하지만, 코드가 충분히 자명하다면 불필요할 수 있습니다.
2. 불필요한 빈 줄이 있습니다.
3. `flush()` 호출은 좋지만, `close()`도 고려해볼 수 있습니다.


```diff
    override fun onAuthenticationSuccess(
        request: HttpServletRequest,
        response: HttpServletResponse,
        authentication: Authentication
    ) {
-       // CustomOauth2UserDetails에서 loginId 추출
        val oauthUser = authentication.principal as? CustomOauth2UserDetails
            ?: throw IllegalStateException("Authentication principal must be CustomOauth2UserDetails")

-

        val loginId = oauthUser.username

-

        val tokenResponse: TokenResponse = oauthTokenService.generateTokenResponse(loginId)

-

        response.status = HttpServletResponse.SC_OK
        response.contentType = MediaType.APPLICATION_JSON_VALUE
        response.characterEncoding = "UTF-8"
        response.writer.write(objectMapper.writeValueAsString(tokenResponse))
        response.writer.flush()
+       response.writer.close()
    }
```

</blockquote></details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt (1)</summary><blockquote>

`1-28`: **전역 에러 처리 컨트롤러 구현**

전역 에러 처리를 위한 `CustomErrorController` 구현은 좋은 접근 방식입니다. 다음 개선 사항을 고려해 보세요:

1. Spring Boot 2.3 이후로는 `ErrorController` 대신 `@ControllerAdvice`와 `@ExceptionHandler`를 사용하는 것이 권장됩니다.
2. 에러 메시지와 상태 코드에 더 유연하게 대응할 수 있도록 개선이 가능합니다.
3. 로깅을 추가하여 서버 측에서 에러 추적이 용이하도록 할 수 있습니다.


```diff
package entry.dsm.gitauth.equusgithubauth.global.exception

+import org.slf4j.LoggerFactory
import org.springframework.boot.web.servlet.error.ErrorController
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import jakarta.servlet.http.HttpServletRequest

@RestController
class CustomErrorController : ErrorController {

+   private val logger = LoggerFactory.getLogger(CustomErrorController::class.java)
+
    @RequestMapping("/error")
    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
        val status = request.getAttribute("javax.servlet.error.status_code") as? Int
            ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
        val message = request.getAttribute("javax.servlet.error.message")?.toString()
            ?: "알 수 없는 오류가 발생했습니다."
+       val exception = request.getAttribute("javax.servlet.error.exception") as? Throwable
+        
+       logger.error("Error handling request: $message", exception)

        val errorResponse = mapOf(
            "error" to "서버 오류",
            "message" to message,
            "status" to status
        )

        return ResponseEntity(errorResponse, HttpStatus.valueOf(status))
    }
}
```

또는 최신 Spring Boot 스타일로 변경하는 것을 고려해보세요:

```kotlin
@RestControllerAdvice
class GlobalExceptionHandler {

    private val logger = LoggerFactory.getLogger(GlobalExceptionHandler::class.java)

    @ExceptionHandler(Exception::class)
    fun handleException(ex: Exception): ResponseEntity<Map<String, Any>> {
        logger.error("Unhandled exception occurred", ex)
        
        val errorResponse = mapOf(
            "error" to "서버 오류",
            "message" to (ex.message ?: "알 수 없는 오류가 발생했습니다."),
            "status" to HttpStatus.INTERNAL_SERVER_ERROR.value()
        )
        
        return ResponseEntity(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR)
    }
    
    // 다른 특정 예외에 대한 핸들러 추가
}
```

</blockquote></details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt (1)</summary><blockquote>

`60-67`: **인증 예외 처리 로직 추가**

인증 실패 시 구조화된 JSON 응답을 제공하는 예외 처리 로직이 추가되었습니다. 이는 클라이언트에게 더 명확한 에러 메시지를 제공하여 디버깅 및 사용자 경험을 개선합니다.

하지만 하드코딩된. 에러 메시지를 사용하는 대신 별도의 메시지 소스나 상수를 사용하는 것이 향후 유지보수에 더 좋을 수 있습니다.

```diff
-                    val errorMessage = authException?.message ?: "허용되지 않은 요청입니다."
-                    response.writer.write("""{ "error": "요청 실패", "message": "$errorMessage" }""")
+                    val errorMessage = authException?.message ?: ErrorMessages.UNAUTHORIZED_REQUEST
+                    response.writer.write("""{ "error": ErrorMessages.REQUEST_FAILED, "message": "$errorMessage" }""")
```

별도의 ErrorMessages 클래스를 만들어 에러 메시지를 관리하는 것을 고려해보세요.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 80b5bd0a0609422a12642a1163e3f5affd5dc087 and ef513e1d2051a99921c7fdc73d8467624430526c.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/user/presentation/dto/response/TokenResponse.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt` (4 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/OauthTokenService.kt` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/OauthTokenService.kt (1)</summary>

`1-16`: **토큰 생성 로직의 분리는 좋은 리팩토링입니다.**

토큰 생성 로직을 별도의 서비스로 분리해서 재사용성을 높인 것은 좋은 접근 방식입니다. 단일 책임 원칙(SRP)을 따르고 있으며, 다른 컴포넌트에서 쉽게 주입하여 사용할 수 있게 되었습니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationSuccessHandler.kt (1)</summary>

`4-18`: **컴포넌트 구조와 의존성 주입 개선**

새로 도입된 `OauthTokenService`를 생성자 주입을 통해 사용하는 것은 좋은 접근 방식입니다. 이는 테스트 용이성과 결합도 감소에 도움이 됩니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt (4)</summary>

`3-3`: **import 변경 적절함**

HttpServletResponse import가 추가되었고, 불필요한 import가 제거되었습니다. 이는 새로운 예외 처리 구현에 필요한 변경으로 적절합니다.



Also applies to: 9-9

---

`29-30`: **의존성 주입 패턴 적용 개선**

생성자에 custom handler들을 직접 주입받도록 변경한 것은 좋은 접근 방식입니다. 이전에는 내부에서 직접 인스턴스를 생성했을 것으로 보이는데, 이런 의존성 주입 패턴을 사용함으로써 테스트 용이성과 결합도를 개선했습니다.

---

`52-52`: **error 경로 permitAll에 추가**

"/error" 경로를 permitAll에 추가한 것은 적절합니다. 이는 새롭게 구현한 에러 처리 메커니즘이 인증되지 않은 사용자에게도 적절한 에러 응답을 반환할 수 있도록 합니다.

---

`78-79`: **OAuth2 핸들러 의존성 주입 활용**

OAuth2 로그인 설정에서 주입된 핸들러를 사용하도록 변경한 것은 좋은 개선입니다. 이전에는 직접 인스턴스를 생성했던 것으로 보이는데, 이런 방식은 테스트가 어렵고 결합도가 높아집니다. 스프링의 DI 컨테이너를 활용하는 현재 방식이 더 바람직합니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/user/presentation/dto/response/TokenResponse.kt (1)</summary>

`9-9`: **불필요한 후행 쉼표 제거 - 좋은 정리입니다!**

후행 쉼표(trailing comma)를 제거하여 코드가 더 깔끔해졌습니다. Kotlin에서는 후행 쉼표가 선택적이지만, 일관된 코드 스타일을 유지하는 것이 좋습니다.

</details>
<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/GoogleOauthService.kt (1)</summary>

`50-50`: **토큰 생성 로직 분리 - 좋은 리팩토링입니다!**

토큰 생성 로직을 `GoogleOauthService`에서 제거하고 별도의 `OauthTokenService`로 이동한 것으로 보입니다. 이는 단일 책임 원칙(SRP)을 더 잘 따르는 방향으로의 개선입니다. 사용자 정보 로드와 토큰 생성이 분리되어 각 클래스의 책임이 더 명확해졌습니다.

PR 설명에 언급된 대로 "oAuth 로직에서 JWT 반환 로직 리팩토링"이 잘 이루어진 것으로 보입니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-28T13:31:16Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt (1)</summary><blockquote>

`5-5`: **하드코딩된 에러 메시지에 대한 검토 필요**

`AuthenticationException`을 상속받는 것은 좋은 변경이지만, 에러 메시지가 하드코딩 되어 있습니다. 이전 구현에서는 `ErrorCode` 열거형을 사용하여 에러 코드를 표준화했었는데, 현재 방식은 다국어 지원이나 일관된 오류 처리가 어려워질 수 있습니다.

PR 설명에서 언급하신 것처럼 오류 처리를 하드코딩한 접근 방식을 수정할 의향이 있으시다면, 아래와 같은 방식을 고려해 보세요:

```diff
- class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
+ class EmailDomainNotAllowedException(
+     private val errorCode: ErrorCode = ErrorCode.EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION
+ ) : AuthenticationException(errorCode.message)
```

이렇게 하면 Spring Security의 인증 예외 흐름을 유지하면서도 표준화된 오류 코드 시스템을 활용할 수 있습니다.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ef513e1d2051a99921c7fdc73d8467624430526c and 8c8aec17ddeb66a982fab333cd1ef22d3aa28c08.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt (1)</summary>

`3-3`: **Spring Security의 AuthenticationException 임포트 적용 완료**

`org.springframework.security.core.AuthenticationException`을 임포트하도록 변경하셨네요. 이 변경은 Spring Security의 인증 흐름과 더 잘 통합되는 올바른 방향입니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:37:16Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T13:43:29Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T13:43:42Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T13:44:00Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt (1)</summary><blockquote>

`13-13`: **ObjectMapper 직접 생성 대신 Bean 주입 고려**  
`private val objectMapper = ObjectMapper()`를 통해 직접 생성하기보다는, 스프링에서 제공하는 기본 `ObjectMapper` Bean을 주입받으면 공통 설정(직렬화/역직렬화 옵션 등)을 재사용할 수 있어 유지보수성이 향상됩니다.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8c8aec17ddeb66a982fab333cd1ef22d3aa28c08 and 8c81902ea950e6fd79134f13e935d4d866548fb5.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
* src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt (4)</summary>

`3-3`: **ObjectMapper import 추가 확인**  
`ObjectMapper`를 통해 JSON 직렬화를 수행하려는 의도와 일치합니다. OAuth 인증 실패 시 에러 정보를 쉽게 반환할 수 있어 적절해 보입니다.

---

`8-10`: **스프링 Bean 등록 사용**  
클래스에 `@Component`를 적용해 핸들러를 스프링 Bean으로 등록한 것은 보안 설정에서 주입하기 용이합니다.

---

`18-28`: **OAuth 인증 예외 처리 로직 점검**  
응답 상태 코드를 401로 설정하고, JSON 형태로 에러 정보를 반환하는 접근이 명확합니다. 다만 국제화(i18n) 적용이 필요한 경우를 대비해 메시지를 리소스 파일로 관리하거나, JSON 변환 과정에서 발생할 수 있는 예외 상황에 대한 로깅·관리 방안을 함께 고려해볼 수 있습니다.

---

`30-30`: **클래스 마무리**  
핸들러 내부 로직이 간결해 가독성이 좋습니다. 현재 구현 상태를 유지해도 문제없어 보입니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:44:01Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:44:13Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T13:44:27Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:44:35Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T13:44:37Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:44:45Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:45:16Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T13:45:34Z
user: login: coderabbitai[bot]
state: APPROVED
author_association: NONE
submitted_at: 2025-03-30T12:42:06Z
user: login: rudeh2926
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-30T23:30:46Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-30T23:37:44Z
user: login: rudeh2926
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T01:43:37Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T03:19:29Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/GlobalExceptionFilter.kt (2)</summary><blockquote>

`14-31`: **예외 처리 범위를 확장하는 것이 좋습니다.**

현재 필터는 `CustomException`만 처리하고 있습니다. 다른 유형의 예외(예: 시스템 예외, 런타임 예외)도 처리하여 클라이언트에 일관된 오류 응답을 제공하는 것이 좋습니다.


```diff
try {
    filterChain.doFilter(request, response)
} catch (e: CustomException) {
    val errorResponse = ErrorResponse(
        statusCode = e.statusCode,
        message = e.message,
        timestamp = LocalDateTime.now(),
        path = request.requestURI,
        method = request.method
    )
    writeErrorResponse(response, e.statusCode, errorResponse)
+} catch (e: Exception) {
+    val errorResponse = ErrorResponse(
+        statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value(),
+        message = "서버 내부 오류가 발생했습니다",
+        timestamp = LocalDateTime.now(),
+        path = request.requestURI,
+        method = request.method
+    )
+    writeErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR.value(), errorResponse)
}
```

---

`14-31`: **예외 로깅을 추가하는 것을 고려해보세요.**

예외가 발생했을 때 로그를 남기면 문제 해결에 도움이 됩니다. SLF4J와 같은 로깅 프레임워크를 사용하여 예외 정보를 기록하는 것이 좋습니다.


```diff
+private val log = LoggerFactory.getLogger(GlobalExceptionFilter::class.java)

override fun doFilterInternal(
    request: HttpServletRequest,
    response: HttpServletResponse,
    filterChain: FilterChain
) {
    try {
        filterChain.doFilter(request, response)
    } catch (e: CustomException) {
+       log.error("CustomException 발생: ${e.message}", e)
        val errorResponse = ErrorResponse(
            statusCode = e.statusCode,
            message = e.message,
            timestamp = LocalDateTime.now(),
            path = request.requestURI,
            method = request.method
        )
        writeErrorResponse(response, e.statusCode, errorResponse)
    }
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4205b72051ddd770a5d1580533ea5563d85e3a21 and 6634cee151812017d5a56b7834912086b5de2107.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/GlobalExceptionFilter.kt` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/GlobalExceptionFilter.kt (4)</summary>

`1-10`: **패키지 및 imports 선언이 적절합니다.**

필요한 모든 종속성이 올바르게 가져와졌으며, 패키지 구조가 전역 예외 처리를 위한 필터에 적합합니다.

---

`11-13`: **필터 클래스 정의가 잘 구성되었습니다.**

`OncePerRequestFilter`를 상속하는 것은 요청당 한 번만 필터가 실행되도록 보장하는 좋은 방법입니다. 생성자를 통한 `ObjectMapper` 주입도 적절합니다.

---

`33-43`: **writeErrorResponse 메서드가 잘 구현되었습니다.**

응답 상태, 콘텐츠 타입 및 문자 인코딩을 적절하게 설정하고 있습니다. ObjectMapper를 사용하여 ErrorResponse 객체를 JSON으로 직렬화하는 접근 방식도 적절합니다.

---

`1-44`: <details>
<summary>✅ Verification successful</summary>

**CustomException 클래스가 코드베이스에 존재하는지 확인하세요.**

이 필터는 `CustomException` 클래스에 의존하고 있습니다. 이 클래스가 코드베이스에 정의되어 있고 필요한 `statusCode` 및 `message` 속성을 가지고 있는지 확인해주세요.

---

🏁 Script executed:

```shell
#!/bin/bash
# CustomException 클래스 확인
echo "CustomException 클래스 찾기:"
rg -l "class CustomException" --type kotlin

# ErrorResponse 클래스 확인
echo "ErrorResponse 클래스 찾기:"
rg -l "class ErrorResponse" --type kotlin
```

Length of output: 369

---



</details>

**검증 완료: CustomException 및 ErrorResponse 클래스 모두 코드베이스에 존재함**

- `CustomException` 클래스는 `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomException.kt` 파일에 정의되어 있습니다.
- `ErrorResponse` 클래스는 `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/ErrorResponse.kt` 파일에 정의되어 있습니다.

필터에서 이 두 클래스를 올바르게 사용하고 있으며, 필요한 속성(`statusCode`, `message` 등)도 포함된 것으로 확인되어 코드에 추가 수정 사항은 없습니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-31T03:20:41Z
user: login: rudeh2926
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-31T05:53:39Z
user: login: rudeh2926
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T05:54:40Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:11:00Z
user: login: qkrwndnjs1075
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:11:08Z

Review comments:
diff_hunk: @@ -2,6 +2,7 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
 
 import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.EmailDomainNotAllowedException
 import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**OAuth2AuthenticationException 임포트 추가**

OAuth2AuthenticationException을 임포트하였으나 현재 코드에서 직접 사용되지 않고 있습니다. EmailDomainNotAllowedException 클래스가 AuthenticationException을 상속받도록 변경되었을 가능성이 있습니다. 코드 변경의 완전성을 위해 EmailDomainNotAllowedException 클래스도 함께 확인하는 것이 좋겠습니다.

실제로 EmailDomainNotAllowedException 클래스가 변경되었는지 확인해 보세요:

---

🏁 Script executed:

```shell
#!/bin/bash
# EmailDomainNotAllowedException 클래스 확인
rg -n "class EmailDomainNotAllowedException"
```

Length of output: 240

---



</details>

**불필요한 OAuth2AuthenticationException import 제거 필요**  
검증 결과, EmailDomainNotAllowedException 클래스가 `AuthenticationException`을 상속하도록 변경된 것을 확인했습니다. 따라서,  
- `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt` 파일에 있는 `OAuth2AuthenticationException` 임포트는 더 이상 사용되지 않으므로 제거해야 합니다.

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit 8c8aec1
created_at: 2025-03-28T13:29:10Z
author_association: NONE
diff_hunk: @@ -2,5 +2,6 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.exception
 
 import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
 import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import org.apache.tomcat.websocket.AuthenticationException
 
-class EmailDomainNotAllowedException : CustomException(ErrorCode.EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION)
+class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**잘못된 AuthenticationException 클래스 임포트**

현재 `org.apache.tomcat.websocket.AuthenticationException`을 임포트하고 있는데, 이는 Spring Security의 표준 예외 클래스가 아닙니다. Spring Security의 인증 흐름과 원활하게 통합되기 위해 Spring Security의 AuthenticationException을 사용해야 합니다.

다음과 같이 임포트를 수정하세요:

```diff
- import org.apache.tomcat.websocket.AuthenticationException
+ import org.springframework.security.core.AuthenticationException
```

이 변경은 Spring Security의 인증 프로세스와 더 잘 통합되고 보안 구성에서 예외를 올바르게 처리할 수 있게 해줍니다.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import org.springframework.security.core.AuthenticationException

class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit 8c8aec1
created_at: 2025-03-28T13:29:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,28 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import org.springframework.boot.web.servlet.error.ErrorController
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import jakarta.servlet.http.HttpServletRequest
+
+@RestController
+class CustomErrorController : ErrorController {
+
+    @RequestMapping("/error")
+    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
+        val status = request.getAttribute("javax.servlet.error.status_code") as? Int
+            ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
+        val message = request.getAttribute("javax.servlet.error.message")?.toString()
+            ?: "알 수 없는 오류가 발생했습니다."
+
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Error 속성 사용 시 네임스페이스 확인 필요**

현재 코드는 `javax.servlet.error.status_code`와 같은 속성을 사용하고 있습니다. 그러나 Jakarta EE로의 전환에 따라 이러한 속성 이름이 변경되었을 수 있습니다. 현재 프로젝트가 Jakarta 패키지를 사용하고 있으므로 일관성을 위해 확인이 필요합니다.

다음 스크립트를 실행하여 프로젝트의 Jakarta 의존성 버전을 확인하세요:



Spring Boot 3.0 이상을 사용하는 경우, 다음과 같이 수정하는 것이 좋습니다:

```diff
- val status = request.getAttribute("javax.servlet.error.status_code") as? Int
- val message = request.getAttribute("javax.servlet.error.message")?.toString()
+ val status = request.getAttribute("jakarta.servlet.error.status_code") as? Int
+ val message = request.getAttribute("jakarta.servlet.error.message")?.toString()
```

---

🏁 Script executed:

```shell
#!/bin/bash
# 프로젝트의 build.gradle 또는 pom.xml에서 Jakarta 의존성 버전 확인
if [ -f "build.gradle" ]; then
  echo "Checking build.gradle for Jakarta dependencies:"
  grep -A 5 -B 5 "jakarta" build.gradle
elif [ -f "build.gradle.kts" ]; then
  echo "Checking build.gradle.kts for Jakarta dependencies:"
  grep -A 5 -B 5 "jakarta" build.gradle.kts
elif [ -f "pom.xml" ]; then
  echo "Checking pom.xml for Jakarta dependencies:"
  grep -A 5 -B 5 "jakarta" pom.xml
else
  echo "No build file found to check dependencies."
fi

# Spring Boot 버전 확인
if [ -f "build.gradle" ]; then
  echo "Checking Spring Boot version:"
  grep -A 2 "spring-boot" build.gradle
elif [ -f "build.gradle.kts" ]; then
  echo "Checking Spring Boot version:"
  grep -A 2 "spring-boot" build.gradle.kts
elif [ -f "pom.xml" ]; then
  echo "Checking Spring Boot version:"
  grep -A 2 -B 2 "<spring-boot.version>" pom.xml || grep -A 2 -B 2 "spring-boot-starter-parent" pom.xml
else
  echo "No build file found to check Spring Boot version."
fi
```

Length of output: 768

---



</details>

**Jakarta 네임스페이스 적용 관련 수정 요청**

확인 결과:
- `build.gradle.kts` 파일에서 Jakarta 관련 종속성(`Dependencies.jakartaValidation`)이 사용되고 있어 프로젝트가 Jakarta API를 기반으로 동작하고 있음을 알 수 있습니다.
- Spring Boot 버전에 대한 명시적 버전 정보는 확인되지 않았으나, Jakarta 의존성 사용을 고려할 때 `javax.servlet.error.*` 대신 `jakarta.servlet.error.*` 네임스페이스를 사용하는 것이 일관성을 유지하는 데 더 적합해 보입니다.

따라서, 아래와 같이 수정할 것을 권장합니다:

```diff
- val status = request.getAttribute("javax.servlet.error.status_code") as? Int
- val message = request.getAttribute("javax.servlet.error.message")?.toString()
+ val status = request.getAttribute("jakarta.servlet.error.status_code") as? Int
+ val message = request.getAttribute("jakarta.servlet.error.message")?.toString()
```

프로젝트의 Jakarta 네임스페이스 일관성을 위해 위와 같이 변경해주시기 바랍니다.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    @RequestMapping("/error")
    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
        val status = request.getAttribute("jakarta.servlet.error.status_code") as? Int
            ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
        val message = request.getAttribute("jakarta.servlet.error.message")?.toString()
            ?: "알 수 없는 오류가 발생했습니다."
        // ... (rest of the implementation)
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit 8c8aec1
created_at: 2025-03-28T13:29:11Z
author_association: NONE
diff_hunk: @@ -48,14 +49,22 @@ class SecurityConfig(
                 auth
                     .requestMatchers(
                         "/", "/login", "/oauth2/**", "/api/github/auth", "/api/github/auth/**",
-                        "/oauth2/authorize/**", "/error", "/notice/**", "/notice", "/reports",
+                        "/oauth2/authorize/**", "/error"
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**엔드포인트 접근 제한 변경 확인 필요**

엔드포인트 접근 권한 목록이 변경되었습니다. "/notice/**", "/notice", "/reports" 엔드포인트가 제거되고 "/oauth2/authorize/**"가 추가되었습니다. 이 변경으로 인해 기존 기능에 영향이 없는지 확인해 주세요.

---

🏁 Script executed:

```shell
#!/bin/bash
# 해당 엔드포인트들이 다른 곳에서 사용되고 있는지 확인
echo "Checking for removed endpoints usage in controllers..."
rg -l "(/notice|/reports)" --type=kotlin | grep -v SecurityConfig.kt
```

Length of output: 383

---



</details>

**엔드포인트 변경으로 인한 기능 영향 검토 및 수정 필요**

보안 설정에서 "/notice/**", "/notice", "/reports" 엔드포인트를 제거하고 "/oauth2/authorize/**"를 추가한 변경 사항과 관련하여 아래 컨트롤러에서 해당 엔드포인트가 여전히 사용되는 것으로 확인되었습니다:  
- `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/notice/presentation/NoticeController.kt`  
- `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/domain/report/presentation/ReportController.kt`

이에 따라, 기존 기능에 영향을 미칠 가능성이 있으므로, 아래 사항을 확인해 주세요:
- 변경이 의도된 사항이라면, 컨트롤러의 엔드포인트 수정이 필요합니다.
- 만약 기존 기능 유지를 원한다면, SecurityConfig의 변경 사항을 재검토해 주세요.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-28T13:29:11Z
author_association: NONE
diff_hunk: @@ -48,14 +49,22 @@ class SecurityConfig(
                 auth
                     .requestMatchers(
                         "/", "/login", "/oauth2/**", "/api/github/auth", "/api/github/auth/**",
-                        "/oauth2/authorize/**", "/error", "/notice/**", "/notice", "/reports",
+                        "/oauth2/authorize/**", "/error"
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt
user: login: qkrwndnjs1075
body: 괜찮아 저건 너무 권한을 오픈 해놔서 지운 거야 어짜피 필요한 엔드포인트는 GET 뿐이라
created_at: 2025-03-28T13:30:50Z
author_association: MEMBER
diff_hunk: @@ -48,14 +49,22 @@ class SecurityConfig(
                 auth
                     .requestMatchers(
                         "/", "/login", "/oauth2/**", "/api/github/auth", "/api/github/auth/**",
-                        "/oauth2/authorize/**", "/error", "/notice/**", "/notice", "/reports",
+                        "/oauth2/authorize/**", "/error"
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/config/SecurityConfig.kt
user: login: coderabbitai[bot]
body: @qkrwndnjs1075 확인했습니다! 보안을 강화하기 위해 의도적으로 권한 범위를 줄이신 것이군요. GET 메소드만 허용하고 나머지 작업(POST, PUT, DELETE 등)은 인증이 필요하도록 제한하신 것은 보안 측면에서 매우 적절한 접근법입니다. 이렇게 하면 공개적으로 조회는 가능하되 데이터 수정은 인증된 사용자만 가능하게 되어 보안이 향상됩니다. 👍

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-28T13:31:12Z
author_association: NONE
diff_hunk: @@ -2,5 +2,6 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.exception
 
 import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
 import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import org.apache.tomcat.websocket.AuthenticationException
 
-class EmailDomainNotAllowedException : CustomException(ErrorCode.EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION)
+class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Spring Security의 AuthenticationException 사용 권장**

현재 `org.apache.tomcat.websocket.AuthenticationException`을 사용하고 있는데, 이는 Tomcat의 WebSocket 구현에 특화된 예외 클래스입니다. OAuth 및 Spring Security 컨텍스트에서는 `org.springframework.security.core.AuthenticationException`을 사용하는 것이 더 적절합니다.

또한, 오류 메시지를 하드코딩하면 다국어 지원이나 표준화된 오류 처리가 어려워질 수 있습니다. 이전에 사용하던 `ErrorCode` 열거형을 활용하는 방식이 더 유연한 오류 처리를 가능하게 합니다.

```diff
 import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
 import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
-import org.apache.tomcat.websocket.AuthenticationException
+import org.springframework.security.core.AuthenticationException

 class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
import org.springframework.security.core.AuthenticationException

class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit 8c8aec1
created_at: 2025-03-28T13:31:16Z
author_association: NONE
diff_hunk: @@ -2,6 +2,7 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
 
 import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.EmailDomainNotAllowedException
 import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**사용되지 않는 import 확인 필요**

`OAuth2AuthenticationException`을 import 했지만 현재 파일에서 직접 사용되지 않는 것으로 보입니다. AI 요약에 따르면 `EmailDomainNotAllowedException`이 `AuthenticationException`을 상속하도록 변경되었다고 합니다. 이 import가 정말 필요한지 확인해 주세요.

다음 명령을 실행하여 이 import가 실제로 사용되는지 확인할 수 있습니다:

---

🏁 Script executed:

```shell
#!/bin/bash
# EmailDomainNotAllowedException 클래스가 OAuth2AuthenticationException을 사용하는지 확인
rg -A 5 -B 5 "class EmailDomainNotAllowedException" --type kotlin
```

Length of output: 997

---



</details>

**불필요한 import 제거 요청**  
현재 파일(`src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt`)에서 `org.springframework.security.oauth2.core.OAuth2AuthenticationException`이 사용되지 않는 것으로 확인되었습니다. 또한, 관련 이메일 도메인 예외(`EmailDomainNotAllowedException`) 클래스는 `org.apache.tomcat.websocket.AuthenticationException`을 사용하고 있으므로, 해당 import는 더 이상 필요하지 않은 것으로 보입니다. 불필요한 코드를 정리해 주시기 바랍니다.

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit 8c8aec1
created_at: 2025-03-28T13:31:16Z
author_association: NONE
diff_hunk: @@ -2,5 +2,6 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.exception
 
 import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
 import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import org.apache.tomcat.websocket.AuthenticationException
 
-class EmailDomainNotAllowedException : CustomException(ErrorCode.EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION)
+class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
user: login: qkrwndnjs1075
body: 8c81902ea950e6fd79134f13e935d4d866548fb5
created_at: 2025-03-28T13:43:29Z
author_association: MEMBER
diff_hunk: @@ -2,6 +2,7 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
 
 import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.EmailDomainNotAllowedException
 import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
user: login: qkrwndnjs1075
body: 8c81902ea950e6fd79134f13e935d4d866548fb5
created_at: 2025-03-28T13:43:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,28 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import org.springframework.boot.web.servlet.error.ErrorController
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import jakarta.servlet.http.HttpServletRequest
+
+@RestController
+class CustomErrorController : ErrorController {
+
+    @RequestMapping("/error")
+    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
+        val status = request.getAttribute("javax.servlet.error.status_code") as? Int
+            ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
+        val message = request.getAttribute("javax.servlet.error.message")?.toString()
+            ?: "알 수 없는 오류가 발생했습니다."
+
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt
user: login: qkrwndnjs1075
body: 8c81902ea950e6fd79134f13e935d4d866548fb5
created_at: 2025-03-28T13:44:00Z
author_association: MEMBER
diff_hunk: @@ -2,5 +2,6 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.exception
 
 import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
 import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import org.apache.tomcat.websocket.AuthenticationException
 
-class EmailDomainNotAllowedException : CustomException(ErrorCode.EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION)
+class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

@qkrwndnjs1075 확인했습니다. 해당 커밋에서 AuthenticationException 임포트를 Tomcat 웹소켓 패키지에서 Spring Security 패키지로 올바르게 변경하셨군요. 이 변경은 Spring Security의 인증 흐름과 잘 통합될 수 있게 해주며, 제안한 대로 정확하게 수정되었습니다. 👍

---

🏁 Script executed:
```
#!/bin/bash
# Show details of the commit referenced by the user
git show 8c81902ea950e6fd79134f13e935d4d866548fb5 --name-status
echo "===================="
# Show the actual changes in the commit
git show 8c81902ea950e6fd79134f13e935d4d866548fb5
```

Length of output: 128292

---



</details>

@qkrwndnjs1075 확인했습니다. 커밋 8c81902ea950e6fd79134f13e935d4d866548fb5에서 AuthenticationException 임포트를 `org.apache.tomcat.websocket.AuthenticationException`에서 `org.springframework.security.core.AuthenticationException`으로 올바르게 변경하셨네요. 이 수정으로 Spring Security 인증 흐름과 정상적으로 통합될 수 있게 되었습니다. 👍

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-28T13:44:13Z
author_association: NONE
diff_hunk: @@ -2,5 +2,6 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.exception
 
 import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
 import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import org.apache.tomcat.websocket.AuthenticationException
 
-class EmailDomainNotAllowedException : CustomException(ErrorCode.EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION)
+class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
user: login: qkrwndnjs1075
body: 8c81902ea950e6fd79134f13e935d4d866548fb5
created_at: 2025-03-28T13:44:27Z
author_association: MEMBER
diff_hunk: @@ -2,6 +2,7 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
 
 import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.EmailDomainNotAllowedException
 import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# Get information about the commit
git show 8c81902ea950e6fd79134f13e935d4d866548fb5 --name-status
# View the actual changes in the commit
git show 8c81902ea950e6fd79134f13e935d4d866548fb5 -- src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
```

Length of output: 12215

---

🏁 Script executed:
```
#!/bin/bash
# Check for any commits that modify the EmailDomainValidator.kt file
git log --oneline -- src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt

# Check to see if the OAuth2AuthenticationException import was added in a later commit
git grep "import org.springframework.security.oauth2.core.OAuth2AuthenticationException" -- src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt

# Check the EmailDomainNotAllowedException class to understand if it extends AuthenticationException
cat src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
```

Length of output: 528

---



</details>

@qkrwndnjs1075 확인했습니다. 커밋 8c81902ea950e6fd79134f13e935d4d866548fb5에서 EmailDomainValidator.kt 파일이 새로 생성되었고, 불필요한 OAuth2AuthenticationException import가 포함되어 있지 않음을 확인했습니다. EmailDomainNotAllowedException 클래스는 적절하게 AuthenticationException을 상속하고 있어서 코드가 정상적으로 작동할 것입니다. 이전 리뷰에서 지적된 사항이 올바르게 해결되었습니다.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-28T13:44:35Z
author_association: NONE
diff_hunk: @@ -2,6 +2,7 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
 
 import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.EmailDomainNotAllowedException
 import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
user: login: qkrwndnjs1075
body: 8c81902ea950e6fd79134f13e935d4d866548fb5
created_at: 2025-03-28T13:44:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,28 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import org.springframework.boot.web.servlet.error.ErrorController
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import jakarta.servlet.http.HttpServletRequest
+
+@RestController
+class CustomErrorController : ErrorController {
+
+    @RequestMapping("/error")
+    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
+        val status = request.getAttribute("javax.servlet.error.status_code") as? Int
+            ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
+        val message = request.getAttribute("javax.servlet.error.message")?.toString()
+            ?: "알 수 없는 오류가 발생했습니다."
+
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# Get details about the specific commit
git show --name-status 8c81902ea950e6fd79134f13e935d4d866548fb5

# Check if this commit is related to the abbreviated hash mentioned earlier
git rev-parse --short 8c81902ea950e6fd79134f13e935d4d866548fb5

# See the recent commit history to understand the context
git log -n 5 --oneline
```

Length of output: 10968

---



</details>

@qkrwndnjs1075 감사합니다. 커밋 `8c81902`에서 제안된 수정사항을 적용해주신 것을 확인했습니다. jakarta 네임스페이스로 변경하여 프로젝트의 일관성을 유지하셨습니다. 👍

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-28T13:44:45Z
author_association: NONE
diff_hunk: @@ -2,5 +2,6 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.exception
 
 import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
 import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import org.apache.tomcat.websocket.AuthenticationException
 
-class EmailDomainNotAllowedException : CustomException(ErrorCode.EMAIL_DOMAIN_IN_NOT_ALLOWED_EXCEPTION)
+class EmailDomainNotAllowedException : AuthenticationException("허용되지 않은 이메일 도메인입니다")
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# Examine the specified commit
git show 8c81902ea950e6fd79134f13e935d4d866548fb5 --name-status
git show 8c81902ea950e6fd79134f13e935d4d866548fb5
```

Length of output: 67559

---

🏁 Script executed:
```
#!/bin/bash
# Check the changes to the specific file in the commit
git show 8c81902ea950e6fd79134f13e935d4d866548fb5 -- src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/exception/EmailDomainNotAllowedException.kt
```

Length of output: 962

---



</details>

@qkrwndnjs1075 확인했습니다. `org.springframework.security.core.AuthenticationException`을 올바르게 사용하도록 변경해주셨네요. 이전의 `org.apache.tomcat.websocket.AuthenticationException` 대신 Spring Security의 인증 예외를 사용함으로써 OAuth 인증 컨텍스트에 더 적합한 예외 처리가 가능해졌습니다. 👍

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-28T13:45:16Z
author_association: NONE
diff_hunk: @@ -2,6 +2,7 @@ package entry.dsm.gitauth.equusgithubauth.global.oauth.service.component
 
 import entry.dsm.gitauth.equusgithubauth.global.oauth.exception.EmailDomainNotAllowedException
 import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# Show the details of the commit provided by the user
git show 8c81902ea950e6fd79134f13e935d4d866548fb5 --name-status
```

Length of output: 10895

---

🏁 Script executed:
```
#!/bin/bash
# Show the changes to the EmailDomainValidator.kt file in this commit
git show 8c81902ea950e6fd79134f13e935d4d866548fb5 -- src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
```

Length of output: 1319

---

🏁 Script executed:
```
#!/bin/bash
# Show the 8c8aec1 commit that was referenced in the previous comment
git show 8c8aec1 -- src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
```

Length of output: 156

---

🏁 Script executed:
```
#!/bin/bash
# List recent commits to find the one that might be related to 8c8aec1
git log --oneline -10

# Check the current state of the EmailDomainValidator.kt file to see if the OAuth2AuthenticationException import is still there
cat src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/service/component/EmailDomainValidator.kt
```

Length of output: 679

---



</details>

@qkrwndnjs1075 감사합니다. 현재 `EmailDomainValidator.kt` 파일을 확인해 보니 불필요한 `OAuth2AuthenticationException` import가 제거된 것을 확인했습니다. 코드가 깔끔하게 정리되었네요. 👍

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-28T13:45:34Z
author_association: NONE
diff_hunk: @@ -1,20 +1,53 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
+import com.fasterxml.jackson.databind.ObjectMapper
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorResponse
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
 import org.springframework.security.core.AuthenticationException
 import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
 
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
+@Component
+class CustomOAuth2AuthenticationFailureHandler(
+    private val objectMapper: ObjectMapper,
+) : AuthenticationFailureHandler {
     override fun onAuthenticationFailure(
         request: HttpServletRequest,
         response: HttpServletResponse,
         exception: AuthenticationException,
     ) {
+        val (statusCode, errorMessage) =
+            when (exception) {
+                is CustomOauth2AuthenticationException -> {
+                    val cause = exception.cause
+                    if (cause is CustomException) {
+                        cause.statusCode to cause.message
+                    } else {
+                        ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+                    }
+                }
+                is CustomException -> exception.statusCode to exception.message
+                else -> ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+            }
+
+        val errorResponse =
+            ErrorResponse(
+                statusCode = statusCode,
+                message = errorMessage ?: "Unknown error",
+                timestamp = LocalDateTime.now(),
+                path = request.requestURI,
+                method = request.method,
+            )
+
+        response.status = statusCode
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
user: login: rudeh2926
body: 이렇게 하면 oauth 부분에서 터지는 오류만 잡을수있는거 아닌가요?
created_at: 2025-03-30T23:30:42Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,53 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
+import com.fasterxml.jackson.databind.ObjectMapper
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorResponse
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
 import org.springframework.security.core.AuthenticationException
 import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
 
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
+@Component
+class CustomOAuth2AuthenticationFailureHandler(
+    private val objectMapper: ObjectMapper,
+) : AuthenticationFailureHandler {
     override fun onAuthenticationFailure(
         request: HttpServletRequest,
         response: HttpServletResponse,
         exception: AuthenticationException,
     ) {
+        val (statusCode, errorMessage) =
+            when (exception) {
+                is CustomOauth2AuthenticationException -> {
+                    val cause = exception.cause
+                    if (cause is CustomException) {
+                        cause.statusCode to cause.message
+                    } else {
+                        ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+                    }
+                }
+                is CustomException -> exception.statusCode to exception.message
+                else -> ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+            }
+
+        val errorResponse =
+            ErrorResponse(
+                statusCode = statusCode,
+                message = errorMessage ?: "Unknown error",
+                timestamp = LocalDateTime.now(),
+                path = request.requestURI,
+                method = request.method,
+            )
+
+        response.status = statusCode
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
user: login: qkrwndnjs1075
body: Oauth 흐름이 실패하면 failureHandler로 가기 때문에 failure Handler에 구현했습니다. 선배가 말했던 것처럼 만들어도 에러를 못 잡더라고요
created_at: 2025-03-30T23:37:43Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,53 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
+import com.fasterxml.jackson.databind.ObjectMapper
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorResponse
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
 import org.springframework.security.core.AuthenticationException
 import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
 
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
+@Component
+class CustomOAuth2AuthenticationFailureHandler(
+    private val objectMapper: ObjectMapper,
+) : AuthenticationFailureHandler {
     override fun onAuthenticationFailure(
         request: HttpServletRequest,
         response: HttpServletResponse,
         exception: AuthenticationException,
     ) {
+        val (statusCode, errorMessage) =
+            when (exception) {
+                is CustomOauth2AuthenticationException -> {
+                    val cause = exception.cause
+                    if (cause is CustomException) {
+                        cause.statusCode to cause.message
+                    } else {
+                        ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+                    }
+                }
+                is CustomException -> exception.statusCode to exception.message
+                else -> ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+            }
+
+        val errorResponse =
+            ErrorResponse(
+                statusCode = statusCode,
+                message = errorMessage ?: "Unknown error",
+                timestamp = LocalDateTime.now(),
+                path = request.requestURI,
+                method = request.method,
+            )
+
+        response.status = statusCode
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
user: login: rudeh2926
body: 그래도 oauth부분 말고도 jwt 관련된 곳에서 터질 수 있으니 만들어주세요
created_at: 2025-03-31T01:43:37Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,53 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
+import com.fasterxml.jackson.databind.ObjectMapper
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorResponse
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
 import org.springframework.security.core.AuthenticationException
 import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
 
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
+@Component
+class CustomOAuth2AuthenticationFailureHandler(
+    private val objectMapper: ObjectMapper,
+) : AuthenticationFailureHandler {
     override fun onAuthenticationFailure(
         request: HttpServletRequest,
         response: HttpServletResponse,
         exception: AuthenticationException,
     ) {
+        val (statusCode, errorMessage) =
+            when (exception) {
+                is CustomOauth2AuthenticationException -> {
+                    val cause = exception.cause
+                    if (cause is CustomException) {
+                        cause.statusCode to cause.message
+                    } else {
+                        ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+                    }
+                }
+                is CustomException -> exception.statusCode to exception.message
+                else -> ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+            }
+
+        val errorResponse =
+            ErrorResponse(
+                statusCode = statusCode,
+                message = errorMessage ?: "Unknown error",
+                timestamp = LocalDateTime.now(),
+                path = request.requestURI,
+                method = request.method,
+            )
+
+        response.status = statusCode
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
user: login: qkrwndnjs1075
body: 추가했습니다
6634cee151812017d5a56b7834912086b5de2107
created_at: 2025-03-31T03:19:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,30 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import jakarta.servlet.http.HttpServletRequest
+import org.springframework.boot.web.servlet.error.ErrorController
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class CustomErrorController : ErrorController {
+    @RequestMapping("/error")
+    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
+        val status =
+            request.getAttribute("jakarta.servlet.error.status_code") as? Int
+                ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
+        val message =
+            request.getAttribute("jakarta.servlet.error.message")?.toString()
+                ?: "알 수 없는 오류가 발생했습니다."
+
+        val errorResponse =
+            mapOf(
+                "error" to "서버 오류",
+                "message" to message,
+                "status" to status,
+            )
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt
user: login: rudeh2926
body: 하드코딩 지양해주세요
created_at: 2025-03-31T05:54:21Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,53 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
+import com.fasterxml.jackson.databind.ObjectMapper
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorResponse
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
 import org.springframework.security.core.AuthenticationException
 import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
 
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
+@Component
+class CustomOAuth2AuthenticationFailureHandler(
+    private val objectMapper: ObjectMapper,
+) : AuthenticationFailureHandler {
     override fun onAuthenticationFailure(
         request: HttpServletRequest,
         response: HttpServletResponse,
         exception: AuthenticationException,
     ) {
+        val (statusCode, errorMessage) =
+            when (exception) {
+                is CustomOauth2AuthenticationException -> {
+                    val cause = exception.cause
+                    if (cause is CustomException) {
+                        cause.statusCode to cause.message
+                    } else {
+                        ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+                    }
+                }
+                is CustomException -> exception.statusCode to exception.message
+                else -> ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
user: login: rudeh2926
body: 하드코딩 지양해주세요
created_at: 2025-03-31T05:54:34Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,53 @@
 package entry.dsm.gitauth.equusgithubauth.global.oauth.handler
 
+import com.fasterxml.jackson.databind.ObjectMapper
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomException
+import entry.dsm.gitauth.equusgithubauth.global.exception.CustomOauth2AuthenticationException
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorCode
+import entry.dsm.gitauth.equusgithubauth.global.exception.ErrorResponse
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
 import org.springframework.security.core.AuthenticationException
 import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
 
-class CustomOAuth2AuthenticationFailureHandler : AuthenticationFailureHandler {
+@Component
+class CustomOAuth2AuthenticationFailureHandler(
+    private val objectMapper: ObjectMapper,
+) : AuthenticationFailureHandler {
     override fun onAuthenticationFailure(
         request: HttpServletRequest,
         response: HttpServletResponse,
         exception: AuthenticationException,
     ) {
+        val (statusCode, errorMessage) =
+            when (exception) {
+                is CustomOauth2AuthenticationException -> {
+                    val cause = exception.cause
+                    if (cause is CustomException) {
+                        cause.statusCode to cause.message
+                    } else {
+                        ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
+                    }
+                }
+                is CustomException -> exception.statusCode to exception.message
+                else -> ErrorCode.INTERNAL_SERVER_ERROR.status to "OAuth2 인증 실패"
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt
user: login: qkrwndnjs1075
body: e15113ba828031dc988bc8ea00389fd0966037fa
created_at: 2025-03-31T06:11:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,30 @@
+package entry.dsm.gitauth.equusgithubauth.global.exception
+
+import jakarta.servlet.http.HttpServletRequest
+import org.springframework.boot.web.servlet.error.ErrorController
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class CustomErrorController : ErrorController {
+    @RequestMapping("/error")
+    fun handleError(request: HttpServletRequest): ResponseEntity<Map<String, Any>> {
+        val status =
+            request.getAttribute("jakarta.servlet.error.status_code") as? Int
+                ?: HttpStatus.INTERNAL_SERVER_ERROR.value()
+        val message =
+            request.getAttribute("jakarta.servlet.error.message")?.toString()
+                ?: "알 수 없는 오류가 발생했습니다."
+
+        val errorResponse =
+            mapOf(
+                "error" to "서버 오류",
+                "message" to message,
+                "status" to status,
+            )
path: src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt
user: login: qkrwndnjs1075
body: 4b5efade411405aed64343b9042f491bb4f2b44c
created_at: 2025-03-31T06:11:08Z
author_association: MEMBER

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-28T13:26:24Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
이번 변경 사항은 OAuth2 인증 로직과 보안 설정의 개선에 중점을 둡니다.  
`SecurityConfig`는 생성자 파라미터에서 `ObjectMapper`를 제거하고, 성공 및 실패 핸들러를 주입받도록 수정되었으며, 인증 예외 발생 시 JSON 형태의 응답을 반환하도록 업데이트되었습니다.  
또한, 새로운 `CustomErrorController`가 추가되어 전역 오류 처리를 담당하며, OAuth2 성공/실패 핸들러와 토큰 생성 관련 서비스(`OauthTokenService`)가 도입되어, 전반적인 인증 흐름이 보다 간결해졌습니다.

## Changes

| 파일 경로 | 변경 요약 |
| --- | --- |
| `src/.../config/SecurityConfig.kt` | 생성자에서 `ObjectMapper` 제거, `customOAuth2AuthenticationFailureHandler` 및 `customOAuth2AuthenticationSuccessHandler` 추가; 예외 발생 시 JSON 응답 처리 로직 추가 |
| `src/.../exception/CustomErrorController.kt` | `/error` 엔드포인트 처리용 REST 컨트롤러 추가, 요청 속성 기반으로 오류 응답 작성 |
| `src/.../handler/CustomOAuth2AuthenticationFailureHandler.kt`<br>`src/.../handler/CustomOAuth2AuthenticationSuccessHandler.kt` | 실패 핸들러: ObjectMapper 주입 및 `when` 식으로 세분화된 오류 처리 로직 적용<br>성공 핸들러: 토큰 정보 수집 로직 제거, `loginId` 기반 토큰 서비스 호출로 간소화 |
| `src/.../service/GoogleOauthService.kt` | 사용자 정보 로드 시 토큰 생성 로직 및 속성 업데이트 로직 제거 |
| `src/.../service/component/OauthTokenService.kt` | 새 토큰 서비스 클래스 추가, `jwtTokenProvider`를 활용한 토큰 생성 메소드 제공 |
| `src/.../exception/CustomOauth2AuthenticationException.kt`<br>`src/.../exception/ErrorCode.kt` | 새로운 사용자 정의 인증 예외 클래스 추가 및 ErrorCode에 INTERNAL_SERVER_ERROR 항목 추가 |
| `src/.../service/component/EmailDomainValidator.kt` | 허용되지 않는 도메인에 대해 기존 예외 대신 `CustomOauth2AuthenticationException` 발생하도록 수정 |
| `src/.../exception/GlobalExceptionFilter.kt` | HTTP 요청 처리 중 예외를 처리하는 새로운 필터 클래스 추가 |
| `src/.../presentation/ReportController.kt` | `createReport` 메서드에서 `@OnlyUser` 애노테이션 제거 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant SecurityConfig
    participant OAuth2SuccessHandler
    participant OAuth2FailureHandler
    participant OauthTokenService
    participant CustomErrorController

    Client->>SecurityConfig: OAuth2 로그인 요청
    alt 인증 성공
        SecurityConfig->>OAuth2SuccessHandler: onAuthenticationSuccess()
        OAuth2SuccessHandler->>OauthTokenService: generateTokenResponse(loginId)
        OauthTokenService-->>OAuth2SuccessHandler: TokenResponse
        OAuth2SuccessHandler-->>Client: JSON 토큰 응답
    else 인증 실패
        SecurityConfig->>OAuth2FailureHandler: onAuthenticationFailure()
        OAuth2FailureHandler-->>Client: JSON 오류 응답 (HTTP 400)
    end

    Client->>CustomErrorController: /error 호출
    CustomErrorController-->>Client: 오류 정보 반환
```

## Poem
> 나는 귀여운 토끼,  
> 뛰어다니며 코드를 탐험해요,  
> 변경된 보안 설정 속에서  
> 오류도 깔끔하게 해결되고,  
> 토큰은 반짝반짝 빛나네요 🐰✨  
> 코드의 숲에서 행복을 노래해요!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpADK2VmgqyjcEj2wAbb5AokAI7Y0riQ4rjedJAARABiJNTYAQD0AKwATJAAZvAAHpCIosnq8gDU/ACCeLCFJLjiGEQxkADuaMiI2ALM6jT0cuGwnthFfIEA1hStGLQYQogAjAAMAOxp6LMoyBhOApTRaQDMGpAAkmEMmJD7OfjYW9RDntn4vvit8E2QAKo2ADJcADasAa3EQHBSKSI6lg3Q0TGYKQAohhcK4ACL2ACyKQAwh1uJQwDZRBRsOo2GiUtwfN50kcALoAClBuHBkOhsPhiJRaMxOPxhOJpIY5Mp7BpdIZAEoNO5hpAlIgxfBuOJ8Fh8Nlnl5fP4giFEGEvgxvNhlegDdkDhgGJ4CJt0IhEPgGPBqNEkF0SAAaQpE925K6+eTekKQMyZU4eSDMbRYXAdCbINC0WgBF1exMIZC0/UBYKhFAYCRvKQoZi8fBSKm4ZCO3CK/DVJs5B5ieCatDeUqneyB+DBnveWT+9Qls0W6SQWCYWi976UCj4CjIALeT30R0tmqQEjx+B+av2l1fIibegBbJoMSr8+67z4GEMO58AK4ZIYB8AKQA6tAkDMj+9gAPIAHKQH+JACDA+ATGQiAyiWuq7m28BVlEdbUF2GDygYlTpuouEjmOupoDUq6QBg+A0KmBoSPAJCtAawTwAEdbjrM8BXI0F5Nk8y5UXOsyLhec4yCQZCzi4tBgEwSj0J8TZfFaN5HskngiQulAxoqFFNlRJB5Lw0hFMgny+Oe5AuuE+BxooQ7yCpqbcNWt61EOKAmsgSgHtEDyIDhiA3nerj4RYkB4iwdbII4ziuEYZw5p4iKxXUnaavwOpNp4+Z+IWRq4P6aCQKMlDUWgbBXtOsAZAAnBkABsPAroxik5FJtACLeEwGkQskPrl+4UCufDaWJlbcFh7DZrqCkkHpJDyF0RCkMa0RMBgiDwEoFAPtcPY0BQGA4RWaBuSuHl2fueT2uquEyaJQ1zmEboMMkyD3PWu2eNquoDvtTTRWixmaDAuYKKw7CTualqlYxXQ9tatr2lxtA8VuNzyNcGFoKQkCLhM/qtAgDC1PGYa+KM6Kes87GFNga2hI9VHVu1qWajQeRhK8fC5QzNp0D1DATPhsZpTD15RGI9FML4KirmdngkNkfNhI6GHsw6ioLZAwQ9qUl4jWNT0Lg+vC3uIp5tLCqnDdWIhiKcFyzvARCwL2Hv1rqWurkmdp/TqGMqqMO3fPODFMZ83wKRbVCdjbVWaheW3ovAAh4KuDb2Yhmn+IINPG4eaIJioUQKEo+H6MY4BQGQ9D/QZhCkOQVD9FDdZcLw/DCKI4hSDI8gLVQqjqFoOg1yYUBwKgqCHVnxBkMoHeS2iXBUCxXQJfIgwjyoaiaNouhgIYtemAYiAUAwKQlykEy0YuKTsK4KS0IgSIwkmNTP8Eoxf3CHqP8iBPh6vSLauQiApHsMUfauBZAg0gRoCYnADAxHQQYSKlQzhLzbljbe8ZXDZUYCJdaCpUqkJnPbRUAADGBH04EIM1EglBNCchHk8F8Ms3gKzMEcsODU20brDTNB0ZAW1jTkjCsbYaRQGFGwge7ZIOEspPhfEtSANDQJ9zENiC6RIKBsO4C4Kq9QKoSRuFJLAHEazRGyCuZg81NSSOwGFEmCAK64FaPZcgLFjFUDYMdRA/oaEfWNCwUCrY6pRPYJjQRcRtDmgCAACXnFEQxxtQk0wiVEjIMS0RxNwo4Bgp5ECpNEpQGh/o5wVn2NJNMik9Jz24rxGcpVEAIGyBrD4slkCjAfEoIkswyAMDDPMfuj0+Zm0mpEmoWQDLDAKbxR6XQSlmWNupJJngVRkBcF2RA+Fkq6hoXIko8C4hHmOniOcXw2GBNgIof0w1jL3UEdMh8aieIyUktJL46JFCuLoE0nYzFCbPi+YgQcuQZwCTCKTepWAFmxOWVlF5JAHpZXep9J5ipMzcGcdsh5PgBjbPqDdUqNCABClQMQAH0bDIgAIo/GRPYaAbDjRJGQMyZJ0BoBWEgAAFmWMsGUJUtgiK5jDeBRJChEu8CSuo3TNH6N7CijAKQFiahoacSoWAhJ8DYC6fGnDxHOPRK4juPUiiNxSrdV5uEADkyAjVBQJlRJQN4fA+0dKVUynqvjRFdSalCABpVciQ8IQ1QGQESNtnmjWEmkh8bByaYCQI4wY7NdoHUKBasQml6AgQggaSFBK3w4FykslRWBNmaQOUYQiGNBGkRxZ4E5rjSnlJ0hkyONC60pLSZUuM9QHnvxQsNWZTYsifKwIoogyi3k0RYngI88AABeOtOHjLENECalBkBfA2mmYhYpEh8WoqCo9Acbb/RoXibJzAp3RJqMimtxSu1Dt7Vse9j7n15NfdW+JiTNLdvSTq6N4jKGtRYLRGFipyrEOMkgC9+xMDICmRNA6gHrY1v4HgRE0h8KgSkFQXwbaHIYwEbhZAsbMD2l1KcxhCgMCQMXY9QYWs2oPgNe8iOWxV29nXQM9FDcRljKdm8qZSKgOPX3WuCKlhKjeGOjWnOuolCiPbjRpDJl/bRDZt0NV+4CniGkEYKAIMXHSP8aY46Bo+FSFoFwGhvBJB0wkMjQQkm9FuUoFwbRPn9HDtUqI2ydDYGlEQe7NhqkTnX1vgme+j8vjP35LIN+H8uTfybL/bA/9uRANyyAwQPYUjzugZF+B0WiDINwDQgwlnzVSIIHwWzgSKoNLoC5tznmaCQE834MJBAn25Pybh4DGlB0VIoFwB94TRtzPG4UzUCSpskDAyFudm5wv0LOUw1jMWULxZvnfB+kRUsvwy+/T+6gFl5YK02bo92StgPK8w92lX5HVY+7V1hjWorNctWzExHW+Bdec5o3rHnkbDZyUtnDK2MAfrMpt2bUU/1jcR+qlHLo0exe22IzRe3GE1YJ5oq+p2kvnafldzLt2cuwAe4gABz3gGgLKxVknUXft1Ya+gmIFmL6U8S6lmnl30v0+y/dw0j3AEvY5/SNFGKNXzZG8iRNFAQb/N8LpFBHA0EYKwTg1uK9ogEJcPIf6aamjmYIpeli4aLu1o4ZotXLANdjW1yuXXFA+ffMsb8/kAL7T0GoXlXqIaaFXY0DdjQX8FkaFlyzwrifXs9iT3ddFgiIOzww67gNNkrS/oW571c3u3jgcYDthsr0pozTRLX9tZetdcx91Xv5lBQovAcQ4Nz3xKX4FohosLh7UwYBokmDuylag0IAAKkmNBX33VSSwY2Wd8WF3lpkzl5fy1iRVx9XnqF+dc0h8XbSoYiyADK2XZB8HGTAbdxa6zb5Xiqpo4ZtMKOeCu9zHmaL7ot6r6kw8SeQurBbbj2TDQ0LPya5sINz4p/JlThz8S0IL6GihC+bcDnhsJP60Q1rAojqGTbhoAITj6aLJJggwIUASBRC4CkhFjGh4H0TtZmLg5bASL5o+zXC8Z4oEouxhAfj7QkCDy6h74CqcqfhmpKA5C97DSFTFjUDpyZx0T+ieoUQqZDT2Q0JUHsj2BT6jAaBnDgTQDIg2DgSVD/C0r2DmEABq5htK5hNgoENgsWOok+rU0g7AuqRELaoY44YQyhB46o6mNouA5MTovGwaBM9iLAuoihxo6ADQ+0qh0g/o2aGMEcSoqsmhYQMR26kA4aAQuMHhmopqdwDwtAL+ng/+9AXBLW9E/Bl+NwigOM9E8Y3ALGSYXwL0Ksmu4QsgRI/oBR4q9AUhowlcfoxsH4p+2+rQVAfmYe1+NCi+F+RQqIEQsgbCM+5El0+AbmdMEheaXKimkAymqmgi6mw0mmm42mzium+KFAHchmAgxmsSZmiAFmUUNe6A6Y3Wmio+GOpemuy+HeWAJ2ouGq4uGqdON20uP8yerORWTO6eSuWeKu+Ij6LeYJeu9WAO2Io6igfxikLm9+WAQBmuzIiRnAkAeh3ANBdB9QjBRUMoXAaxBKmxpQwAvmwABhQMRA/oeqsgegeg5OQJJe6uoJb+K+x2IuZ2KWsJku8JCeiJf8KeT2KJ0Iiuz8GJgiWJIJXuMp6SfOhugujWwuCWCpzuaW6I12WWqpuWSJqe7OpW9I+A928mBpI2/6y26qa2WyaOdWBuAumCSmJuy87c5uTghCVuOoNuZCBgsYkp8O06fpNaAZoGX6bCQJFidSWAO0RA34w4aIo4lGQ4TEUBU0bUnCPsWG3wTc2OeGA6RG5xDuU0/sFaWiOiuA2Bw6eZViJJdAJUT43wuxfszxGGVERQpGsOj69q2ej0o+bSWwfB5+5a1mn4mYI+vxqAy6mwk+WMux8+0UVY5RaIq+Xw6+OE3wE488feApYA8Yp0pAJKmApw5CLGm5SaPyWA2A3AtAWMmsdoX+VogyYmdoVuEJgW/cfZhiGiNCmo6Zk2WydyRJ9AA50kmY3BhaN0Sgx0vQ5AjGhhMhngkc0RZkIa1qBmdqMqQcC5KuJmTADwx0QK5wYQ+55U9ENC8KGACBJkmYO0WUjo+6SoQ4NoAQaIDFbydFwSAYogFZIYZZKZT6CyAGVaE2uEyIepuEeBP67uzA2lDq2qI5T4McF4UyfCAQ1E2A9G0QvG9ZtWkAjaEJLeHJl+bC3m/c2wjAJRHcjoIyF0XQm4/WsiJFUx/oFFxqpATyGEoQVU3A/oNJPA1AsAYxdJfKAqdRGizRRQzqJx0hPlRQQRJSq4WR/E0Bio+FlAhF5u4VC06VkqoMUldFRVUKlZzomiMQqqSOmqroGAMQOxsIvw0AcQYAAAHCQiYrunwCMo5E0BomuWWi0agGMB6EJtEI6MWpBHzPGGEP0t8N2UFn5t+kpHAjQImJVZ4LlZ4AseoJQEleipuO6JvoqKZIxPcMgHOBQLQAtPQFFW6otOQkUFNbbrRhgHGlunmi1vnJHE+CGBunhv9EtesTOLQCUBHE2VJiBtueYEpipivDpo2FVaIHcWpo8fpo3G1kZl8h8UxF8QDniL8f+YBf0C5gZb6VjbhJmdNj2uTpCdabTsqQ6XdmqflhqfLq6W9h6T/F6RzVjhpUjjzRtl+qaU1ttNwdOe7KdFuSMABVuOzZjgjorf6TjSrTNsyDKPzfKdToqbaa/CqaLU6eqciQrm6SkDLblnLUbWmVzatmbUGawpAIAEmEbuPtL6JtGZAdX6zI0O/Wg2vcx1BiAWPZcFVtcpVpttNpcJItjOzOrtUtZWntTO3tC2nNkdKFWZM2ppoZFpYARgNtYudtOdDOMuLtLpxWOpxdKQpdPpCtiymlmouOZSqt+uZpYZ5xEZeCHcFuRC1ulCDNEsC9E6tC8txtA9SOw9gd9W7CFcXC5YZFP+RZilmAQhoQMNApxCMBSFftyOnaZkaFJBGidR1EHwBo6coh8G7as6ZwtAbCGMAQYgZZcRjiMBHNalPwYwGI9QiSiAnlPZ7iYBPlVwSRID5Et9rUpoaoGe5x+qOlmoISyFuEVgQM7oxi3g2IGa1A5Ml5teK4MwKAOUb1pD2Dfge5tEV9ioxkgY/lQx0xdGdoPGAxjlGi71XYkxWsDeU+j0W+DRlq0QFKBACEGAyUrwdyF0ch8Rnm+0n1yRKheAM4G4QF9kt4pSMxqsmYtQSjiEAe+ZDmti1R5wGtiQtAJU7ZNC1jGA7lRQsWyApuUZ9AB1aB7axd0A8EZAjJPEJAbCWaYifRmiP9f9OV65K1Z+AD/cm1JjvwAINwFIKmmjoDtCnj3j0TiDkRn+044Nf1+sIQRCbBQSFaw0pj6ynjj19i0gVj4TGAjVwwDMeKEyFY3D7EeG4gRqI+UqLVfDxsaaCc9mc15VSqfEU5/MuKKTINNSngdjJAvYhFW4JUTepaqNPl+5pUAQ6TYg/gqVFUAk1+21+G7IeAZxFxhNDxxNngtxJiVxFNzxBm1NbxtNpm9N3xTNROLNBtYdZd/db6giW92ZGdVOTd2dwtrdYtcubOnd7t3dvdqZEdG9OO99eOo9O9od+5FT8M7ZcjNmoO7BUO+0fWngCdoTXTkT9oAWCyYTyjLL0TmSc+dhPYIQzIMQAAOgACQADexd8eQ+ICi0Zz7E/cGQAAvjEMhHHQy8jHEWiBiPK2ID8BQN4FwPybgdXJAISSQUfTrfnGC2zZorYqNL9O2FqBgEQ0PQS4gNSZgcaFwPSYyfQSyaEI9ctUUN69QZQEyQwWs9MTJoPRgFwC6xgOnXFmvb7RXUUm6/jvC1Cclki3aVLo6Uzs6ZqW7dLZ6V+t6Ti+pXi++um0S2wiS/ZAmTCldSlQEuwXJaob7KDCdMjJ8q+JhXOiwNNGYmWQEIjECt8eBKCi/RDj1nS3TOSbk0eLQKSOc7gHq94MyMU5G1wBy2QCU2yQ4OnE0Pzcm7i9C2m2soSzNtbZnYi0Lbm47XnYW5LRiyW7LWW6e5W+e665eyPdXawvKLXRAPXZfLe9Cc3ciwic7eLQXa+0XfdmMIxPaCkAAOLStRCgQLJcvBnj3G64Jm7jExmW6noL2fmNuHp2o0JofPgYdYdhtRP+65B72lgH0JEHg1heZMMOhdOQD+N4aRzKGpEGNlT60Xq9u2wqSUdPhpiQPDrZV94KXDgBGPiBq15PB8cdwUq7teORtsJBOaJCCtC4DackM1i/R+4ackDafMiJPp0bOB5YBKD0FsVWaYGllkSyLdBFBFhSXieyN+UKNxh4Dly1EaP/TWsKMpEZwGNyWgHlMgXTj0DNO2SRzXiWN2TKOpijkXi7ECyzUmTDNvL4WwPVIdDoDeCugOf2NOYuzOMnr/SzEnTxMqWYdzKycUDQM9EVdDVtjDQRdJdRdpFyXDQv37mrsjsn4nTIAtcQNQMwNHhwMoHxP3gwinR+CCfRd0QFO2u5Lte56QyZjeoUdWg7SYQn1vL/QjfoX5XieDDbMYRfA3nBN8YXiXc8dfC7WEF43nEE33FCJvM5Fabk3I16Y/NU1eD/Ovh0125QDgQmO/fk0A8fN/dfQ6jcOU38B/PvGAszitAHCP6yHUKoBUbZCAcYJ10N1gfZv3sO251t0wcd2old0If0fIeIgX6SitdNjafYdj2hl4eWeEc7wkeYCJmVDtlO6LjV5E5aLsvMus/cv9slj/Lo2h4r3tox5x75tJ7t1Fs1Dx6K4aCSuIcMfs/nk73GKiwmpEFAnzwT4EHT7DUnmDvm+XktJPdDBPB3n0SAznhPmYAmpvlRquwWKlSUutae8axkHf6oEVz1MPUGdGcmfcZ7QgFkzgFOjXp2XEI0I/hJ9dOmcczwUQypS7kJfksv00KWfaclNp9IPNOhHF6JMsFWjx8cHH5blCJaddO19EEv2CYbpf2aLV9dNsLkmZS1ryG0KGfGcF8p/DqOhnp0ypeTe0U8c3U3AdA0W6iZHRCzrnAYhPMI9fNI+k2fM6Yg9PEvFY8AsRBAuM2/EzuAm/Fc+wA88K83sIvgc5u08ovQdotak0SHtFnrQSibvYzy5AakK/3f6gD7QatU1uhSHKQ5+0DwXjpGU9A19I2NncFCo0hxGsmgB7TAUG25ahZfisvGoDAKQ4kCISjdb/jT3tJ/8C2uvF9kz0xYgCqB4AjnlALl6csFeNdcnsB0p5f9qeEuB9nT1RYS10WrAt7Mrn1LgN1637DAEZUXKagcO/PcMvhwCaMxhe89UXnbnF6+IiiipaXrZFOjVQIWPpNSvG2UEq42ESvP5CuFV5zQHYkeAmNHnSyx4P48eJ2rAB14M89eTYA3m6UzzGU+KNvX4mDAbjTdrB+DPitt2Gi+9vg3OeBHIVMTeIKAYsdirDEqat99osZL8prRWZPAkw5BcIN4hbZ2YD0XAUqLsAVhvF20+AogGPyogwECielJLjZTqEVwaEcAehiF2aErN20VwcqPt1L5E5aA+AGcJ4QaSbAcYfhEiH4HH7+EjY+wWQJqCUh15UAu/AYGGB9hdADEo+fCN92eYo9hEJNIHl80v4Y9Xi2PO/jDx+JE4n+M3BQbJk1A2DBEzIAooayPZEAAA/P6GGHBsIYfQ+ob8MTZYBLeEwKPJry8Ha9n2UgoIWAhCEqC+K49CnqB2EEwl7aDAqDkwP8EsDtS7tWQbhBRDSkq4fPI3BoMF7aD8hugsGi5RJLERuwfgK7LY0HIOCQ8mTY5DiUUDcsyATgEwQchL7tkrsISEwmYQsJWEbC9hRws4VcKr5Q+BVSYnrDvRpARU7Qq0EwFGgpMFmBRWIIABxBwACE9gABdHIAxowADftgAABrIAgAHB7AAuwOAANVcAAAY4AFahwABNNgAE6aNALQJtEyLW6jhxUtEYYLNXSyEMfg0AZJMiFMJnA8QlQaAGcAgi0pbAJhPEGcCsBSjsQZwHEHGLxDJIFRZXOxhDgk61B2k9VXkTn2FRHANRV3aKp4BiCAAPccAC5k5AEAA/E4ABaZyAIAAjewAD7jgADTnvRJfEGuR1ujGIJUXDYRsmljgXQVAa6T4pwzIpuQ1UeGLNCnwfCQpFOtNAYgtGWbK9KAa3OoLQQqgGoj8laKtm8n7xkNkYvQD+NQ2GANoJ6JwxHs22R7A80eoPa/hD1uHER7hk7FiPyMcSsinh4o8wpYWsK2EbADhGwE4RsAuEbAzINUcsH9AxAjRpoi0daPtHOj3RXolVuTn/GCjNEPIpQJ/yzZYiW6uI/Ooz0JEyDYhpIo0uSPxKw9QUeEwCf8WQGVBwxkY6MbGPjGJjkx4EVMemOsKZjsx0AXMcyErFISmxrYjsT2N7E4SUIeEiUoROoEU4qepEyDvmwokBDpBZWYkZqFonl5eR/A80oIIxEkSIOYgxgVpIJFADu6JvNni70gG4AUQh4bwBiBYAJg+WvYVmquDUGUjJ6mg/BERznrxlSOSZWhMiFcnuSS4Xk3aNQFXA5lfiFiKopQCIDlErQfCKjEjhQjqBaME456N8HE4z5w8miJAMpjMp0BH6Y6U4CQ1ELiNEAAYtoIsiiKuSxK9ATwg3GLHkR3gePegBMJLgUYX6TYAICxGuA0JIpiSaKQmDh64BypHwOgO8N0pEElejbKssNLfoUp5BKbM8VpViEE5j0rjLqaVD1FXlCkr1YoVw1an9SEwxzDhiOHmmONYwhgvSYigq72QFiQVVCPtFW7IxxpUUjyV8BmlzTepi0kyjBnagHR5hzI7omDEabjixoNRUGgTCOgHpdQrVEHqEKGD0NumTUpBpTHQBqxvKDyP8ZrlTDWV90gTcEOiESCOISpPVdVPs23wcinB9EZ6bEMGKypQCFcD8AmGW7fTHuLI9mWV38RvQuOIo2IflTThgwjhD44/kTWfFn9ThVwsHpj0/G39vxDNKAKDMJwpdWJhtMulYNvraz+aMI5gN4MZx+CABaeQ3i9P5wCDLSmIiyb/3InwjABzPREjRPjYkMsG5DShjeIiK+CKRguAXugJnrBS4ySM/QRL2MFAkzB0QGhF7JYa+yqGAcuwQWPZHB4nByxHfq4I14eCtePgy2ZIJRKIiM8krW2eEKJyRDZg03AysbONjNJiIhsTdEpGGoJpTYescaWSMWjsSIxUY+MdxITHgQkxNgFMWmIzFZjsQOYvMRogaR+jwZv0eiOuKDC012ZrVTDJOIvDRtspF41hnGBTnkxcedsO1M+hhk8x5x6ARcUjhllYI5ZrzBWRcIv5vir+vzNWVDxx6ayHhus0ks/yJyJyfZPYP2XtRoaZtBaog52ZpNdnFt4OHs0ISkH/mCNk5/s8mMZKFwgdaBKQTMPcGvjSAUg9MwgrICqgGtcOVI0OdGR0GhS9BDNAwaCnnTsZUU6WNkfUlYllRhkgw21mnnQ7cs5EbyEqTQnwWCINAhC5gN4DH4cJ+w7VJSh50VCLh20crQBrgAyBcBxWcQFwqYVpQYgzgDKPENAFpR/B/girNOb+WV6ODAUh0uLrUELwrlGR/hA0Ku2yb/AK01HGVpAGfQ29oM3DecAc2QzGgHwdCv7tjAXkLNeCPMKgBULBwVpHKriqJHYp1b1gSYsIb6OWWyCyAHwVEGxIxH4zyAfFF6F+PTRvn41Li8sjTIrNfEjjrhN/d+XcM/m/iWMbGAJSxJ/k0J5FCrZRWK1UUQRdFmi7Rbov0WGLjskrNKTR24UTJMUOoAWkliwXJBTweCq+eqiEVEK7ZJkh2eZJ/44jIFzAqQVRN0k0SUOiubWRcl+5+Tg5pC6euQtpGUL6R9uQwRKT2VukDllyELBRxV5mL1emiU2ebMTxQL9eaJZEbYJSpW9SAlc2yNXPHRaJA4VgSgP62NCHLjoGou1vtEtAwEJhsKygMlGOhrcqpxJTWJhH8hSU4cAE9mVEvRqX1jiyVE8GZHPCnA/wx8oYKgDqKBE9GIRH1LnDyDFBQqioJjvZjTRfAXYOoTadiT2nIMKI3sRleHwoJdyxotfROt5TTi8zXq2yMsUoBGKUUYqjAZIJJQ1hxVOUVYR6kwTCB/Azg6VY4vJzzwmx0lkbHylWjaDnVpIAPMlZav04wE7qNANyrp2ILVThRPFOBCQDdXECsV4xeoAcw34TE5KUsyZsMWmbfUrYR4u0PNQvD7A+YpKfaM3Oa5+rUa8DSTDdDuafd9qqBJ0EdVgrBZi+ZHX4rv06A4Uzm5q8aJvIrQOr9VclPOJfS3wvSyZqUC6LrSS5bBc1q8ZxChnYBlkSpN1ApT9yKX3ySlj8h4srI/G0hIeJmapcC0f56zf5tkO5WAgeW/diJYCpUpZJdmbK3ZRI3ZfstiGoq/c/2IDisu3XYi82Pg6yVsuumpYAgV/GkJmHYA1oUgpIK/riTPWoJ1BAU6kbPQjnkcyO0GfejwiH4ZLOOxyOfKBAwCjg9uB5B3pMin5DCAun6/2AGreWrF0U/sb9YlLEREF8C0jVRPAAQhllEOCjJeVVnkBsAOg+cR0NhX2gXNku1xWhEvxoDobniCBWYEgTRBlMM+uVDOBXCmTsaHwhgp9f7CFGUp5ATneoC9Dt6HlBEg0xAfjP3K3AWNQm7jlaAEBXQ9olzW3MQnKhrgMicGApKRHK7HR4mLG7oj7jhmoAlhCwvsEYGOF3z/uD8smpcOfkVK3586jWd8TNZjoLWSQayv1zJKoD2NJATjbgGZAYF9Vg+WgMPBijzg5saGnDc8VPLPlaA4I/Cdhq/XGktsqkx2WspvVPt9192B9Rqgk3PEX13hUuPqSi3fr/codFAXOlS1X8Yt0K3APFsS3OBZgKW89JFrS24AMt84bLRKQa35aMkcWDBepN3UbL8R96gGZVqG01aigdWkkRNuDy+5/czIIjXhnnywb4NYwNhBkroAyg0RNcAwNPBMyNx+Vi8akWvFpKbwaRxHPeLyNHiHwJ4J8QwNdrSjqBaUu0RALSlHbRw6AtKTlM8W+3nx642QNIIsCOAkBFgtADIMsDh1oB6oGOjIIsAYCrBsgv1VYEcFoDjVBUTUVYE1AyCCpBURwVHc1FfBTw64kAcagwHGpoBRAiwVYP8QEBNQmo6O8ahkBvACAjgQuhgLQEWCqwMgGQWgEcDQBoAMgTO5YJNXp0QBGdzOxYPVGWAZBEg9UNUSQCah47Vg9UeHYKmyDw6SA9UI4GkFoCCoid3OtIIKnGrZABAGwJXVAB50MAMddu7HQwAyCrAGAauwVJrqaiqxjdIunqAIDWD1QBAJumCONWWC66od12gPajoECrAUdcO9MLQFWCrBlgaAS3YsDSCx7jgCO3PWkCaiE7xqaQEgNLqx0J6Gd3Oo4IKntCI64d41RYCjvZ20A0guepqCnvGoN7DdKO8aj3st0kAsdawWvcrpl3S6jg/O2gHHoyAN6cdfuxvUcAYCo7sgre36nrqD1E7xq9UJqPbsFRQ6rtDOv7bgAB3vxgddU5iGDs6n6AgAA== -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between aa3a32fd0e234c7c14c3c05f81dc6f6ed8896484 and e15113ba828031dc988bc8ea00389fd0966037fa.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/exception/CustomErrorController.kt` (1 hunks)
* `src/main/kotlin/entry/dsm/gitauth/equusgithubauth/global/oauth/handler/CustomOAuth2AuthenticationFailureHandler.kt` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=EntryDSM/Casper-Recruitment&utm_content=53):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: rudeh2926
created_at: 2025-03-30T23:37:01Z
body: ![image](https://github.com/user-attachments/assets/84f625a1-2e38-42e7-a971-17085bd34e31)
SpringContext 밖에서 터지는 예외처리를 이 코드처럼 작성하는건 어떤가요?
author_association: MEMBER