Conversation
개요JWT 기반 인증 인프라스트럭처를 도입하고 보안 구성을 추가했습니다. 빌드 의존성에 JWT 라이브러리를 추가하고, 패키지명 오타를 수정하며, JWT 검증 필터, 제공자, 보안 설정 및 커스텀 예외 처리 클래스를 구현했습니다. 변경사항
시퀀스 다이어그램sequenceDiagram
actor Client
participant Filter as JwtAuthFilter
participant Provider as JwtProvider
participant Auth as SecurityContext
participant EntryPoint as CustomAuthEntryPoint
Client->>Filter: HTTP 요청 (Authorization 헤더 or 쿠키)
Filter->>Provider: 접근 토큰 해석
alt 토큰 없음/공백
Provider-->>Filter: 토큰 계속 진행
Filter->>Auth: 인증 설정 없음
else 토큰 유효
Provider->>Provider: RSA 공개키로 JWT 파싱
Provider-->>Filter: Claims 반환
alt 서비스 토큰
Filter->>Auth: SVC_ 권한 설정
else 사용자 토큰
Filter->>Auth: ROLE_*, WORKSPACE_* 권한 설정
else 새로고침 토큰
Filter->>EntryPoint: NOT_ACCESS_TOKEN 오류
end
else 토큰 만료/유효하지 않음
Provider-->>EntryPoint: CustomAuthenticationException 발생
EntryPoint->>Client: JSON 오류 응답
end
Filter->>Client: 다음 필터/엔드포인트 진행
예상 코드 리뷰 소요 시간🎯 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 4 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: 6
🧹 Nitpick comments (6)
src/main/java/com/sampoom/factory/common/exception/CustomAuthenticationException.java (1)
6-16: 인증 예외 처리 구현이 적절합니다Spring Security의
AuthenticationException을 올바르게 확장하고ErrorStatus를 캡슐화하여 일관된 에러 응답을 제공합니다.프로젝트의 다른 클래스들과 일관성을 위해 Lombok의
@Getter어노테이션 사용을 고려할 수 있습니다:+import lombok.Getter; + +@Getter public class CustomAuthenticationException extends AuthenticationException { private final ErrorStatus errorStatus; public CustomAuthenticationException(ErrorStatus errorStatus) { super(errorStatus.getMessage()); this.errorStatus = errorStatus; } - - public ErrorStatus getErrorStatus() { - return errorStatus; - } }src/main/java/com/sampoom/factory/common/config/security/SecurityConfig.java (3)
31-31: 권한 문자열을 상수로 추출하는 것을 권장합니다.
"SVC_AUTH"문자열 리터럴을 직접 사용하고 있습니다. 오타 방지 및 유지보수성 향상을 위해 상수로 추출하는 것이 좋습니다.다음과 같이 상수로 정의하세요:
+ private static final String AUTHORITY_SVC_AUTH = "SVC_AUTH"; + @Bean public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthFilter jwtAuthFilter, CustomAuthEntryPoint customAuthEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth // Feign용 POST만 허용 - .requestMatchers(HttpMethod.POST, "/internal/**").hasAuthority("SVC_AUTH") + .requestMatchers(HttpMethod.POST, "/internal/**").hasAuthority(AUTHORITY_SVC_AUTH)
47-60: 주석 처리된 CORS 설정을 제거하거나 문서화하세요.14줄에 달하는 CORS 설정이 주석 처리되어 있습니다. 향후 사용 예정이라면 별도 문서화하고, 그렇지 않다면 제거하여 코드 가독성을 높이는 것이 좋습니다.
사용 예정이 아니라면 다음과 같이 제거하세요:
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - // CORS 비활성화(배포 시) -// .cors(cors -> cors.configurationSource(request -> { -// var corsConfig = new CorsConfiguration(); -// corsConfig.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); -// corsConfig.setAllowedOrigins(List.of("https://sampoom.store" -// ,"https://samsam.autos" -// ,"https://sampoom-management-frontend.vercel.app" -// ,"http://localhost:8082" -// ,"http://localhost:3000" -// )); -// corsConfig.setAllowCredentials(true); -// corsConfig.setExposedHeaders(List.of("Authorization")); -// corsConfig.setAllowedHeaders(List.of("Content-Type", "Authorization", "X-Client-Type")); -// return corsConfig; -// })) .addFilterAfter(jwtAuthFilter, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.class)
77-83: 주석을 더 명확하게 개선할 수 있습니다.JWT 기반 인증만 사용하므로 기본 UserDetailsService를 비활성화하는 것은 적절합니다. 다만, 주석이 더 명확하면 좋겠습니다.
주석을 다음과 같이 개선할 수 있습니다:
- // Spring Security 자동 보안 설정 해제 + // JWT 기반 인증을 사용하므로 기본 UserDetailsService 비활성화 + // 실제 사용자 인증은 JwtAuthFilter에서 처리됨 @Bean public UserDetailsService userDetailsService() {src/main/java/com/sampoom/factory/common/response/ErrorStatus.java (2)
27-33: 에러 코드 체계의 일관성을 검토하세요.새로 추가된 에러 코드들이 일관되지 않은 번호 체계를 사용하고 있습니다:
124xx범위: JWT 관련 (12400, 12401, 12404, 12405, 12406)114xx범위: 입력/권한 관련 (11401, 11402)또한, 다음 에러들은 목적이 유사해 보입니다:
NULL_BLANK_TOKEN(12400): "토큰 값은 Null 또는 공백이면 안됩니다"BLANK_TOKEN_ROLE(12404): "토큰 내 권한 정보가 공백입니다"NULL_TOKEN_ROLE(12405): "토큰 내 권한 정보가 NULL입니다"에러 코드 체계에 대한 문서화가 있다면 괜찮지만, 그렇지 않다면 일관된 번호 체계를 사용하는 것을 권장합니다.
50-50: ACCESS_DENIED 에러 코드가 다른 범위를 사용합니다.
ACCESS_DENIED가11430코드를 사용하는데, 다른 JWT 관련 FORBIDDEN 에러들과 일관성을 위해124xx또는403xx범위를 사용하는 것을 고려하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
build.gradle(1 hunks)src/main/java/com/sampoom/factory/api/material/entity/MaterialOrder.java(1 hunks)src/main/java/com/sampoom/factory/api/mps/entity/Mps.java(1 hunks)src/main/java/com/sampoom/factory/api/mps/entity/MpsPlan.java(1 hunks)src/main/java/com/sampoom/factory/api/part/entity/PartOrder.java(1 hunks)src/main/java/com/sampoom/factory/common/config/jwt/JwtAuthFilter.java(1 hunks)src/main/java/com/sampoom/factory/common/config/jwt/JwtProvider.java(1 hunks)src/main/java/com/sampoom/factory/common/config/security/CustomAccessDeniedHandler.java(1 hunks)src/main/java/com/sampoom/factory/common/config/security/CustomAuthEntryPoint.java(1 hunks)src/main/java/com/sampoom/factory/common/config/security/RoleHierarchyConfig.java(1 hunks)src/main/java/com/sampoom/factory/common/config/security/SecurityConfig.java(1 hunks)src/main/java/com/sampoom/factory/common/entity/BaseTimeEntity.java(1 hunks)src/main/java/com/sampoom/factory/common/entity/Role.java(1 hunks)src/main/java/com/sampoom/factory/common/entity/SoftDeleteEntity.java(1 hunks)src/main/java/com/sampoom/factory/common/entity/Workspace.java(1 hunks)src/main/java/com/sampoom/factory/common/exception/CustomAuthenticationException.java(1 hunks)src/main/java/com/sampoom/factory/common/response/ErrorStatus.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-10T02:39:29.568Z
Learnt from: taemin3
Repo: 33-Auto/Sampoom-Management-Backend-Factory PR: 34
File: src/main/java/com/sampoom/factory/api/part/service/PartOrderService.java:668-682
Timestamp: 2025-11-10T02:39:29.568Z
Learning: In the Sampoom Factory project, PartOrder entities always contain only one item per order. The `createPartOrdersSeparately` method in PartOrderService creates individual orders for each item. Therefore, N+1 query concerns in the toResponseDto and toProductionPlanResponseDto methods are not applicable since N is always 1.
Applied to files:
src/main/java/com/sampoom/factory/api/part/entity/PartOrder.java
🧬 Code graph analysis (6)
src/main/java/com/sampoom/factory/common/config/jwt/JwtProvider.java (3)
src/main/java/com/sampoom/factory/common/exception/BadRequestException.java (1)
BadRequestException(6-19)src/main/java/com/sampoom/factory/common/exception/CustomAuthenticationException.java (1)
CustomAuthenticationException(6-17)src/main/java/com/sampoom/factory/common/exception/UnauthorizedException.java (1)
UnauthorizedException(6-18)
src/main/java/com/sampoom/factory/common/config/jwt/JwtAuthFilter.java (3)
src/main/java/com/sampoom/factory/common/exception/CustomAuthenticationException.java (1)
CustomAuthenticationException(6-17)src/main/java/com/sampoom/factory/common/config/security/CustomAuthEntryPoint.java (1)
Slf4j(16-47)src/main/java/com/sampoom/factory/common/config/jwt/JwtProvider.java (1)
Component(19-89)
src/main/java/com/sampoom/factory/common/config/security/RoleHierarchyConfig.java (1)
src/main/java/com/sampoom/factory/common/config/security/SecurityConfig.java (1)
Configuration(20-84)
src/main/java/com/sampoom/factory/common/config/security/CustomAccessDeniedHandler.java (2)
src/main/java/com/sampoom/factory/common/config/jwt/JwtAuthFilter.java (1)
Slf4j(26-135)src/main/java/com/sampoom/factory/common/config/security/CustomAuthEntryPoint.java (1)
Slf4j(16-47)
src/main/java/com/sampoom/factory/common/config/security/CustomAuthEntryPoint.java (4)
src/main/java/com/sampoom/factory/common/exception/CustomAuthenticationException.java (1)
CustomAuthenticationException(6-17)src/main/java/com/sampoom/factory/common/config/jwt/JwtAuthFilter.java (1)
Slf4j(26-135)src/main/java/com/sampoom/factory/common/config/security/CustomAccessDeniedHandler.java (1)
Slf4j(16-41)src/main/java/com/sampoom/factory/common/config/jwt/JwtProvider.java (1)
Component(19-89)
src/main/java/com/sampoom/factory/common/config/security/SecurityConfig.java (1)
src/main/java/com/sampoom/factory/common/config/security/RoleHierarchyConfig.java (1)
Configuration(8-29)
⏰ 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 (13)
build.gradle (1)
45-50: JWT 및 Spring Security 의존성 구성이 적절함의존성 스코프 설정이 올바릅니다:
jjwt-api: compile-time에 필요한 공개 인터페이스jjwt-impl,jjwt-jackson: runtime-only 구현체spring-boot-starter-security: 인증/인가 처리를 위한 필수 라이브러리이러한 구성은 JWT 인증 구현의 표준 패턴입니다.
src/main/java/com/sampoom/factory/common/entity/BaseTimeEntity.java (1)
1-1: 패키지 경로 오타 수정 완료!
entitiy→entity로 올바르게 수정되었습니다.src/main/java/com/sampoom/factory/api/part/entity/PartOrder.java (1)
3-3: LGTM!import 경로가 올바르게 수정되었습니다.
src/main/java/com/sampoom/factory/api/material/entity/MaterialOrder.java (1)
3-3: LGTM!import 경로가 올바르게 수정되었습니다.
src/main/java/com/sampoom/factory/api/mps/entity/MpsPlan.java (1)
3-3: LGTM!import 경로가 올바르게 수정되었습니다.
src/main/java/com/sampoom/factory/common/entity/SoftDeleteEntity.java (1)
1-1: 패키지 경로 오타 수정 완료!
entitiy→entity로 올바르게 수정되었습니다.src/main/java/com/sampoom/factory/common/entity/Role.java (1)
3-6: 기본 역할 정의 확인
ADMIN과USER두 가지 기본 역할이 정의되어 있습니다.RoleHierarchyConfig에 정의된 중간 레벨 역할들(ROLE_MD,ROLE_SALES등)이 다른 방식으로 관리되는지 확인이 필요합니다. (이미RoleHierarchyConfig.java에서 검증 요청됨)src/main/java/com/sampoom/factory/common/config/security/RoleHierarchyConfig.java (1)
12-27: 역할 계층 구조와 Role enum 간의 불일치 확인 필요역할 계층 구조에서
ROLE_MD,ROLE_SALES,ROLE_INVENTORY,ROLE_PRODUCTION,ROLE_PURCHASE,ROLE_HR,ROLE_AGENCY를 정의하고 있지만,Role.javaenum에는ADMIN과USER만 정의되어 있습니다.중간 레벨 역할들이 다른 곳(예: Workspace enum, 외부 시스템, 또는 동적 권한 부여)에서 관리되는 것인지 확인이 필요합니다.
다음 스크립트로 중간 레벨 역할의 정의 위치를 확인하세요:
src/main/java/com/sampoom/factory/common/config/security/SecurityConfig.java (3)
20-23: LGTM!Spring Security 설정 클래스 선언이 적절합니다.
@EnableMethodSecurity(prePostEnabled = true)를 통해 메서드 레벨 보안도 활성화되어 있습니다.
71-75: LGTM!BCrypt를 사용한 안전한 비밀번호 암호화 설정입니다.
32-38: 공개 엔드포인트의 보안 영향을 확인하세요.
/invitations엔드포인트가 인증 없이 접근 가능하도록 설정되어 있습니다. 이 엔드포인트가 민감한 정보를 노출하거나 악용 가능한 기능을 제공하지 않는지 확인이 필요합니다.다음 스크립트로
/invitations엔드포인트의 구현을 확인하세요:src/main/java/com/sampoom/factory/common/config/jwt/JwtProvider.java (1)
56-71: 예외 타입의 일관성을 검토하세요.null/blank 토큰 검증(line 57-59)은
BadRequestException을 던지지만, 토큰 파싱 오류(line 65, 69)는CustomAuthenticationException을 던집니다. 이것이 의도적인 설계인지 확인이 필요합니다.
BadRequestException: 입력 유효성 검증 실패CustomAuthenticationException: 인증 실패만약 null/blank 토큰도 인증 실패로 간주한다면, 일관성을 위해
CustomAuthenticationException을 사용하는 것을 고려하세요.src/main/java/com/sampoom/factory/common/response/ErrorStatus.java (1)
41-46: LGTM!UNAUTHORIZED 에러 상태들이 명확하게 정의되어 있습니다. 각 에러 메시지가 구체적이어서 디버깅에 유용합니다.
📝 Summary
🙏 Question & PR point
📬 Reference
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
개선사항