Conversation
작업 요약JWT 기반 인증 및 보안 설정을 구현하는 변경사항입니다. 필요한 라이브러리 의존성을 추가하고, JWT 토큰 검증 및 필터링을 위한 컴포넌트, Spring Security 설정, 새로운 엔티티 타입(역할, 작업 공간), 그리고 관련 예외 처리 및 오류 상태 코드를 도입했습니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant Client
participant JwtAuthFilter
participant JwtProvider
participant SecurityContext
participant CustomAuthEntryPoint
Client->>JwtAuthFilter: HTTP 요청
JwtAuthFilter->>JwtProvider: 토큰 추출 (쿠키/헤더)
alt 토큰 없음
JwtProvider-->>JwtAuthFilter: null/blank
JwtAuthFilter->>Client: 요청 계속 진행
else 토큰 존재
JwtProvider->>JwtProvider: JWT 파싱 및 검증
alt 토큰 유효함
JwtProvider-->>JwtAuthFilter: Claims
JwtAuthFilter->>JwtAuthFilter: 토큰 타입 확인
alt Service 토큰
JwtAuthFilter->>SecurityContext: SVC_AUTH 권한 설정
else Refresh 토큰
JwtAuthFilter->>CustomAuthEntryPoint: NOT_ACCESS_TOKEN 오류
else 일반 토큰
JwtAuthFilter->>SecurityContext: 사용자 정보 및 권한 설정
end
JwtAuthFilter->>Client: 요청 계속 진행
else 토큰 유효하지 않음
JwtProvider-->>JwtAuthFilter: 예외 발생
JwtAuthFilter->>CustomAuthEntryPoint: CustomAuthenticationException
CustomAuthEntryPoint->>Client: 401/400 오류 응답
end
end
예상 코드 리뷰 소요 시간🎯 3 (중간) | ⏱️ ~25분 추가 검토 필요 영역:
관련된 PR
제안된 레이블
제안된 리뷰어
시 🐰
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ 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 |
| public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthFilter jwtAuthFilter, CustomAuthEntryPoint customAuthEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) throws Exception { | ||
| http | ||
| // CodeQL [java/spring-disabled-csrf-protection]: suppress - Stateless JWT API라 CSRF 불필요 | ||
| .csrf(csrf -> csrf.disable()) |
Check failure
Code scanning / CodeQL
Disabled Spring CSRF protection High
Copilot Autofix
AI 7 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
src/main/java/com/sampoom/purchase/common/config/jwt/JwtProvider.java (3)
24-36: 생성자의 예외 처리를 단순화할 수 있습니다.Lines 30-31에서
BadRequestException을 다시 던지는 것은 불필요합니다.loadPublicKey메서드에서 이미BadRequestException을 던지고 있으므로, catch 블록을 단순화할 수 있습니다.public JwtProvider(@Value("${jwt.public-key-base64}") String publicKeyBase64) { if (publicKeyBase64 == null || publicKeyBase64.isBlank()) { throw new BadRequestException(ErrorStatus.INVALID_PUBLIC_KEY); } try { this.publicKey = loadPublicKey(publicKeyBase64); - } catch (BadRequestException e) { - throw e; } catch (Exception e) { throw new BadRequestException(ErrorStatus.INVALID_PUBLIC_KEY); } }
38-54: 예외 처리를 단순화할 수 있습니다.Lines 49-51에서
BadRequestException을 다시 던지는 것은 불필요합니다. 이미 던져진 예외를 그대로 전파하면 됩니다.private PublicKey loadPublicKey(String base64) throws Exception { try { byte[] keyBytes = Base64.getDecoder().decode(base64); PublicKey key = KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(keyBytes)); if (key instanceof RSAPublicKey rsaKey) { if (rsaKey.getModulus().bitLength() < 2048) { throw new BadRequestException(ErrorStatus.SHORT_PUBLIC_KEY); } } return key; - } catch (BadRequestException e) { - throw e; } catch (Exception e) { throw new BadRequestException(ErrorStatus.INVALID_PUBLIC_KEY); } }
73-88: 쿠키 이름을 상수로 추출하는 것을 고려하세요.Line 77에서 "ACCESS_TOKEN" 문자열이 하드코딩되어 있습니다. 이를 클래스 상수로 추출하면 유지보수성과 일관성이 향상됩니다.
+ private static final String ACCESS_TOKEN_COOKIE_NAME = "ACCESS_TOKEN"; + public String resolveAccessToken(HttpServletRequest request) { // 쿠키에서 ACCESS_TOKEN 찾기 if (request.getCookies() != null) { for (Cookie cookie : request.getCookies()) { - if ("ACCESS_TOKEN".equals(cookie.getName())) { + if (ACCESS_TOKEN_COOKIE_NAME.equals(cookie.getName())) { return cookie.getValue(); } } } // Bearer 방식일 때 String header = request.getHeader("Authorization"); if (header == null) return null; if (!header.startsWith("Bearer ")) throw new UnauthorizedException(ErrorStatus.INVALID_TOKEN); return header.substring(7); // "Bearer " 제거 }src/main/java/com/sampoom/purchase/common/config/security/CustomAuthEntryPoint.java (1)
20-44: 공통 ObjectMapper 빈을 주입해 주세요전역에서 커스터마이징된 ObjectMapper(예: JavaTimeModule, naming 전략 등)를 사용하고 있는데, 여기서
new ObjectMapper()를 직접 생성하면 해당 설정이 적용되지 않아 보안 응답 직렬화가 깨질 수 있습니다. 스프링 컨테이너에 등록된 ObjectMapper 빈을 주입받도록 변경하는 편이 안전합니다.@@ -import com.fasterxml.jackson.databind.ObjectMapper; -import com.sampoom.purchase.common.exception.CustomAuthenticationException; -import com.sampoom.purchase.common.response.ApiResponse; -import com.sampoom.purchase.common.response.ErrorStatus; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sampoom.purchase.common.exception.CustomAuthenticationException; +import com.sampoom.purchase.common.response.ApiResponse; +import com.sampoom.purchase.common.response.ErrorStatus; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @@ -@Slf4j -@Component -public class CustomAuthEntryPoint implements AuthenticationEntryPoint { - - private final ObjectMapper objectMapper = new ObjectMapper(); +@Slf4j +@Component +@RequiredArgsConstructor +public class CustomAuthEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper;src/main/java/com/sampoom/purchase/common/config/security/CustomAccessDeniedHandler.java (1)
20-37: 전역 ObjectMapper 설정을 재사용해 주세요이곳에서도
new ObjectMapper()로 별도 인스턴스를 만들면 글로벌 모듈/설정이 반영되지 않아 직렬화 형태가 다른 API와 달라지거나 실패할 수 있습니다. 빈으로 등록된 ObjectMapper를 주입해서 동일한 설정을 적용해 주세요.@@ -import com.fasterxml.jackson.databind.ObjectMapper; -import com.sampoom.purchase.common.response.ApiResponse; -import com.sampoom.purchase.common.response.ErrorStatus; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sampoom.purchase.common.response.ApiResponse; +import com.sampoom.purchase.common.response.ErrorStatus; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @@ -@Slf4j -@Component -public class CustomAccessDeniedHandler implements AccessDeniedHandler { - - private final ObjectMapper objectMapper = new ObjectMapper(); +@Slf4j +@Component +@RequiredArgsConstructor +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + private final ObjectMapper objectMapper;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
build.gradle(1 hunks)src/main/java/com/sampoom/purchase/api/purchase/entity/PurchaseOrder.java(1 hunks)src/main/java/com/sampoom/purchase/common/config/jwt/JwtAuthFilter.java(1 hunks)src/main/java/com/sampoom/purchase/common/config/jwt/JwtProvider.java(1 hunks)src/main/java/com/sampoom/purchase/common/config/security/CustomAccessDeniedHandler.java(1 hunks)src/main/java/com/sampoom/purchase/common/config/security/CustomAuthEntryPoint.java(1 hunks)src/main/java/com/sampoom/purchase/common/config/security/RoleHierarchyConfig.java(1 hunks)src/main/java/com/sampoom/purchase/common/config/security/SecurityConfig.java(1 hunks)src/main/java/com/sampoom/purchase/common/entity/BaseTimeEntity.java(1 hunks)src/main/java/com/sampoom/purchase/common/entity/Role.java(1 hunks)src/main/java/com/sampoom/purchase/common/entity/SoftDeleteEntity.java(1 hunks)src/main/java/com/sampoom/purchase/common/entity/Workspace.java(1 hunks)src/main/java/com/sampoom/purchase/common/exception/CustomAuthenticationException.java(1 hunks)src/main/java/com/sampoom/purchase/common/response/ErrorStatus.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/main/java/com/sampoom/purchase/common/config/jwt/JwtProvider.java (3)
src/main/java/com/sampoom/purchase/common/exception/BadRequestException.java (1)
BadRequestException(6-19)src/main/java/com/sampoom/purchase/common/exception/CustomAuthenticationException.java (1)
CustomAuthenticationException(6-17)src/main/java/com/sampoom/purchase/common/exception/UnauthorizedException.java (1)
UnauthorizedException(6-18)
src/main/java/com/sampoom/purchase/common/config/security/RoleHierarchyConfig.java (1)
src/main/java/com/sampoom/purchase/common/config/security/SecurityConfig.java (1)
Configuration(20-84)
src/main/java/com/sampoom/purchase/common/config/jwt/JwtAuthFilter.java (3)
src/main/java/com/sampoom/purchase/common/exception/CustomAuthenticationException.java (1)
CustomAuthenticationException(6-17)src/main/java/com/sampoom/purchase/common/config/security/CustomAuthEntryPoint.java (1)
Slf4j(16-47)src/main/java/com/sampoom/purchase/common/config/jwt/JwtProvider.java (1)
Component(19-89)
src/main/java/com/sampoom/purchase/common/config/security/CustomAccessDeniedHandler.java (3)
src/main/java/com/sampoom/purchase/common/config/jwt/JwtAuthFilter.java (1)
Slf4j(26-135)src/main/java/com/sampoom/purchase/common/config/security/CustomAuthEntryPoint.java (1)
Slf4j(16-47)src/main/java/com/sampoom/purchase/common/config/jwt/JwtProvider.java (1)
Component(19-89)
src/main/java/com/sampoom/purchase/common/config/security/SecurityConfig.java (1)
src/main/java/com/sampoom/purchase/common/config/security/RoleHierarchyConfig.java (1)
Configuration(8-29)
src/main/java/com/sampoom/purchase/common/config/security/CustomAuthEntryPoint.java (4)
src/main/java/com/sampoom/purchase/common/exception/CustomAuthenticationException.java (1)
CustomAuthenticationException(6-17)src/main/java/com/sampoom/purchase/common/config/jwt/JwtAuthFilter.java (1)
Slf4j(26-135)src/main/java/com/sampoom/purchase/common/config/security/CustomAccessDeniedHandler.java (1)
Slf4j(16-41)src/main/java/com/sampoom/purchase/common/config/jwt/JwtProvider.java (1)
Component(19-89)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (6)
src/main/java/com/sampoom/purchase/common/entity/Workspace.java (1)
3-13: LGTM!워크스페이스 타입을 명확하게 정의한 enum입니다. 주석으로 각 타입의 의미가 잘 설명되어 있습니다.
src/main/java/com/sampoom/purchase/common/entity/BaseTimeEntity.java (1)
1-1: 패키지 명 오타 수정 확인.
entitiy에서entity로 올바르게 수정되었습니다.src/main/java/com/sampoom/purchase/common/response/ErrorStatus.java (1)
27-50: LGTM!JWT 인증 및 권한 관리를 위한 에러 상태 코드들이 잘 정의되어 있습니다. HTTP 상태 코드별로 논리적으로 그룹화되어 있고, 한글 메시지도 명확합니다.
src/main/java/com/sampoom/purchase/common/entity/SoftDeleteEntity.java (1)
1-1: 패키지 명 오타 수정 확인.
entitiy에서entity로 올바르게 수정되었습니다.src/main/java/com/sampoom/purchase/api/purchase/entity/PurchaseOrder.java (1)
3-3: import 경로 수정 확인.SoftDeleteEntity의 패키지 명 수정에 따라 import 경로가 올바르게 업데이트되었습니다.
src/main/java/com/sampoom/purchase/common/entity/Role.java (1)
3-6: LGTM!역할(Role)을 명확하게 정의한 enum입니다. 타입 안전성을 제공하며 간결합니다.
📝 Summary
🙏 Question & PR point
📬 Reference
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정