♻️ Refactor: JwtAuthenticationFilter User 조회 Redis 캐싱 적용#44
Conversation
Walkthrough이 PR은 사용자 인증 정보를 캐싱 가능한 DTO로 마이그레이션하고, 여러 도메인의 컨트롤러와 서비스에서 User 엔티티 직접 참조를 제거하여 Long userId 기반 접근으로 변경합니다. JWT 필터는 UserCacheService를 통해 캐시된 UserAuthInfo를 사용하도록 업데이트되었습니다. Changes
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: 인증 완료
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 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: 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과 primitivelong비교 시equals()또는Objects.equals()사용 권장
club.getLeader().getId()는 primitivelong이고userId는Longwrapper입니다.!=비교 시 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()사용 권장
userId는Long래퍼 타입이고,post.getUser().getId()는 primitivelong을 반환합니다. 현재 코드는 auto-unboxing으로 동작하지만,userId가null인 경우 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); }
| 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)); | ||
| } |
There was a problem hiding this comment.
🧩 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 -20Repository: 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 2Repository: Sportize/be
Length of output: 8358
🏁 Script executed:
#!/bin/bash
# SecurityConfig.java 전체 내용 확인
cat -n src/main/java/com/be/sportizebe/global/security/SecurityConfig.javaRepository: 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.javaRepository: Sportize/be
Length of output: 589
getPostLikeStatus, getCommentLikeStatus의 userAuthInfo null 체크 필수
현재 SecurityConfig에서 GET 요청(/api/**)은 permitAll()로 설정되어 있어 인증 없이 접근 가능하지만, 두 GET 엔드포인트에서 @AuthenticationPrincipal UserAuthInfo userAuthInfo를 인증 필수로 사용하고 있습니다. 인증되지 않은 사용자의 요청에서 userAuthInfo는 null이 되어 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.
#️⃣ Issue Number
📝 요약(Summary)
구현 요약
새로 생성된 파일
수정된 파일
캐시 설정
인증 필터
컨트롤러 (6개)
서비스 인터페이스/구현체 (10개)
리포지토리 (2개)
캐시 효과
변경 전: 인증된 API 요청마다 userRepository.findById() 호출 (DB 조회)
변경 후: Redis에서 UserAuthInfo 조회 (5분 TTL)
🛠️ PR 유형
어떤 변경 사항이 있나요?
📸스크린샷 (선택)
💬 공유사항 to 리뷰어
추후 사용자 정보 변경 로직 적용 필요 (캐시 무효화 로직 적용)
사용자 정보 변경된 경우 캐시 정보 갱신
// UserCacheService.java
@CacheEvict(cacheNames = "userAuthInfo", key = "#userId")
public void evictUserAuthInfo(Long userId) {
log.debug("사용자 캐시 무효화: userId={}", userId);
}
사용자 정보 변경되는 시점
✅ PR Checklist
PR이 다음 요구 사항을 충족하는지 확인하세요.
Summary by CodeRabbit
릴리스 노트
성능 개선
보안 강화