Skip to content

♻️ Refactor: JwtAuthenticationFilter User 조회 Redis 캐싱 적용#44

Merged
angoroa merged 1 commit intodevelopfrom
cache/user
Feb 3, 2026
Merged

♻️ Refactor: JwtAuthenticationFilter User 조회 Redis 캐싱 적용#44
angoroa merged 1 commit intodevelopfrom
cache/user

Conversation

@imjuyongp
Copy link
Copy Markdown
Member

@imjuyongp imjuyongp commented Feb 3, 2026

#️⃣ Issue Number

📝 요약(Summary)

구현 요약

새로 생성된 파일

  • UserAuthInfo.java : JWT 인증용 캐시 DTO (id, username, nickname, role)
    수정된 파일
  • UserCacheServie.java : 사용자 정보 캐시 서비스 (@Cacheable 적용)

캐시 설정

  • RedisCacheConfig.java - userAuthInfo 캐시 추가 (TTL: 5분)
  • User 관련 HTTP api dto와 Caching dto를 분리 -> User관련 HTTP dto에는 민감한 정보가 있기 때문에 재사용x

인증 필터

  • JwtAuthenticationFilter.java - UserCacheService 사용으로 변경

컨트롤러 (6개)

  • @AuthenticationPrincipal User → @AuthenticationPrincipal UserAuthInfo

서비스 인터페이스/구현체 (10개)

  • User user 파라미터 → Long userId로 변경

리포지토리 (2개)

  • LikeRepository, ChatRoomRepository - 메서드 시그니처 변경

캐시 효과

변경 전: 인증된 API 요청마다 userRepository.findById() 호출 (DB 조회)

변경 후: Redis에서 UserAuthInfo 조회 (5분 TTL)

  • 캐시 미스(첫요청) : Redis 조회 -> DB조회 -> Redis 저장 -> 응답
  • 캐시 히트(이후 요청(TTL만료전까지)) : Redis 조회 -> 응답
  • DB 부하 감소
  • 응답 속도 향상 (408ms -> 11ms) : 추후 더미로 테스트 다시

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 파일 혹은 폴더명 수정

📸스크린샷 (선택)

💬 공유사항 to 리뷰어

  • 추후 사용자 정보 변경 로직 적용 필요 (캐시 무효화 로직 적용)

  • 사용자 정보 변경된 경우 캐시 정보 갱신
    // UserCacheService.java
    @CacheEvict(cacheNames = "userAuthInfo", key = "#userId")
    public void evictUserAuthInfo(Long userId) {
    log.debug("사용자 캐시 무효화: userId={}", userId);
    }

    • 사용자 정보가 변경되는 시점에서 호출!!! -> 현재 변경 로직 미구현

사용자 정보 변경되는 시점

  • 닉네임 변경
  • 역할(Role)변경
  • 회원 탈퇴

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

릴리스 노트

  • 성능 개선

    • 사용자 인증 정보 캐싱 기능을 추가하여 인증 관련 API 응답 속도 향상
  • 보안 강화

    • 인증 처리 메커니즘을 개선하여 사용자 데이터 보호 강화

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 3, 2026

Walkthrough

이 PR은 사용자 인증 정보를 캐싱 가능한 DTO로 마이그레이션하고, 여러 도메인의 컨트롤러와 서비스에서 User 엔티티 직접 참조를 제거하여 Long userId 기반 접근으로 변경합니다. JWT 필터는 UserCacheService를 통해 캐시된 UserAuthInfo를 사용하도록 업데이트되었습니다.

Changes

Cohort / File(s) Summary
인증 및 캐싱 인프라
global/cache/dto/UserAuthInfo.java, global/cache/service/UserCacheService.java, global/config/RedisCacheConfig.java, global/jwt/JwtAuthenticationFilter.java
User 엔티티를 직렬화 가능한 UserAuthInfo DTO로 대체하고, UserCacheService 를 통한 Redis 캐시 기반 사용자 정보 조회 추가. JwtAuthenticationFilter에서 UserRepository 직접 조회를 캐시 기반 조회로 변경.
채팅 도메인
domain/chat/controller/NoteController.java, domain/chat/repository/ChatRoomRepository.java, domain/chat/service/ChatRoomService.java
NoteController에서 User를 UserAuthInfo로 변경하고, 레포지토리 메서드를 User 파라미터에서 Long userId 기반으로 리팩토링. ChatRoomService에 UserRepository 의존성 추가 및 사용자 조회 로직 포함.
클럽 도메인
domain/club/controller/ClubController.java, domain/club/service/ClubService.java, domain/club/service/ClubServiceImpl.java
ClubController에서 User를 UserAuthInfo로 변경하고 userAuthInfo.getId() 전달. 서비스들이 User 객체 대신 Long userId를 받도록 리팩토링하며, ClubServiceImpl에 UserRepository 추가 및 사용자 조회 로직 포함.
댓글 도메인
domain/comment/controller/CommentController.java, domain/comment/service/CommentService.java, domain/comment/service/CommentServiceImpl.java
CommentController에서 User를 UserAuthInfo로 변경. 서비스 메서드 시그니처를 User 객체에서 Long userId로 변경하고, CommentServiceImpl에 UserRepository 추가 및 사용자 조회 로직 포함.
좋아요 도메인
domain/like/controller/LikeController.java, domain/like/repository/LikeRepository.java, domain/like/service/LikeService.java, domain/like/service/LikeServiceImpl.java
LikeController에서 User를 UserAuthInfo로 변경. 모든 리포지토리 메서드를 User 파라미터에서 Long userId 기반으로 변경. 서비스 메서드 시그니처를 Long userId 기반으로 리팩토링하고 LikeServiceImpl에 UserRepository 추가.
게시글 도메인
domain/post/controller/PostController.java, domain/post/service/PostService.java, domain/post/service/PostServiceImpl.java
PostController에서 User를 UserAuthInfo로 변경하고 userAuthInfo.getId() 전달. 서비스 메서드 시그니처를 User 객체에서 Long userId로 변경하고, PostServiceImpl에 UserRepository 추가 및 사용자 조회 로직 포함.
사용자 도메인
domain/user/controller/UserController.java, domain/user/service/UserService.java
UserController에서 User를 UserAuthInfo로 변경하고 userAuthInfo.getId() 전달. UserService는 빈 줄 제거만 변경.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant JwtAuthenticationFilter
    participant UserCacheService
    participant Redis
    participant Database
    
    Client->>JwtAuthenticationFilter: 요청 (JWT 토큰 포함)
    JwtAuthenticationFilter->>JwtAuthenticationFilter: JWT에서 userId 추출
    JwtAuthenticationFilter->>UserCacheService: findUserAuthInfoById(userId)
    
    alt 캐시 히트
        UserCacheService->>Redis: 캐시 조회
        Redis-->>UserCacheService: UserAuthInfo 반환
    else 캐시 미스
        UserCacheService->>Database: User 엔티티 조회
        Database-->>UserCacheService: User 반환
        UserCacheService->>UserCacheService: UserAuthInfo.from(user) 변환
        UserCacheService->>Redis: UserAuthInfo 캐싱
        Redis-->>UserCacheService: 확인
    end
    
    UserCacheService-->>JwtAuthenticationFilter: UserAuthInfo
    JwtAuthenticationFilter->>JwtAuthenticationFilter: SecurityContext에 UserAuthInfo 설정
    JwtAuthenticationFilter-->>Client: 인증 완료
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Poem

🐰 사용자 정보는 캐시 속에,
빠르고 가볍게 날갯짓해요.
User에서 ID로 변신하니,
마이그레이션 완벽하게 완성!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.18% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 핵심 변경사항인 JwtAuthenticationFilter의 User 조회에 Redis 캐싱을 적용한 내용을 명확하게 요약하고 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cache/user

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.

❤️ Share

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

Copy link
Copy Markdown

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/be/sportizebe/global/jwt/JwtAuthenticationFilter.java (1)

42-53: ⚠️ Potential issue | 🔴 Critical

캐시 무효화 로직이 구현되지 않음 (Critical)

evictUserAuthInfo() 메서드는 UserCacheService에 정의되어 있지만, 사용자 정보 변경 시 실제로 호출되지 않습니다. uploadProfileImage() 등 사용자 데이터를 수정하는 메서드에서 캐시 무효화가 발생하지 않아, 변경된 정보가 캐시에 반영되지 않는 데이터 일관성 문제가 발생합니다.

해결 방법:

  • UserServiceImpl에 UserCacheService 의존성 추가
  • 사용자 정보 변경 메서드(uploadProfileImage() 등)에서 userCacheService.evictUserAuthInfo(userId) 호출
🤖 Fix all issues with AI agents
In `@src/main/java/com/be/sportizebe/domain/like/controller/LikeController.java`:
- Around line 26-31: getPostLikeStatus and getCommentLikeStatus currently assume
`@AuthenticationPrincipal` UserAuthInfo is non-null but SecurityConfig permits
unauthenticated GETs; update the method signatures to use
`@AuthenticationPrincipal`(required = false) UserAuthInfo userAuthInfo and add a
null check inside each method (getPostLikeStatus, getCommentLikeStatus) to
handle anonymous users (e.g., treat as not-liked or return appropriate response)
before calling userAuthInfo.getId(); alternatively, if you prefer enforcing
authentication, change the GET security rule for /api/likes/** to
authenticated() in SecurityConfig instead of permitAll(); pick one approach and
implement the corresponding change consistently for both getPostLikeStatus and
getCommentLikeStatus.
🧹 Nitpick comments (6)
src/main/java/com/be/sportizebe/global/cache/dto/UserAuthInfo.java (1)

18-22: serialVersionUID 누락 - 역직렬화 호환성 문제 발생 가능

Serializable 인터페이스를 구현하는 클래스에 serialVersionUID가 없으면, 클래스 구조 변경 시 캐시된 데이터의 역직렬화가 실패할 수 있습니다. Redis에 캐시된 기존 데이터와 새 클래스 버전 간 호환성 문제가 발생할 수 있습니다.

♻️ serialVersionUID 추가 제안
 `@Getter`
 `@NoArgsConstructor`
 `@AllArgsConstructor`
 public class UserAuthInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     private Long id;
     private String username;
     private String nickname;
     private Role role;
src/main/java/com/be/sportizebe/domain/comment/service/CommentServiceImpl.java (1)

37-44: 추가적인 DB 조회 발생에 대한 고려

이전에는 컨트롤러에서 인증된 User 객체를 직접 전달했지만, 이제 userId를 받아 매번 UserRepository.findById()를 호출합니다. 이는 댓글 생성 시마다 추가 DB 쿼리가 발생함을 의미합니다.

JPA 엔티티 연관관계 설정을 위해 User 객체가 필요하므로 현재 구현은 기능적으로 올바릅니다. 다만, 빈번한 댓글 생성이 예상되는 경우 성능 영향을 모니터링하는 것이 좋습니다.

src/main/java/com/be/sportizebe/domain/club/service/ClubServiceImpl.java (2)

76-78: Long과 primitive long 비교 시 equals() 또는 Objects.equals() 사용 권장

club.getLeader().getId()는 primitive long이고 userIdLong wrapper입니다. != 비교 시 auto-unboxing이 발생하는데, 일반적으로 동작하지만 Objects.equals()를 사용하는 것이 더 안전하고 명시적입니다.

♻️ 권장 수정안
-    if (club.getLeader().getId() != userId) {
+    if (!userId.equals(club.getLeader().getId())) {

100-102: 동일한 비교 패턴 - equals() 사용 권장

Line 76과 동일한 이슈입니다. 일관성을 위해 동일하게 수정하세요.

♻️ 권장 수정안
-    if (club.getLeader().getId() != userId) {
+    if (!userId.equals(club.getLeader().getId())) {
src/main/java/com/be/sportizebe/domain/post/service/PostServiceImpl.java (2)

63-65: Long 타입 비교 시 == 대신 .equals() 사용 권장

userIdLong 래퍼 타입이고, post.getUser().getId()는 primitive long을 반환합니다. 현재 코드는 auto-unboxing으로 동작하지만, userIdnull인 경우 NPE가 발생할 수 있습니다. 명시적인 비교를 위해 .equals() 사용을 권장합니다.

♻️ 권장 수정
         // 작성자 확인
-        if (post.getUser().getId() != userId) {
+        if (!userId.equals(post.getUser().getId())) {
             throw new CustomException(PostErrorCode.POST_DELETE_DENIED);
         }

78-80: 동일한 Long 타입 비교 이슈

deletePost와 동일하게 .equals() 사용을 권장합니다.

♻️ 권장 수정
         // 작성자 확인
-        if (post.getUser().getId() != userId) {
+        if (!userId.equals(post.getUser().getId())) {
             throw new CustomException(PostErrorCode.POST_UPDATE_DENIED);
         }

Comment on lines 26 to 31
public ResponseEntity<BaseResponse<LikeResponse>> togglePostLike(
@AuthenticationPrincipal User user,
@AuthenticationPrincipal UserAuthInfo userAuthInfo,
@Parameter(description = "게시글 ID") @PathVariable Long postId) {
LikeResponse response = likeService.toggleLike(user, LikeTargetType.POST, postId);
LikeResponse response = likeService.toggleLike(userAuthInfo.getId(), LikeTargetType.POST, postId);
return ResponseEntity.ok(BaseResponse.success("좋아요 처리 완료", response));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Spring Security 설정 및 LikeController 구현 확인

echo "=== LikeController.java 전체 내용 확인 ==="
cat -n src/main/java/com/be/sportizebe/domain/like/controller/LikeController.java 2>/dev/null || echo "파일을 찾을 수 없습니다"

echo -e "\n=== Spring Security 설정 파일 검색 ==="
find . -type f -name "*Security*Config*.java" | head -20

echo -e "\n=== SecurityFilterChain 또는 WebSecurityConfigurerAdapter 검색 ==="
rg -l "SecurityFilterChain|WebSecurityConfigurerAdapter|WebSecurityConfigurer" --type=java 2>/dev/null | head -20

Repository: Sportize/be

Length of output: 4045


🏁 Script executed:

#!/bin/bash
# 보안 설정에서 likes 엔드포인트 인증 요구 여부 확인

echo "=== /api/likes 관련 보안 설정 검색 ==="
rg -n -B 3 -A 3 "likes|antMatchers|requestMatchers" --type=java | grep -A 5 -B 5 "likes" 2>/dev/null || echo "결과 없음"

echo -e "\n=== `@AuthenticationPrincipal` 사용 패턴 검색 ==="
rg -n "@AuthenticationPrincipal" --type=java -A 2

Repository: Sportize/be

Length of output: 8358


🏁 Script executed:

#!/bin/bash
# SecurityConfig.java 전체 내용 확인

cat -n src/main/java/com/be/sportizebe/global/security/SecurityConfig.java

Repository: Sportize/be

Length of output: 2960


🏁 Script executed:

#!/bin/bash
# 모든 requestMatchers/antMatchers 패턴 확인

echo "=== SecurityConfig에서 requestMatchers 패턴 검색 ==="
rg -n "requestMatchers|antMatchers|permitAll|authenticated" \
  src/main/java/com/be/sportizebe/global/security/SecurityConfig.java

Repository: Sportize/be

Length of output: 589


getPostLikeStatus, getCommentLikeStatususerAuthInfo null 체크 필수

현재 SecurityConfig에서 GET 요청(/api/**)은 permitAll()로 설정되어 있어 인증 없이 접근 가능하지만, 두 GET 엔드포인트에서 @AuthenticationPrincipal UserAuthInfo userAuthInfo를 인증 필수로 사용하고 있습니다. 인증되지 않은 사용자의 요청에서 userAuthInfonull이 되어 userAuthInfo.getId() 호출 시 NullPointerException이 발생합니다.

다음 중 하나의 방식으로 수정이 필요합니다:

  • userAuthInfo 매개변수에 required=false를 추가하고 null 체크 추가
  • SecurityConfig에서 /api/likes/** GET 요청도 authenticated() 요구로 변경
  • 인증되지 않은 사용자를 위한 별도의 처리 로직 구현

참고: POST 엔드포인트(togglePostLike, toggleCommentLike)는 anyRequest().authenticated()에 해당하여 인증이 강제되므로 현재는 문제없습니다.

🤖 Prompt for AI Agents
In `@src/main/java/com/be/sportizebe/domain/like/controller/LikeController.java`
around lines 26 - 31, getPostLikeStatus and getCommentLikeStatus currently
assume `@AuthenticationPrincipal` UserAuthInfo is non-null but SecurityConfig
permits unauthenticated GETs; update the method signatures to use
`@AuthenticationPrincipal`(required = false) UserAuthInfo userAuthInfo and add a
null check inside each method (getPostLikeStatus, getCommentLikeStatus) to
handle anonymous users (e.g., treat as not-liked or return appropriate response)
before calling userAuthInfo.getId(); alternatively, if you prefer enforcing
authentication, change the GET security rule for /api/likes/** to
authenticated() in SecurityConfig instead of permitAll(); pick one approach and
implement the corresponding change consistently for both getPostLikeStatus and
getCommentLikeStatus.

@imjuyongp imjuyongp changed the title ♻️ Recycle: JwtAuthenticationFilter User 조회 Redis 캐싱 적용 ♻️ Refactor: JwtAuthenticationFilter User 조회 Redis 캐싱 적용 Feb 3, 2026
@angoroa angoroa merged commit e345306 into develop Feb 3, 2026
1 check passed
@imjuyongp imjuyongp deleted the cache/user branch February 3, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants