[WTH-409] 위드 탈퇴 구현#82
Hidden character warning
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough사용자 탈퇴(leave) 기능을 전체 구현합니다. Changes사용자 탈퇴 전체 기능
Sequence Diagram(s)sequenceDiagram
participant Client
participant UserController
participant LeaveUserUseCase
participant ClubMemberRepository
participant User
participant TransactionSynchronizationManager
participant JwtManageUseCase
participant RedisAccessTokenBlacklistStoreAdapter
Client->>UserController: DELETE /api/v4/users/me
UserController->>LeaveUserUseCase: execute(userId)
LeaveUserUseCase->>ClubMemberRepository: findAllActiveByUserIdWithLock(userId)
alt LEAD 활성 멤버 존재
LeaveUserUseCase-->>UserController: UserHasLeadClubException
else 정상
LeaveUserUseCase->>User: member.leave(now), user.leave(now)
LeaveUserUseCase->>TransactionSynchronizationManager: registerSynchronization
LeaveUserUseCase-->>UserController: 완료
UserController-->>Client: USER_LEFT_SUCCESS + 만료 쿠키
TransactionSynchronizationManager-->>JwtManageUseCase: afterCommit → deleteRefreshToken(userId)
TransactionSynchronizationManager-->>RedisAccessTokenBlacklistStoreAdapter: afterCommit → blacklist(userId)
end
sequenceDiagram
participant Client
participant JwtAuthenticationProcessingFilter
participant RedisAccessTokenBlacklistStoreAdapter
participant SecurityContextHolder
Client->>JwtAuthenticationProcessingFilter: HTTP 요청 (액세스 토큰)
JwtAuthenticationProcessingFilter->>RedisAccessTokenBlacklistStoreAdapter: isBlacklisted(userId)
alt 블랙리스트 등록됨
RedisAccessTokenBlacklistStoreAdapter-->>JwtAuthenticationProcessingFilter: true
JwtAuthenticationProcessingFilter->>JwtAuthenticationProcessingFilter: UserInActiveException 발생
else 정상
RedisAccessTokenBlacklistStoreAdapter-->>JwtAuthenticationProcessingFilter: false
JwtAuthenticationProcessingFilter->>SecurityContextHolder: setAuthentication(token)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.kt (1)
47-69:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
UserInActiveException가 실제 차단 응답으로 이어지지 않습니다.
validateUserStatus()가UserInActiveException을 던져도doFilterInternal()의catch (e: RuntimeException)에서 로깅만 하고 체인을 계속 태워 버립니다. 그래서 이 변경으로 기대한USER_INACTIVE(403)응답은 나오지 않고, LEFT/BANNED 사용자는 그냥 익명 요청처럼 처리됩니다. 보호된 엔드포인트는 일반 인증 실패로 바뀌고,permitAll엔드포인트는 그대로 진행될 수 있습니다. 이 예외는 여기서 삼키지 말고 보안 예외 처리기나HandlerExceptionResolver로 위임해서 명시적인 실패 응답으로 끝내는 쪽이 맞습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.kt` around lines 47 - 69, The filter currently swallows UserInActiveException in doFilterInternal (catch(RuntimeException) logs and continues), so banned/left users are treated as anonymous; modify JwtAuthenticationProcessingFilter so validateUserStatus(userId) exceptions are not swallowed: either remove the broad RuntimeException catch or in that catch detect UserInActiveException (or AuthenticationException) and delegate to the app's HandlerExceptionResolver (resolveException(request, response, null, e)) or to the security failure handlers (AuthenticationEntryPoint/AccessDeniedHandler) and then return without calling filterChain.doFilter; ensure validateUserStatus, UserInActiveException and the doFilterInternal catch are the referenced symbols when making the change.
🧹 Nitpick comments (2)
src/test/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCaseTest.kt (1)
36-65: ⚡ Quick win새
InvalidTokenException분기도 테스트로 고정해 주세요.Line 36-65의 추가 케이스는
LEFT사용자 차단만 검증합니다. 이번 변경의 또 다른 핵심 분기인extractId("refresh-token") == null경로도 함께 테스트해 두는 편이 좋습니다. 그 케이스에서InvalidTokenException이 발생하고userReader.getById(...)/jwtManageUseCase.reIssueToken(...)가 호출되지 않아야 이 변경이 회귀 없이 유지됩니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCaseTest.kt` around lines 36 - 65, Add a test that covers the extractId == null branch: mock jwtTokenExtractor.extractRefreshToken(servletRequest) to return "refresh-token" and mock jwtTokenExtractor.extractId("refresh-token") to return null, then assert useCase.refreshToken(servletRequest) throws InvalidTokenException and verify that userReader.getById(...) and jwtManageUseCase.reIssueToken(...) are never called; reference the existing mocks and methods (jwtTokenExtractor.extractRefreshToken, jwtTokenExtractor.extractId, userReader.getById, jwtManageUseCase.reIssueToken, useCase.refreshToken) to locate where to add this new spec alongside the current refreshToken tests.src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt (1)
64-67: ⚡ Quick win비활성 사용자 검사를 리소스/중복 조회보다 먼저 배치해 주세요.
공통 원인은
src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt의join()과src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.kt의create()가 모두 호출자 자격 확인보다 리소스 조회를 먼저 수행한다는 점입니다. 이 순서에서는 탈퇴/미등록 사용자가 클럽 존재 여부나 이름 중복 여부에 따라 서로 다른 실패를 관찰할 수 있습니다. 두 유스케이스 모두 사용자 상태 검사를 가장 먼저 수행하도록 맞추는 편이 일관성과 방어 측면에서 더 낫습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt` around lines 64 - 67, The user-active check must run before any resource/duplication lookups; in ManageClubMemberUsecase.join() (and similarly in ManageClubUseCase.create()) move the user retrieval via userReader.getByIdWithLock and the isRegistered() check (throwing UserInActiveException) to occur before calling clubRepository.getClubById or any name/existence checks so that inactive/unregistered callers fail consistently regardless of resource state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCase.kt`:
- Around line 20-23: The current flow extracts userId via
jwtTokenExtractor.extractId(refreshToken) then fetches the user and checks
user.isBannedOrLeft() before Redis matching in JwtManageUseCase.reIssueToken;
change this so Redis refresh token validation (call
refreshTokenStore.validateRefreshToken(refreshToken) or the existing validation
method used by JwtManageUseCase.reIssueToken) runs immediately after
jwtTokenExtractor.extractId(refreshToken) and before calling
userReader.getById(userId), and if validation fails throw the appropriate token
exception (e.g., InvalidTokenException/TokenMismatchException) so we avoid
unnecessary userReader.getById and user.isBannedOrLeft() checks for revoked
tokens.
---
Outside diff comments:
In
`@src/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.kt`:
- Around line 47-69: The filter currently swallows UserInActiveException in
doFilterInternal (catch(RuntimeException) logs and continues), so banned/left
users are treated as anonymous; modify JwtAuthenticationProcessingFilter so
validateUserStatus(userId) exceptions are not swallowed: either remove the broad
RuntimeException catch or in that catch detect UserInActiveException (or
AuthenticationException) and delegate to the app's HandlerExceptionResolver
(resolveException(request, response, null, e)) or to the security failure
handlers (AuthenticationEntryPoint/AccessDeniedHandler) and then return without
calling filterChain.doFilter; ensure validateUserStatus, UserInActiveException
and the doFilterInternal catch are the referenced symbols when making the
change.
---
Nitpick comments:
In
`@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt`:
- Around line 64-67: The user-active check must run before any
resource/duplication lookups; in ManageClubMemberUsecase.join() (and similarly
in ManageClubUseCase.create()) move the user retrieval via
userReader.getByIdWithLock and the isRegistered() check (throwing
UserInActiveException) to occur before calling clubRepository.getClubById or any
name/existence checks so that inactive/unregistered callers fail consistently
regardless of resource state.
In
`@src/test/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCaseTest.kt`:
- Around line 36-65: Add a test that covers the extractId == null branch: mock
jwtTokenExtractor.extractRefreshToken(servletRequest) to return "refresh-token"
and mock jwtTokenExtractor.extractId("refresh-token") to return null, then
assert useCase.refreshToken(servletRequest) throws InvalidTokenException and
verify that userReader.getById(...) and jwtManageUseCase.reIssueToken(...) are
never called; reference the existing mocks and methods
(jwtTokenExtractor.extractRefreshToken, jwtTokenExtractor.extractId,
userReader.getById, jwtManageUseCase.reIssueToken, useCase.refreshToken) to
locate where to add this new spec alongside the current refreshToken tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f25cbda5-83ed-4213-8315-8a2a9ba98169
📒 Files selected for processing (22)
src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.ktsrc/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.ktsrc/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.ktsrc/main/kotlin/com/weeth/domain/user/application/exception/UserErrorCode.ktsrc/main/kotlin/com/weeth/domain/user/application/exception/UserHasLeadClubException.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCase.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/command/WithdrawUserUseCase.ktsrc/main/kotlin/com/weeth/domain/user/domain/entity/User.ktsrc/main/kotlin/com/weeth/domain/user/presentation/UserController.ktsrc/main/kotlin/com/weeth/domain/user/presentation/UserResponseCode.ktsrc/main/kotlin/com/weeth/global/auth/jwt/application/service/TokenCookieProvider.ktsrc/main/kotlin/com/weeth/global/auth/jwt/application/usecase/JwtManageUseCase.ktsrc/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.ktsrc/main/kotlin/com/weeth/global/config/SecurityConfig.ktsrc/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/user/application/usecase/command/WithdrawUserUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/user/domain/entity/UserTest.ktsrc/test/kotlin/com/weeth/domain/user/presentation/UserControllerTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/application/service/TokenCookieProviderTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilterTest.kt
| @Column(name = "left_at", nullable = true) | ||
| var leftAt: LocalDateTime? = null | ||
| private set | ||
|
|
||
| @Column(name = "hard_delete_after", nullable = true) | ||
| var hardDeleteAfter: LocalDateTime? = null | ||
| private set |
There was a problem hiding this comment.
새 컬럼 매핑은 이번 PR에 마이그레이션이 같이 들어와야 합니다.
users.left_at, users.hard_delete_after를 엔티티에 바로 매핑했는데, PR 설명상 Flyway migration은 아직 없는 상태입니다. 이대로 배포되면 JPA가 users를 조회/저장할 때 존재하지 않는 컬럼을 참조해서 탈퇴 기능뿐 아니라 일반 사용자 로드도 깨질 수 있습니다.
hyxklee
left a comment
There was a problem hiding this comment.
고생하셨습니다!
탈퇴 후 접근을 막아야하니 그런 부분에 대해 정합성을 맞추면서도 성능을 챙길 수 있는 방향으로 리뷰를 집중적으로 달아봤습니당
한 번 읽어보시고 적절하다고 판단되는 부분들 수정해주시면 감사하겠습니다!
| """ | ||
| SELECT cm | ||
| FROM ClubMember cm | ||
| JOIN FETCH cm.club |
There was a problem hiding this comment.
club까지 JOIN으로 가져오면 해당 동아리 row까지 Lock으로 잠길 것 같아요!
해당 메서드의 사용처를 보니 Club 정보를 사용하진 않는 것 같던데, Lock 걸리는 요소는 최소화하는게 좋아 보입니다!
There was a problem hiding this comment.
현재 사용처에서 필요한 건 club 엔티티 자체가 아니라 member.club.id 정도라서 락 범위가 불필요하게 커질 수 있겠네용
ClubMember row 중심으로만 락을 잡도록 수정하겠습니다!!
| fun refreshToken(httpServletRequest: HttpServletRequest): JwtDto { | ||
| val refreshToken = jwtTokenExtractor.extractRefreshToken(httpServletRequest) | ||
| val userId = jwtTokenExtractor.extractId(refreshToken) ?: throw InvalidTokenException() | ||
| val user = userReader.getById(userId) |
There was a problem hiding this comment.
이미 탈퇴 로직에서 Refresh Token 제거를 진행했다면, 재발급에서는 DB를 타지 않는 것은 어떨까요??
해당 방식이 보안적으로는 더 안전하긴 하지만, 1차 보안 정책(토큰 삭제)가 있는 상황에서 2차 보안 정책으로 DB 접근이 들어가면 토큰 재발급 성능이 약간 아쉬워질 것 같아요!
1차 보안 정책에서 동시성/엣지 케이스 없이 토큰을 삭제함이 보장된다면 2차 보안 정책은 없어도 될 것 같다는 의견입니다!
| import java.time.LocalDateTime | ||
|
|
||
| @Service | ||
| class WithdrawUserUseCase( |
There was a problem hiding this comment.
탈퇴를 위한 유스케이스 일까요?? Withdraw라는 표현을 AI가 좀 좋아하는 것 같은데, 약관 직관적으로 아쉬운 것 같아용 좀 더 직관적인 이름이 머가 있을까요.. LeaveUserUseCase..?
There was a problem hiding this comment.
동아리를 탈퇴하다가 Leave a club이니까 Leave가 좋을 것 같네용! 이걸로 수정하겠습니닷
| deleteRefreshTokenAfterCommit(userId) | ||
| } | ||
|
|
||
| private fun deleteRefreshTokenAfterCommit(userId: Long) { |
There was a problem hiding this comment.
트랜잭션이 완료된 후에 동작하도록 분리된 것은 정말 조은 것 같아요!
하지만 위에서 달았던 코멘트와 연결되는 부분인데, 해당 메서드에서 리프레시 토큰이 제거되지 않는 경우가 발생하는 것을 최대한 막으면 좋을 것 같아요!
아웃박스까지는 너무 오버 엔지니어링 같고, 여기서 1, 2회 정도 짧은 재시도라도 일단 추가해서 방어적으로 설계해두는 건 어떨까용??
There was a problem hiding this comment.
좋은 의견 감사합니당! 저도 아웃박스 패턴을 고민하긴 했는데 현재 상황에서는 다소 오버엔지니어링이라고 판단됐습니닷
말씀해주신 대로 재시도 로직을 추가해서 실패 가능성을 줄여보겠습니다!!
| MDC.put("userId", claims.id.toString()) | ||
| } | ||
|
|
||
| private fun validateUserStatus(userId: Long) { |
There was a problem hiding this comment.
해당 Filter는 모든 요청에 대해서 동작하기 때문에 DB에 접근하는 건 전체적으로 봤을 때 비용이 많이 발생해요!
실제로 측정해봤을 때 요청 시간도 몇 ms 정도 지연이 됐고, 매 요청마다 DB에 접근하면 커넥션이 부족해지는 케이스도 생길 것 같아서 개인적으로 filter에서 DB에 접근하는 것은 최대한 지양하는 편입니당
액세스 토큰 방어가 필요하긴 하니... 서비스 사용시 무조건적으로 요청되는 API에서 확인을 하거나, Redis를 이용해서 짧은 기간 동안(액세스 토큰 만료 기간보다 약간 크게) 블랙리스트를 관리해서 필터링한다거나 하면 좋을 것 같아요!
There was a problem hiding this comment.
넵!!! Filter에서의 DB 접근 비용이 생각보다 크군요..!
말씀해주신 방향으로 DB 접근을 줄일 수 있도록 개선해보겠습니당👍 좋은 의견 감사합니다!!
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.kt (1)
48-61: 🏗️ Heavy lift
afterCommit훅에서 동기적 Redis 작업으로 응답 시간이 증가할 수 있습니다.
TransactionSynchronization.afterCommit()은 HTTP 응답을 반환하기 전에 실행되므로, 각 토큰 폐기 작업이 3회씩 재시도하면 최악의 경우 사용자는 탈퇴 API 응답을 받기까지 수 초 대기할 수 있습니다.비동기 처리로 개선하는 것을 고려해보세요:
- Spring의
@Async를 사용하여afterCommit내부 작업을 비동기로 실행- 또는 메시지 큐를 활용하여 토큰 폐기를 분리된 워커에서 처리
이렇게 하면 사용자는 즉시 응답을 받고, 토큰 폐기는 백그라운드에서 처리됩니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.kt` around lines 48 - 61, The revokeTokensAfterCommit method executes synchronous Redis operations (retryTokenRevoke calls for both deleteRefreshToken and blacklist) within the afterCommit callback, which blocks the HTTP response. Since each operation retries up to 3 times, this causes significant response delays. Refactor this to use asynchronous processing: either extract the token revocation logic from the afterCommit method into a separate service method marked with Spring's `@Async` annotation, or publish a domain event/message that is handled asynchronously by a message queue consumer, allowing the afterCommit hook to return immediately while token revocation proceeds in the background.src/main/kotlin/com/weeth/global/auth/jwt/infrastructure/RedisAccessTokenBlacklistStoreAdapter.kt (1)
14-23: 💤 Low value블랙리스트 TTL이 access token의 남은 유효 시간을 고려하지 않습니다.
현재 구현은
jwtProperties.access.expiration + 60초를 TTL로 사용하므로, access token이 발급된 직후 탈퇴하든 만료 직전에 탈퇴하든 항상 같은 TTL이 적용됩니다.예를 들어 access token 유효 기간이 1시간이고 발급 1분 후 탈퇴하면, 블랙리스트는 약 1시간 1분 동안 유지되지만 실제로는 59분만 필요합니다.
더 정확하게 하려면 access token에서 만료 시간(exp claim)을 추출하여
(exp - now) + buffer를 TTL로 사용할 수 있습니다. 하지만 현재 구현도 기능상 문제는 없으며, 약간의 메모리 오버헤드만 발생합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/kotlin/com/weeth/global/auth/jwt/infrastructure/RedisAccessTokenBlacklistStoreAdapter.kt` around lines 14 - 23, The blacklist method in RedisAccessTokenBlacklistStoreAdapter currently uses a fixed TTL based on the full access token expiration duration plus a buffer, regardless of when the token was issued. To optimize this, modify the blacklist method to accept the access token's expiration timestamp (exp claim) as a parameter, calculate the actual remaining validity time from the current moment to that expiration timestamp, and use (remaining_time + TTL_BUFFER_MILLIS) as the Redis TTL instead of (jwtProperties.access.expiration + TTL_BUFFER_MILLIS). This ensures the blacklist entry persists only as long as necessary to cover the actual remaining validity of the token.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.kt`:
- Around line 63-83: The retryTokenRevoke method silently fails after exhausting
all TOKEN_REVOKE_ATTEMPTS without propagating the error or implementing proper
failure handling. After the retry loop completes in the for statement, you need
to check if the final attempt failed and handle the error appropriately by
either re-throwing the last exception, sending an admin notification (e.g.,
Slack alert), publishing to a compensation/retry queue (e.g., SQS/Kafka), or at
minimum recording an ERROR level log and metrics. Currently the method only logs
warnings during retry attempts and returns without indicating to the caller
whether token revocation ultimately succeeded or failed, which leaves the system
in an inconsistent state where the user is marked as LEFT in the database but
their tokens may still be active in Redis.
---
Nitpick comments:
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.kt`:
- Around line 48-61: The revokeTokensAfterCommit method executes synchronous
Redis operations (retryTokenRevoke calls for both deleteRefreshToken and
blacklist) within the afterCommit callback, which blocks the HTTP response.
Since each operation retries up to 3 times, this causes significant response
delays. Refactor this to use asynchronous processing: either extract the token
revocation logic from the afterCommit method into a separate service method
marked with Spring's `@Async` annotation, or publish a domain event/message that
is handled asynchronously by a message queue consumer, allowing the afterCommit
hook to return immediately while token revocation proceeds in the background.
In
`@src/main/kotlin/com/weeth/global/auth/jwt/infrastructure/RedisAccessTokenBlacklistStoreAdapter.kt`:
- Around line 14-23: The blacklist method in
RedisAccessTokenBlacklistStoreAdapter currently uses a fixed TTL based on the
full access token expiration duration plus a buffer, regardless of when the
token was issued. To optimize this, modify the blacklist method to accept the
access token's expiration timestamp (exp claim) as a parameter, calculate the
actual remaining validity time from the current moment to that expiration
timestamp, and use (remaining_time + TTL_BUFFER_MILLIS) as the Redis TTL instead
of (jwtProperties.access.expiration + TTL_BUFFER_MILLIS). This ensures the
blacklist entry persists only as long as necessary to cover the actual remaining
validity of the token.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 886cb8bb-3ea1-4e21-81a8-778031aa0b2e
📒 Files selected for processing (13)
src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCase.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCase.ktsrc/main/kotlin/com/weeth/domain/user/presentation/UserController.ktsrc/main/kotlin/com/weeth/global/auth/jwt/domain/port/AccessTokenBlacklistStorePort.ktsrc/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.ktsrc/main/kotlin/com/weeth/global/auth/jwt/infrastructure/RedisAccessTokenBlacklistStoreAdapter.ktsrc/main/kotlin/com/weeth/global/config/SecurityConfig.ktsrc/test/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/user/application/usecase/command/LeaveUserUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/user/presentation/UserControllerTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilterTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/infrastructure/store/RedisAccessTokenBlacklistStoreAdapterTest.kt
💤 Files with no reviewable changes (2)
- src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.kt
- src/main/kotlin/com/weeth/domain/user/application/usecase/command/AuthUserUseCase.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- src/test/kotlin/com/weeth/domain/user/presentation/UserControllerTest.kt
|
|
||
| private fun saveAuthentication(accessToken: String) { | ||
| val claims = jwtTokenExtractor.extractClaims(accessToken) ?: throw TokenNotFoundException() | ||
| validateAccessTokenBlacklist(claims.id) |
There was a problem hiding this comment.
해당 방식에 대한 성능 측정 / 부하 테스트도 한 번 진행되면 좋을 것 같아요!
저도 측정해보진 못했지만 도입 전/후로 측정 데이터가 나오면 조금 더 명확하게 블랙리스트 필터링을 할지 액세스 토큰을 극단적으로 줄일지 결정이 가능할 것 같아욥
redis를 넣는 것도 부담이 될지, 혹은 큰 문제가 없을지
또 만약에 Redis에 장애가 발생한 경우에 대한 처리 방안도 필요할 것 같아용 try - catch 라거나
There was a problem hiding this comment.
1차로 /api/v4/clubs로 로컬 부하 테스트를 진행해봤는데 p95 차이는 VU 10/50/100 기준 최대 1.79ms였습니당
현재 측정 범위에서는 Redis 조회가 큰 병목으로 보이지 않아 우선 블랙리스트 필터링은 유지해도 괜찮아 보입니다!
로컬 테스트라서 추후에 개발환경에서 반복 측정해 더 명확히 확인해보는 편이 좋을 것 같습니다용
장애가 발생한 경우에 대한 처리 방안도 추가해두겠습니닷!! 👍
There was a problem hiding this comment.
위에 부하테스트 결과 스크린샷도 추가해두었습니당
📌 Summary
위드 탈퇴를 구현했습니다.
📝 Changes
What
Why
How
TransactionSynchronization.afterCommit으로 분리BANNED/LEFT사용자를 차단📸 Screenshots / Logs
💡 Reviewer 참고사항
✅ Checklist
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항