Skip to content

Comments

[WTH-157] global 도메인 마이그레이션#13

Merged
hyxklee merged 17 commits intodevfrom
refactor/WTH-157-weeth-server-global-도메인-마이그레이션
Feb 20, 2026

Hidden character warning

The head ref may contain hidden characters: "refactor/WTH-157-weeth-server-global-\ub3c4\uba54\uc778-\ub9c8\uc774\uadf8\ub808\uc774\uc158"
Merged

[WTH-157] global 도메인 마이그레이션#13
hyxklee merged 17 commits intodevfrom
refactor/WTH-157-weeth-server-global-도메인-마이그레이션

Conversation

@hyxklee
Copy link
Contributor

@hyxklee hyxklee commented Feb 19, 2026

📌 Summary

어떤 작업인지 한 줄 요약해 주세요.

Global 도메인을 마이그레이션 했습니다

📝 Changes

변경사항을 what, why, how로 구분해 작성해 주세요.

What

  • 코틀린 마이그레이션
  • 불필요 코드 제거 및 기존 코드 개선

Why

  • 장기 유지보수를 위해

How

  • AppleService를 최신 베스트 프렉티스에 맞게 개선하고, 인메모리 캐싱을 적용했습니다. 애플 공개키를 1시간 동안 메모리에 저장하도록 했습니다.
  • 기존 JwtFilter가 UsernamePasswordAuthenticationToken을 인증객체로 저장하는 형식으로 작성이 되어 있었는데, Principal을 저장하도록 바꾸고, 한 번의 파싱으로 id, email, role을 가져올 수 있도록 했습니다.

📸 Screenshots / Logs

필요시 스크린샷 or 로그를 첨부해주세요.

💡 Reviewer 참고사항

리뷰에 참고할 내용을 작성해주세요.

  • Jwt의 경우는 도메인은 아니지만 도메인과 유사한 정도의 복잡성을 가지고 있어서 아키텍처를 유사하게 분리를 했습니다.만.. 도메인과 동일하게 맞추기가 어려워서 네이밍이나 위치가 약간씩 다른 부분이 있습니다. (JwtTokenExtractor 등..)
  • 후속 정리를 함께 하려고 했으나, 너무 PR이 지저분해질 것 같아서 마이그레이션 완료 후에 한 번에 정리 작업을 하겠습니당
    • BaseEntity 사용하는 쪽 수정
    • ErrorCode 코드 정리
    • enum 위치 통일
    • 마이그레이션 후 API 전체 테스트
    • 또 머있더라..알려주시면 감사하겠습니다

✅ Checklist

  • PR 제목 설정 완료 (WTH-123 인증 필터 설정)
  • 테스트 구현 완료
  • 리뷰어 등록 완료
  • 자체 코드 리뷰 완료

Summary by CodeRabbit

  • 인프라 개선

    • 인증 시스템 재구축 및 토큰 저장·검증 흐름 전면 개선
    • Redis 기반 리프레시 토큰 저장소 도입 및 운영 안정성 향상
    • 보안 설정(인증·인가, CORS 등) 정비
  • API 변경

    • 상태 확인 엔드포인트(/health-check) 유지
    • OpenAPI/Swagger 문서 개선 — 오류 응답 예제 및 보안 스키마 추가
  • 테스트

    • 인증·토큰·Redis 관련 단위/통합 테스트 대폭 추가

@hyxklee hyxklee requested a review from soo0711 February 19, 2026 15:31
@hyxklee hyxklee self-assigned this Feb 19, 2026
@hyxklee hyxklee added 🔨 Refactor 코드 구조 개선 및 리팩토링 ⚙ Setting 환경 설정 및 구성 변경 🔐 Security 보안 및 인증/인가 관련 작업 labels Feb 19, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Java 기반 인증·예외·OAuth 구현을 Kotlin으로 마이그레이션하고 JWT 토큰 관리(포트/어댑터), Spring Security 설정, Swagger 설정 및 관련 테스트를 대규모로 재구성/교체했습니다. 다수의 Java 파일이 삭제되고 대응하는 Kotlin 파일이 추가되었습니다.

Changes

Cohort / File(s) Summary
인증 인프라 (Java 삭제)
src/main/java/com/weeth/global/auth/annotation/*, src/main/java/com/weeth/global/auth/authentication/*, src/main/java/com/weeth/global/auth/jwt/*, src/main/java/com/weeth/global/auth/resolver/*
Java로 작성된 CurrentUser/CurrentUserRole 어노테이션, CustomEntry/AccessDenied 핸들러, JWT provider/filter/service, Redis 토큰 서비스, JWT 관련 예외 및 argument resolver 등 기존 인증·JWT 관련 클래스들이 삭제됨.
인증 인프라 (Kotlin 추가)
src/main/kotlin/com/weeth/global/auth/annotation/*, src/main/kotlin/com/weeth/global/auth/authentication/*, src/main/kotlin/com/weeth/global/auth/jwt/..., src/main/kotlin/com/weeth/global/auth/resolver/*, src/main/kotlin/com/weeth/global/auth/jwt/infrastructure/*
동일 책임을 하는 Kotlin 구현 추가: JwtTokenProvider, JwtTokenExtractor, JwtManageUseCase, JwtAuthenticationProcessingFilter, RefreshTokenStorePort 인터페이스 및 RedisRefreshTokenStoreAdapter, 예외 클래스, argument resolver 등 포트/어댑터 기반 토큰 저장소 재구성.
OAuth (Apple/Kakao) 마이그레이션
src/main/java/com/weeth/global/auth/apple/* (삭제), src/main/kotlin/com/weeth/global/auth/apple/* (추가), src/main/java/com/weeth/global/auth/kakao/* (삭제), src/main/kotlin/com/weeth/global/auth/kakao/* (추가)
Apple/Kakao OAuth 서비스와 DTO를 Java에서 Kotlin으로 재작성. Apple은 ES256 클라이언트 시크릿 생성, JWK 공개키 검증 및 캐싱 로직 포함; Kakao는 토큰/유저정보 호출 DTO 매핑으로 재구현.
예외 처리 및 에러 코드 재구성
src/main/java/com/weeth/global/common/exception/* (삭제), src/main/kotlin/com/weeth/global/common/exception/* (추가)
BaseException, ErrorCodeInterface, ExplainError 어노테이션, CommonExceptionHandler, BindExceptionResponse 등 예외/에러코드 관련 Java 구현 제거 및 Kotlin 기반으로 재작성.
보안·인프라 설정
src/main/java/com/weeth/global/config/* (일부 삭제), src/main/kotlin/com/weeth/global/config/* (추가)
Java 기반 SecurityConfig, RedisConfig, AwsS3Config, SwaggerConfig 삭제 및 Kotlin으로 재작성. SecurityFilterChain, CORS, Swagger OpenAPI 및 예제 생성 로직이 Kotlin으로 이동.
도메인 사용처 변경
src/main/java/com/weeth/domain/user/..., src/main/java/com/weeth/domain/user/presentation/*
JwtRedisService 의존을 RefreshTokenStorePort로 대체(생성자/호출부/테스트 업데이트), 토큰 DTO 접근자 변경(getter 사용), dev 환경 자동 승인 로직 추가.
테스트 추가/마이그레이션
src/test/kotlin/... (다수)
JwtProvider/Extractor/Filter/Redis adapter/argument resolver 및 JwtManageUseCase 관련 단위·통합 테스트 추가. 기존 Java 테스트는 Kotlin 테스트로 대체 또는 수정됨.
문서/스타일
.claude/rules/code-style.md, src/main/kotlin/com/weeth/global/common/controller/ExceptionDocController.kt, src/main/kotlin/com/weeth/global/config/swagger/SwaggerConfig.kt
코드 스타일 문서에 주석 규칙 추가(중복 삽입 존재), Swagger 설정을 Kotlin으로 재구현하고 에러 코드 예제 자동 생성 로직 포함.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Filter as JwtAuthenticationProcessingFilter
    participant Extractor as JwtTokenExtractor
    participant Provider as JwtTokenProvider
    participant SecContext as SecurityContext
    participant UseCase as JwtManageUseCase
    participant Store as RefreshTokenStorePort

    Note over Client,UseCase: 토큰 발급
    Client->>UseCase: create(userId, email, role)
    UseCase->>Provider: createAccessToken(id,email,role)
    Provider-->>UseCase: accessToken
    UseCase->>Provider: createRefreshToken(id)
    Provider-->>UseCase: refreshToken
    UseCase->>Store: save(userId, refreshToken, role, email)
    UseCase-->>Client: JwtDto(accessToken, refreshToken)

    Note over Client,Filter: 요청 인증 흐름
    Client->>Filter: doFilterInternal(request)
    Filter->>Extractor: extractAccessToken(request)
    Extractor-->>Filter: accessToken
    Filter->>Provider: validate(accessToken)
    Provider-->>Filter: valid
    Filter->>Provider: parseClaims(accessToken)
    Provider-->>Filter: Claims(id,email,role)
    Filter->>SecContext: setAuthentication(AuthenticatedUser(id,email,role))
    SecContext-->>Filter: auth set

    Note over Client,UseCase: 리프레시(재발급)
    Client->>UseCase: reIssueToken(oldRefreshToken)
    UseCase->>Extractor: extractId(oldRefreshToken)
    Extractor-->>UseCase: userId
    UseCase->>Store: validateRefreshToken(userId, oldRefreshToken)
    Store-->>UseCase: validated
    UseCase->>Store: getRole(userId)
    Store-->>UseCase: role
    UseCase->>Store: getEmail(userId)
    Store-->>UseCase: email
    UseCase->>Provider: createAccessToken(id,email,role)
    UseCase->>Provider: createRefreshToken(id)
    UseCase->>Store: save(userId,newRefreshToken,role,email)
    UseCase-->>Client: JwtDto(newAccessToken,newRefreshToken)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

Possibly related PRs

Suggested reviewers

  • soo0711
  • JIN921
  • dalzzy

Poem

🐰 나는 깡충 토끼, 리팩터의 밤에 왔네
코드 숲을 뛰어다니며 Java를 Kotlin으로 바꿨네
토큰은 포트에 담고, 필터는 길을 지키네
Redis는 리듬을 타고, 예외는 부드럽게 노래하네
축하하라 — 빌드가 통과하길, 당근 한 조각! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 작업 범위와 주요 변경사항을 명확하게 전달합니다. 'WTH-157'은 이슈 번호이고, 'global 도메인 마이그레이션'은 주요 변경내용을 간결하게 설명합니다.
Description check ✅ Passed PR 설명은 템플릿의 주요 섹션(Summary, Changes, Reviewer 참고사항)을 완성했으나, Screenshots/Logs 섹션은 비어있고 체크리스트의 첫 번째 항목 링크가 잘못되었습니다(WTH-123 대신 WTH-157이어야 함).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/WTH-157-weeth-server-global-도메인-마이그레이션

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (13)
src/main/kotlin/com/weeth/global/common/controller/ExceptionDocController.kt (1)

59-63: TODO(SAS 예외) 릴리스 전 정리 필요

Line 59의 TODO가 제품 요구사항이라면 릴리스 전에 이슈로 트래킹하거나 이번 PR에서 마무리하는 편이 안전합니다.

원하시면 SAS 예외 코드 추가 범위를 정리해 구현안/이슈 템플릿을 만들어 드릴까요?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/common/controller/ExceptionDocController.kt`
around lines 59 - 63, The TODO comment about adding SAS-related exceptions must
be resolved before release: either create a tracked issue and reference it from
the TODO or implement the SAS error codes now by adding the appropriate error
enum/class and annotating the controller endpoint; update
ExceptionDocController.authErrorCodes to include the SAS error code class (or
replace the TODO with an issue link), and ensure the new SAS error enum is
included in `@ApiErrorCodeExample` alongside JwtErrorCode so the /auth Operation
exposes those codes.
src/main/kotlin/com/weeth/global/auth/kakao/KakaoAuthService.kt (1)

29-37: Kakao API 호출 실패 시 에러 처리 고려

RestClient.retrieve()는 4xx/5xx 응답 시 예외를 발생시키지만, Kakao API 고유의 에러 응답을 적절히 처리하지 않습니다. 인증 실패, 토큰 만료 등의 케이스에서 더 명확한 예외 변환을 위해 .onStatus() 핸들러 추가를 고려해 보세요.

♻️ 에러 처리 추가 예시
 return requireNotNull(
     restClient
         .post()
         .uri(kakaoProperties.tokenUri)
-        .body(body)
         .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+        .body(body)
         .retrieve()
+        .onStatus({ it.isError }) { _, response ->
+            throw IllegalStateException("Kakao token request failed: ${response.statusCode}")
+        }
         .body<KakaoTokenResponse>(),
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/auth/kakao/KakaoAuthService.kt` around lines
29 - 37, The RestClient call in KakaoAuthService (the chain starting with
restClient.post().uri(kakaoProperties.tokenUri) and returning
body<KakaoTokenResponse>()) doesn't handle Kakao-specific error payloads; add a
.onStatus(...) handler on the ResponseSpec returned by .retrieve() to map
4xx/5xx responses into a meaningful exception (e.g., KakaoAuthException) by
reading the error body (into a DTO or String) and throwing that exception, so
authentication failures / token errors from Kakao are translated into clear,
actionable exceptions rather than generic WebClient errors.
src/main/java/com/weeth/domain/user/application/usecase/UserUseCaseImpl.java (2)

178-186: JwtDto 재생성 불필요 — 직접 반환으로 단순화

Line 185에서 새로운 JwtDto를 생성하는 것은 불필요합니다. JwtDtoaccessTokenrefreshToken 2개 필드만 포함하므로, reIssueToken() 반환값을 그대로 반환하면 됩니다.

권장 변경
-        JwtDto token = jwtManageUseCase.reIssueToken(requestToken);
-        log.info("RefreshToken 발급 완료: {}", token);
-        return new JwtDto(token.getAccessToken(), token.getRefreshToken());
+        JwtDto token = jwtManageUseCase.reIssueToken(requestToken);
+        log.info("RefreshToken 발급 완료: {}", token);
+        return token;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/weeth/domain/user/application/usecase/UserUseCaseImpl.java`
around lines 178 - 186, The refresh method creates a redundant new JwtDto from
jwtManageUseCase.reIssueToken(requestToken); instead of returning the existing
object; modify the refresh(String refreshToken) implementation so it strips the
BEARER, calls jwtManageUseCase.reIssueToken(requestToken) and returns that
JwtDto directly (remove the unnecessary new JwtDto(...) construction and return
the token from reIssueToken as-is), keeping the log.info("RefreshToken 발급 완료:
{}", token) and using the same variable names (refresh, requestToken, token) to
locate the change.

298-313: 신규 헬퍼 메서드는 Kotlin으로 작성하거나 추후 UseCase 마이그레이션 시 함께 이관 권장

isDevEnvironment() 헬퍼가 2026-02-20에 새로 추가된 코드이며, 가이드라인상 신규 코드는 Kotlin으로 작성해야 합니다. 현재 UserUseCaseImpl은 Java로만 존재하므로, 다음 중 하나를 고려해주세요:

  1. 헬퍼만 별도의 Kotlin 유틸리티로 작성, 또는
  2. UseCase 전체 마이그레이션 시 함께 이관

참고로 Kotlin에서는 environment.activeProfiles.any { it in setOf("dev", "local") } 같은 더 간결한 표현이 가능합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/weeth/domain/user/application/usecase/UserUseCaseImpl.java`
around lines 298 - 313, The new Java helper method isDevEnvironment in
UserUseCaseImpl was added but new code should be written in Kotlin; either move
this helper into a small Kotlin utility (e.g., create a Kotlin function
isDevEnvironment(environment: Environment): Boolean that checks activeProfiles
for "dev" or "local") and call that from UserUseCaseImpl, or postpone and plan a
full UseCase migration to Kotlin and remove the Java helper; update references
to use the Kotlin utility (or mark for migration) and ensure behavior matches
the current check for "dev" or "local".
src/test/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilterTest.kt (1)

47-54: Kotest matchers를 더 관용적으로 사용할 수 있습니다.

null 체크와 타입 체크에 Kotest의 내장 matcher를 사용하면 더 읽기 쉬운 테스트가 됩니다.

♻️ 더 관용적인 Kotest assertions 사용 제안
-                (authentication == null) shouldBe false
-                (authentication.principal is AuthenticatedUser) shouldBe true
+                authentication.shouldNotBeNull()
+                authentication.principal.shouldBeInstanceOf<AuthenticatedUser>()

shouldNotBeNull()shouldBeInstanceOf<T>()를 사용하려면 import 추가 필요:

import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.types.shouldBeInstanceOf
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilterTest.kt`
around lines 47 - 54, Replace the raw null/type checks in the test that inspects
SecurityContextHolder.getContext().authentication by using Kotest matchers: call
SecurityContextHolder.getContext().authentication.shouldNotBeNull(), then use
shouldBeInstanceOf<AuthenticatedUser>() on that authentication.principal (or on
the variable you cast) and assign the result to principal; keep subsequent
assertions (principal.id, principal.email, principal.role.name, and
authentication.authorities check) unchanged, and add the imports
io.kotest.matchers.nulls.shouldNotBeNull and
io.kotest.matchers.types.shouldBeInstanceOf to the test file.
src/main/kotlin/com/weeth/global/config/RedisConfig.kt (1)

33-39: RedisConnectionFactory가 중복 생성될 수 있습니다.

redisTemplate() 내에서 redisConnectionFactory()를 직접 호출하면 새로운 LettuceConnectionFactory 인스턴스가 생성됩니다. Spring이 빈을 프록시하지만, 명시적으로 의존성 주입을 사용하는 것이 더 명확합니다.

♻️ 의존성 주입 방식으로 변경 제안
     `@Bean`
-    fun redisTemplate(): RedisTemplate<String, String> =
+    fun redisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<String, String> =
         RedisTemplate<String, String>().apply {
             keySerializer = StringRedisSerializer()
             valueSerializer = StringRedisSerializer()
-            connectionFactory = redisConnectionFactory()
+            connectionFactory = redisConnectionFactory
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/config/RedisConfig.kt` around lines 33 - 39,
redisTemplate() currently calls redisConnectionFactory() directly which can
create a duplicate LettuceConnectionFactory; change redisTemplate to accept a
RedisConnectionFactory (or LettuceConnectionFactory) parameter and use that
injected instance for connectionFactory instead of calling
redisConnectionFactory() so Spring provides the single shared bean; update the
method signature and remove the direct factory call, keeping
keySerializer/valueSerializer assignments on RedisTemplate<String,String>.
src/test/kotlin/com/weeth/global/auth/jwt/application/service/JwtTokenExtractorTest.kt (2)

43-52: extractRefreshToken 성공 케이스 테스트 추가를 고려하세요.

현재 예외 케이스만 테스트하고 있습니다. 정상적으로 refresh token을 추출하는 성공 케이스도 추가하면 좋습니다.

♻️ 성공 케이스 테스트 예시
it("헤더에서 refresh token을 추출한다") {
    val request = mockk<HttpServletRequest>()
    every { request.getHeader("Refresh") } returns "refresh-token"

    val token = jwtTokenExtractor.extractRefreshToken(request)

    token shouldBe "refresh-token"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/kotlin/com/weeth/global/auth/jwt/application/service/JwtTokenExtractorTest.kt`
around lines 43 - 52, Add a success-case unit test in JwtTokenExtractorTest for
the extractRefreshToken method: create a mock HttpServletRequest, stub
getHeader("Refresh") to return a sample token (e.g., "refresh-token"), call
jwtTokenExtractor.extractRefreshToken(request), and assert the returned value
equals that sample token; place this alongside the existing exception test in
the describe("extractRefreshToken") block using the same test style.

32-41: extractAccessToken의 엣지 케이스 테스트 추가를 고려하세요.

Bearer prefix가 없거나 헤더가 null인 경우에 대한 테스트가 있으면 커버리지가 향상됩니다.

♻️ 추가 테스트 케이스 예시
it("헤더가 없으면 null을 반환한다") {
    val request = mockk<HttpServletRequest>()
    every { request.getHeader("Auth") } returns null

    val token = jwtTokenExtractor.extractAccessToken(request)

    token shouldBe null
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/kotlin/com/weeth/global/auth/jwt/application/service/JwtTokenExtractorTest.kt`
around lines 32 - 41, Add edge-case unit tests to JwtTokenExtractorTest for
JwtTokenExtractor.extractAccessToken: add one test where
HttpServletRequest.getHeader("Auth") returns null and assert the method returns
null, and another where the header exists but does not start with "Bearer "
(e.g., returns "access-token" or "Bearer"), asserting null (or expected
behavior) to cover missing-prefix behavior; use mockk to stub HttpServletRequest
and keep test names consistent with existing style.
src/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.kt (1)

22-35: 예상치 못한 RuntimeException은 삼키지 않는 편이 안전합니다.

현재는 모든 RuntimeException을 info 로그 후 통과시켜 내부 오류가 숨겨질 수 있습니다. 토큰 관련 예외만 처리하고 나머지는 전파(또는 공통 예외 처리기로 위임)하는 쪽을 권장합니다.

♻️ 예외 처리 범위 축소 제안
+import com.weeth.global.auth.jwt.application.exception.InvalidTokenException
@@
         } catch (e: TokenNotFoundException) {
             log.debug("Token not found: {}", e.message)
-        } catch (e: RuntimeException) {
-            log.info("error token: {}", e.message)
+        } catch (e: InvalidTokenException) {
+            log.info("invalid token: {}", e.message)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.kt`
around lines 22 - 35, The current doFilterInternal in
JwtAuthenticationProcessingFilter swallows all RuntimeException; change it to
only catch token-related exceptions (e.g., TokenNotFoundException and whatever
specific token validation exception your jwtTokenProvider.validate can throw),
log those, and continue the filter chain, but allow any other unexpected
exceptions to propagate (or be handled by the global exception handler) instead
of catching RuntimeException. Specifically update doFilterInternal to catch
TokenNotFoundException (and the token-validation-specific exception type), call
saveAuthentication on success, ensure filterChain.doFilter(request, response) is
always invoked for normal flow, and remove or rethrow the broad catch
(RuntimeException) so non-token errors surface.
src/main/kotlin/com/weeth/global/auth/apple/AppleAuthService.kt (4)

82-85: JWT 파트 수 검증을 강화하세요.

JWT는 header.payload.signature 형식으로 정확히 3개의 파트로 구성됩니다. 현재 < 2 검사는 header와 payload 접근에는 충분하지만, 서명이 없는 불완전한 토큰도 통과시킬 수 있습니다.

♻️ 제안된 수정
         val tokenParts = idToken.split(".")
-        if (tokenParts.size < 2) {
+        if (tokenParts.size != 3) {
             throw AppleAuthenticationException()
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/auth/apple/AppleAuthService.kt` around lines
82 - 85, The current idToken split only checks for tokenParts.size < 2 and can
allow malformed JWTs without a signature; update the validation in
AppleAuthService.kt where idToken is split into tokenParts to require exactly 3
parts (header, payload, signature) and throw AppleAuthenticationException when
tokenParts.size != 3 (include a clear message or error code if needed) so
incomplete or signature-less tokens are rejected; keep references to the
existing tokenParts variable and AppleAuthenticationException class when
implementing the change.

248-253: 예외 처리 시 원인 예외를 유지하는 것을 권장합니다.

parseJson에서 모든 예외를 잡아 새 AppleAuthenticationException으로 던지면 디버깅에 유용한 스택 트레이스가 손실됩니다. 예외 체인을 유지하면 로깅 시 근본 원인 파악에 도움이 됩니다.

♻️ 예외 체인 유지 제안

AppleAuthenticationException이 원인 예외를 받을 수 있도록 수정되어 있다면:

     private fun parseJson(json: String): ObjectNode =
         try {
             objectMapper.readTree(json) as? ObjectNode ?: throw AppleAuthenticationException()
         } catch (e: Exception) {
-            throw AppleAuthenticationException()
+            throw AppleAuthenticationException(e)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/auth/apple/AppleAuthService.kt` around lines
248 - 253, The parseJson function currently catches all exceptions and rethrows
a new AppleAuthenticationException, losing the original cause; update parseJson
to preserve the exception chain by passing the caught exception (e) as the cause
when constructing AppleAuthenticationException (so the original stack trace from
objectMapper.readTree(json) is retained) and remove swallowing behavior so
callers can inspect/log the root cause.

227-246: 동시성 경쟁 조건(Race Condition) 가능성이 있습니다.

@Volatile은 가시성만 보장하고 원자성은 보장하지 않습니다. 캐시가 만료되거나 null일 때 여러 스레드가 동시에 Apple 서버로 요청을 보낼 수 있습니다. 현재 트래픽 수준에서는 큰 문제가 아닐 수 있지만, 고려해 볼 만한 사항입니다.

♻️ 동기화를 사용한 제안
+    private val cacheLock = Any()
+
     private fun getApplePublicKeys(): ApplePublicKeys {
         val now = Instant.now(clock)
         cached?.let {
             if (now.isBefore(it.expiresAt)) {
                 return it.keys
             }
         }
 
-        val fetched =
-            requireNotNull(
-                restClient
-                    .get()
-                    .uri(appleProperties.keysUri)
-                    .retrieve()
-                    .body<ApplePublicKeys>(),
-            )
-
-        cached = CachedKeys(fetched, now.plus(publicKeysTtl))
-        return fetched
+        synchronized(cacheLock) {
+            // Double-check after acquiring lock
+            cached?.let {
+                if (now.isBefore(it.expiresAt)) {
+                    return it.keys
+                }
+            }
+
+            val fetched =
+                restClient
+                    .get()
+                    .uri(appleProperties.keysUri)
+                    .retrieve()
+                    .body<ApplePublicKeys>()
+                    ?: throw AppleAuthenticationException()
+
+            cached = CachedKeys(fetched, now.plus(publicKeysTtl))
+            return fetched
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/auth/apple/AppleAuthService.kt` around lines
227 - 246, getApplePublicKeys currently reads and updates the shared cached
field without synchronization, allowing multiple threads to fetch keys
concurrently; fix by introducing a lock (e.g., a private val lock = Any() or
Kotlin Mutex) in the AppleAuthService and apply double-checked locking inside
getApplePublicKeys: first check cached/staleness, if stale/null enter
synchronized(lock) (or acquire mutex) then re-check cached, and only then call
restClient to fetch and update cached with CachedKeys(now.plus(publicKeysTtl));
ensure all reads/writes to cached happen under the same synchronization to
guarantee atomicity.

69-77: requireNotNull 대신 도메인 예외 사용을 권장합니다.

requireNotNull은 null일 경우 IllegalArgumentException을 발생시키는데, 이는 다른 메서드에서 사용하는 AppleAuthenticationException과 일관성이 맞지 않습니다.

♻️ 제안된 수정
-        return requireNotNull(
-            restClient
-                .post()
-                .uri(appleProperties.tokenUri)
-                .body(body)
-                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
-                .retrieve()
-                .body<AppleTokenResponse>(),
-        )
+        return restClient
+            .post()
+            .uri(appleProperties.tokenUri)
+            .body(body)
+            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+            .retrieve()
+            .body<AppleTokenResponse>()
+            ?: throw AppleAuthenticationException()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/auth/apple/AppleAuthService.kt` around lines
69 - 77, Replace the use of requireNotNull around the WebClient call in
AppleAuthService (the chain ending with .body<AppleTokenResponse>()) with an
explicit null check that throws the domain exception
AppleAuthenticationException instead of IllegalArgumentException; for example,
assign the result to a variable and if it's null throw new
AppleAuthenticationException with a clear message (including context like
"failed to fetch Apple token") so callers remain consistent with other Apple
auth failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/kotlin/com/weeth/global/auth/authentication/ErrorMessage.kt`:
- Around line 3-9: The ErrorMessage enum must implement ErrorCodeInterface and
replace HTTP status numeric codes with project error codes in the 2xxx range (or
reuse the existing JwtErrorCode constants) to follow the project's error-code
pattern; update the enum declaration ErrorMessage to implement
ErrorCodeInterface, change members (e.g., UNAUTHORIZED ->
ANONYMOUS_AUTHENTICATION with code 2903 and FORBIDDEN -> a suitable 2xxx code or
map to an existing JwtErrorCode entry), and ensure the enum exposes the
interface-required properties/methods so it conforms to the same shape as
JwtErrorCode.

In `@src/main/kotlin/com/weeth/global/auth/kakao/dto/KakaoAccount.kt`:
- Around line 10-11: The KakaoAccount DTO's email property must be nullable
because Kakao may omit email when user doesn't consent; change the declaration
in KakaoAccount from "val email: String" to "val email: String?" while keeping
the `@field`:JsonProperty("email") annotation, and review any usages of
KakaoAccount.email (and related flags isEmailValid/isEmailVerified) to handle
null safely (e.g., null checks or safe-call) so the code no longer assumes email
is always present.

In `@src/main/kotlin/com/weeth/global/common/converter/JsonConverter.kt`:
- Around line 7-10: Remove the unnecessary `@Converter` annotation from the
abstract class JsonConverter<T>; locate the class declaration for
JsonConverter<T> and delete the `@Converter` above it so only concrete converters
(e.g., BoardConfigConverter) remain annotated and the entity field (config)
continues to use `@Convert`(converter = BoardConfigConverter::class) as before.

In `@src/main/kotlin/com/weeth/global/common/exception/CommonExceptionHandler.kt`:
- Around line 74-91: The handler function handle(Exception) currently uses
ex.message directly in the client response; change it to return a generic
message (e.g., INPUT_FORMAT_ERROR_MESSAGE for format errors or a fixed generic
string like "서버에서 오류가 발생했습니다.") when building CommonResponse.createFailure,
while still logging the detailed ex.message and stacktrace via log.warn (use the
existing LOG_FORMAT and the first log.warn that logs the exception). Keep the
statusCode determination logic (using ErrorResponse) and update only the
response body creation in handle and the logging lines so detailed internals
remain in logs but the response to clients contains a non-sensitive, generalized
message.

---

Nitpick comments:
In
`@src/main/java/com/weeth/domain/user/application/usecase/UserUseCaseImpl.java`:
- Around line 178-186: The refresh method creates a redundant new JwtDto from
jwtManageUseCase.reIssueToken(requestToken); instead of returning the existing
object; modify the refresh(String refreshToken) implementation so it strips the
BEARER, calls jwtManageUseCase.reIssueToken(requestToken) and returns that
JwtDto directly (remove the unnecessary new JwtDto(...) construction and return
the token from reIssueToken as-is), keeping the log.info("RefreshToken 발급 완료:
{}", token) and using the same variable names (refresh, requestToken, token) to
locate the change.
- Around line 298-313: The new Java helper method isDevEnvironment in
UserUseCaseImpl was added but new code should be written in Kotlin; either move
this helper into a small Kotlin utility (e.g., create a Kotlin function
isDevEnvironment(environment: Environment): Boolean that checks activeProfiles
for "dev" or "local") and call that from UserUseCaseImpl, or postpone and plan a
full UseCase migration to Kotlin and remove the Java helper; update references
to use the Kotlin utility (or mark for migration) and ensure behavior matches
the current check for "dev" or "local".

In `@src/main/kotlin/com/weeth/global/auth/apple/AppleAuthService.kt`:
- Around line 82-85: The current idToken split only checks for tokenParts.size <
2 and can allow malformed JWTs without a signature; update the validation in
AppleAuthService.kt where idToken is split into tokenParts to require exactly 3
parts (header, payload, signature) and throw AppleAuthenticationException when
tokenParts.size != 3 (include a clear message or error code if needed) so
incomplete or signature-less tokens are rejected; keep references to the
existing tokenParts variable and AppleAuthenticationException class when
implementing the change.
- Around line 248-253: The parseJson function currently catches all exceptions
and rethrows a new AppleAuthenticationException, losing the original cause;
update parseJson to preserve the exception chain by passing the caught exception
(e) as the cause when constructing AppleAuthenticationException (so the original
stack trace from objectMapper.readTree(json) is retained) and remove swallowing
behavior so callers can inspect/log the root cause.
- Around line 227-246: getApplePublicKeys currently reads and updates the shared
cached field without synchronization, allowing multiple threads to fetch keys
concurrently; fix by introducing a lock (e.g., a private val lock = Any() or
Kotlin Mutex) in the AppleAuthService and apply double-checked locking inside
getApplePublicKeys: first check cached/staleness, if stale/null enter
synchronized(lock) (or acquire mutex) then re-check cached, and only then call
restClient to fetch and update cached with CachedKeys(now.plus(publicKeysTtl));
ensure all reads/writes to cached happen under the same synchronization to
guarantee atomicity.
- Around line 69-77: Replace the use of requireNotNull around the WebClient call
in AppleAuthService (the chain ending with .body<AppleTokenResponse>()) with an
explicit null check that throws the domain exception
AppleAuthenticationException instead of IllegalArgumentException; for example,
assign the result to a variable and if it's null throw new
AppleAuthenticationException with a clear message (including context like
"failed to fetch Apple token") so callers remain consistent with other Apple
auth failures.

In
`@src/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.kt`:
- Around line 22-35: The current doFilterInternal in
JwtAuthenticationProcessingFilter swallows all RuntimeException; change it to
only catch token-related exceptions (e.g., TokenNotFoundException and whatever
specific token validation exception your jwtTokenProvider.validate can throw),
log those, and continue the filter chain, but allow any other unexpected
exceptions to propagate (or be handled by the global exception handler) instead
of catching RuntimeException. Specifically update doFilterInternal to catch
TokenNotFoundException (and the token-validation-specific exception type), call
saveAuthentication on success, ensure filterChain.doFilter(request, response) is
always invoked for normal flow, and remove or rethrow the broad catch
(RuntimeException) so non-token errors surface.

In `@src/main/kotlin/com/weeth/global/auth/kakao/KakaoAuthService.kt`:
- Around line 29-37: The RestClient call in KakaoAuthService (the chain starting
with restClient.post().uri(kakaoProperties.tokenUri) and returning
body<KakaoTokenResponse>()) doesn't handle Kakao-specific error payloads; add a
.onStatus(...) handler on the ResponseSpec returned by .retrieve() to map
4xx/5xx responses into a meaningful exception (e.g., KakaoAuthException) by
reading the error body (into a DTO or String) and throwing that exception, so
authentication failures / token errors from Kakao are translated into clear,
actionable exceptions rather than generic WebClient errors.

In
`@src/main/kotlin/com/weeth/global/common/controller/ExceptionDocController.kt`:
- Around line 59-63: The TODO comment about adding SAS-related exceptions must
be resolved before release: either create a tracked issue and reference it from
the TODO or implement the SAS error codes now by adding the appropriate error
enum/class and annotating the controller endpoint; update
ExceptionDocController.authErrorCodes to include the SAS error code class (or
replace the TODO with an issue link), and ensure the new SAS error enum is
included in `@ApiErrorCodeExample` alongside JwtErrorCode so the /auth Operation
exposes those codes.

In `@src/main/kotlin/com/weeth/global/config/RedisConfig.kt`:
- Around line 33-39: redisTemplate() currently calls redisConnectionFactory()
directly which can create a duplicate LettuceConnectionFactory; change
redisTemplate to accept a RedisConnectionFactory (or LettuceConnectionFactory)
parameter and use that injected instance for connectionFactory instead of
calling redisConnectionFactory() so Spring provides the single shared bean;
update the method signature and remove the direct factory call, keeping
keySerializer/valueSerializer assignments on RedisTemplate<String,String>.

In
`@src/test/kotlin/com/weeth/global/auth/jwt/application/service/JwtTokenExtractorTest.kt`:
- Around line 43-52: Add a success-case unit test in JwtTokenExtractorTest for
the extractRefreshToken method: create a mock HttpServletRequest, stub
getHeader("Refresh") to return a sample token (e.g., "refresh-token"), call
jwtTokenExtractor.extractRefreshToken(request), and assert the returned value
equals that sample token; place this alongside the existing exception test in
the describe("extractRefreshToken") block using the same test style.
- Around line 32-41: Add edge-case unit tests to JwtTokenExtractorTest for
JwtTokenExtractor.extractAccessToken: add one test where
HttpServletRequest.getHeader("Auth") returns null and assert the method returns
null, and another where the header exists but does not start with "Bearer "
(e.g., returns "access-token" or "Bearer"), asserting null (or expected
behavior) to cover missing-prefix behavior; use mockk to stub HttpServletRequest
and keep test names consistent with existing style.

In
`@src/test/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilterTest.kt`:
- Around line 47-54: Replace the raw null/type checks in the test that inspects
SecurityContextHolder.getContext().authentication by using Kotest matchers: call
SecurityContextHolder.getContext().authentication.shouldNotBeNull(), then use
shouldBeInstanceOf<AuthenticatedUser>() on that authentication.principal (or on
the variable you cast) and assign the result to principal; keep subsequent
assertions (principal.id, principal.email, principal.role.name, and
authentication.authorities check) unchanged, and add the imports
io.kotest.matchers.nulls.shouldNotBeNull and
io.kotest.matchers.types.shouldBeInstanceOf to the test file.

Comment on lines +3 to +9
enum class ErrorMessage(
val code: Int,
val message: String,
) {
UNAUTHORIZED(401, "인증 정보가 존재하지 않습니다."),
FORBIDDEN(403, "권한이 없습니다."),
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find ErrorCodeInterface definitions and existing error code implementations

echo "=== ErrorCodeInterface 정의 및 사용 패턴 ==="
rg "interface.*ErrorCode|enum.*ErrorCode" --type=kt --type=java -B 2 -A 15 | head -150

echo -e "\n=== ErrorCode enum 인스턴스 예제 ==="
rg "enum class.*ErrorCode|ErrorCode\(" --type=kt --type=java -A 20 | head -100

Repository: Team-Weeth/weeth-server

Length of output: 207


🏁 Script executed:

#!/bin/bash
# 프로젝트 내 에러 코드 범위 확인
echo "=== 프로젝트의 에러 코드 범위 패턴 ==="
rg "code.*[0-9]{3,4}" src/main --type=kt --type=java | grep -E "[2-4]xxx|[2-4][0-9]{3}" | head -50

Repository: Team-Weeth/weeth-server

Length of output: 136


🏁 Script executed:

#!/bin/bash
# ResponseCodeInterface와 ErrorCodeInterface 구조 확인
echo "=== ResponseCodeInterface 및 ErrorCodeInterface 구조 ==="
fd "ResponseCode|ErrorCode" --type f --extension kt --extension java | head -20

Repository: Team-Weeth/weeth-server

Length of output: 1690


🏁 Script executed:

#!/bin/bash
# Examine ErrorCodeInterface definition
echo "=== ErrorCodeInterface 정의 ==="
cat src/main/kotlin/com/weeth/global/common/exception/ErrorCodeInterface.kt

echo -e "\n=== JwtErrorCode 예제 (인증 관련) ==="
cat src/main/kotlin/com/weeth/global/auth/jwt/application/exception/JwtErrorCode.kt

echo -e "\n=== UserErrorCode 예제 ==="
cat src/main/java/com/weeth/domain/user/application/exception/UserErrorCode.java | head -50

Repository: Team-Weeth/weeth-server

Length of output: 3878


🏁 Script executed:

#!/bin/bash
# Check if ErrorMessage.kt is used elsewhere
echo "=== ErrorMessage 사용 위치 ==="
rg "ErrorMessage" src/ --max-count=20

Repository: Team-Weeth/weeth-server

Length of output: 733


🏁 Script executed:

#!/bin/bash
# Check ResponseCodeInterface pattern
echo "=== ResponseCodeInterface 정의 ==="
fd "ResponseCodeInterface" --type f | head -1 | xargs cat

Repository: Team-Weeth/weeth-server

Length of output: 290


ErrorCodeInterface 패턴을 따르도록 구현이 필요합니다.

이 enum은 프로젝트의 에러 코드 규칙을 따르지 않습니다. ErrorCodeInterface를 구현해야 하며, HTTP 상태 코드(401, 403) 대신 프로젝트 규칙에 따른 에러 코드(2xxx 범위)를 사용해야 합니다. 같은 인증 레이어의 JwtErrorCode를 참고하여 ANONYMOUS_AUTHENTICATION(2903) 같은 명확한 에러 코드로 정의하거나, 기존 JwtErrorCode로 통합하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/auth/authentication/ErrorMessage.kt` around
lines 3 - 9, The ErrorMessage enum must implement ErrorCodeInterface and replace
HTTP status numeric codes with project error codes in the 2xxx range (or reuse
the existing JwtErrorCode constants) to follow the project's error-code pattern;
update the enum declaration ErrorMessage to implement ErrorCodeInterface, change
members (e.g., UNAUTHORIZED -> ANONYMOUS_AUTHENTICATION with code 2903 and
FORBIDDEN -> a suitable 2xxx code or map to an existing JwtErrorCode entry), and
ensure the enum exposes the interface-required properties/methods so it conforms
to the same shape as JwtErrorCode.

Comment on lines +7 to 10
import jakarta.persistence.Converter

@Converter
abstract class JsonConverter<T>(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# JsonConverter 구체 구현 및 `@Converter/`@Convert 사용 여부 확인

# 1) `@Converter` 사용 위치 확인
echo "=== `@Converter` 사용 위치 ==="
rg -n --type=kt '@Converter' -A 2

# 2) JsonConverter 상속 클래스 확인
echo -e "\n=== JsonConverter 상속 클래스 ==="
rg -n --type=kt 'class\s+\w+.*:\s*JsonConverter<'

# 3) 엔티티 필드에 `@Convert로` 지정된 곳 확인
echo -e "\n=== `@Convert` 사용 위치 ==="
rg -n --type=kt '@Convert' -A 2

# 4) 파일 구조 확인
echo -e "\n=== converter 디렉토리 구조 ==="
find . -path '*/converter/*' -name '*.kt' -type f | head -20

Repository: Team-Weeth/weeth-server

Length of output: 507


🏁 Script executed:

#!/bin/bash

# 1) Read the JsonConverter file to see implementation
echo "=== JsonConverter.kt 전체 내용 ==="
cat -n ./src/main/kotlin/com/weeth/global/common/converter/JsonConverter.kt

# 2) Read BoardConfigConverter file
echo -e "\n=== BoardConfigConverter.kt 전체 내용 ==="
cat -n ./src/main/kotlin/com/weeth/domain/board/domain/converter/BoardConfigConverter.kt

# 3) Search for `@Converter` annotation (without type filter)
echo -e "\n=== `@Converter` 어노테이션 검색 ==="
rg '@Converter' -A 3

# 4) Search for JsonConverter usage
echo -e "\n=== JsonConverter 상속/사용 검색 ==="
rg 'JsonConverter' -A 2

# 5) Search for `@Convert` annotation
echo -e "\n=== `@Convert` 어노테이션 검색 ==="
rg '@Convert' -A 2

Repository: Team-Weeth/weeth-server

Length of output: 4004


추상 클래스의 @Converter 제거 권장

현재 구현은 안전합니다. 구체 변환기 BoardConfigConverter@Converter로 올바르게 표시되어 있고, 엔티티 필드(config)에서 @Convert(converter = BoardConfigConverter::class)로 명시적으로 지정되어 있습니다. 그러나 추상 클래스 JsonConverter<T>에는 인스턴스화할 수 없으므로 @Converter 어노테이션이 불필요합니다. 추상 클래스의 어노테이션을 제거하여 의도를 더 명확히 하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/common/converter/JsonConverter.kt` around
lines 7 - 10, Remove the unnecessary `@Converter` annotation from the abstract
class JsonConverter<T>; locate the class declaration for JsonConverter<T> and
delete the `@Converter` above it so only concrete converters (e.g.,
BoardConfigConverter) remain annotated and the entity field (config) continues
to use `@Convert`(converter = BoardConfigConverter::class) as before.

Comment on lines +74 to +91
@ExceptionHandler(Exception::class)
fun handle(ex: Exception): ResponseEntity<CommonResponse<Void?>> {
val statusCode = if (ex is ErrorResponse) ex.statusCode.value() else 500

log.warn("예외 처리(Exception)", ex)
log.warn(LOG_FORMAT, ex::class.simpleName, statusCode, ex.message)

val response = CommonResponse.createFailure(statusCode, ex.message ?: "")

return ResponseEntity
.status(statusCode)
.body(response)
}

companion object {
private const val INPUT_FORMAT_ERROR_MESSAGE = "입력 포맷이 올바르지 않습니다."
private const val LOG_FORMAT = "Class : {}, Code : {}, Message : {}"
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

일반 예외 응답에 내부 메시지 노출 위험이 있습니다.

ex.message를 그대로 내려주면 내부 구현 정보가 노출될 수 있습니다. 클라이언트에는 일반화된 메시지를 반환하고 상세 내용은 로그로만 남기는 편이 안전합니다.

🔒 노출 최소화 응답 제안
-        val response = CommonResponse.createFailure(statusCode, ex.message ?: "")
+        val response = CommonResponse.createFailure(statusCode, INTERNAL_SERVER_ERROR_MESSAGE)
@@
     companion object {
         private const val INPUT_FORMAT_ERROR_MESSAGE = "입력 포맷이 올바르지 않습니다."
+        private const val INTERNAL_SERVER_ERROR_MESSAGE = "서버 오류가 발생했습니다."
         private const val LOG_FORMAT = "Class : {}, Code : {}, Message : {}"
     }
📝 Committable suggestion

‼️ 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.

Suggested change
@ExceptionHandler(Exception::class)
fun handle(ex: Exception): ResponseEntity<CommonResponse<Void?>> {
val statusCode = if (ex is ErrorResponse) ex.statusCode.value() else 500
log.warn("예외 처리(Exception)", ex)
log.warn(LOG_FORMAT, ex::class.simpleName, statusCode, ex.message)
val response = CommonResponse.createFailure(statusCode, ex.message ?: "")
return ResponseEntity
.status(statusCode)
.body(response)
}
companion object {
private const val INPUT_FORMAT_ERROR_MESSAGE = "입력 포맷이 올바르지 않습니다."
private const val LOG_FORMAT = "Class : {}, Code : {}, Message : {}"
}
`@ExceptionHandler`(Exception::class)
fun handle(ex: Exception): ResponseEntity<CommonResponse<Void?>> {
val statusCode = if (ex is ErrorResponse) ex.statusCode.value() else 500
log.warn("예외 처리(Exception)", ex)
log.warn(LOG_FORMAT, ex::class.simpleName, statusCode, ex.message)
val response = CommonResponse.createFailure(statusCode, INTERNAL_SERVER_ERROR_MESSAGE)
return ResponseEntity
.status(statusCode)
.body(response)
}
companion object {
private const val INPUT_FORMAT_ERROR_MESSAGE = "입력 포맷이 올바르지 않습니다."
private const val INTERNAL_SERVER_ERROR_MESSAGE = "서버 오류가 발생했습니다."
private const val LOG_FORMAT = "Class : {}, Code : {}, Message : {}"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/kotlin/com/weeth/global/common/exception/CommonExceptionHandler.kt`
around lines 74 - 91, The handler function handle(Exception) currently uses
ex.message directly in the client response; change it to return a generic
message (e.g., INPUT_FORMAT_ERROR_MESSAGE for format errors or a fixed generic
string like "서버에서 오류가 발생했습니다.") when building CommonResponse.createFailure,
while still logging the detailed ex.message and stacktrace via log.warn (use the
existing LOG_FORMAT and the first log.warn that logs the exception). Keep the
statusCode determination logic (using ErrorResponse) and update only the
response body creation in handle and the logging lines so detailed internals
remain in logs but the response to clients contains a non-sensitive, generalized
message.

Copy link
Collaborator

@soo0711 soo0711 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생 많으셨습니다!! 모두 잘 맞게 마이그레이션 된 것 같아요!!
JWT 구조가 다른 도메인과 통일되어 코드 흐름이 더 잘 읽힙니당 👍

@hyxklee hyxklee merged commit 3b2288a into dev Feb 20, 2026
2 checks passed
@hyxklee hyxklee deleted the refactor/WTH-157-weeth-server-global-도메인-마이그레이션 branch February 20, 2026 04:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 Refactor 코드 구조 개선 및 리팩토링 🔐 Security 보안 및 인증/인가 관련 작업 ⚙ Setting 환경 설정 및 구성 변경

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants