Skip to content

Comments

[FEAT] jwt 인증 추가#37

Merged
taemin3 merged 1 commit intomainfrom
SPM-505
Nov 11, 2025
Merged

[FEAT] jwt 인증 추가#37
taemin3 merged 1 commit intomainfrom
SPM-505

Conversation

@taemin3
Copy link
Contributor

@taemin3 taemin3 commented Nov 11, 2025

📝 Summary

  • [FEAT] jwt 인증 추가

🙏 Question & PR point

📬 Reference

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • JWT 기반 인증 시스템 도입 - 보안 강화 및 토큰 기반 접근 제어 구현
    • 역할 기반 접근 제어(RBAC) 추가 - 관리자, 사용자 등 다층 권한 관리 지원
    • 부서별 워크스페이스 구분 - MD, 영업, 재고, 생산, 구매, HR, 에이전시 역할 분류
  • 버그 수정

    • 패키지 명명 오류 수정 (entitiy → entity)
  • 개선사항

    • 향상된 보안 필터 및 인증 예외 처리 추가

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

개요

JWT 기반 인증 인프라스트럭처를 도입하고 보안 구성을 추가했습니다. 빌드 의존성에 JWT 라이브러리를 추가하고, 패키지명 오타를 수정하며, JWT 검증 필터, 제공자, 보안 설정 및 커스텀 예외 처리 클래스를 구현했습니다.

변경사항

코호트 / 파일(들) 변경 요약
의존성 및 빌드 설정
build.gradle
JWT 라이브러리(jjwt-api, jjwt-impl, jjwt-jackson) 및 Spring Security 스타터 의존성 추가
엔티티 패키지 오타 수정
src/main/java/com/sampoom/factory/api/material/entity/MaterialOrder.java, src/main/java/com/sampoom/factory/api/mps/entity/Mps.java, src/main/java/com/sampoom/factory/api/mps/entity/MpsPlan.java, src/main/java/com/sampoom/factory/api/part/entity/PartOrder.java
임포트 경로 오타 수정: entitiyentity
공통 엔티티 패키지 수정
src/main/java/com/sampoom/factory/common/entity/BaseTimeEntity.java, src/main/java/com/sampoom/factory/common/entity/SoftDeleteEntity.java
패키지 선언 오타 수정: com.sampoom.factory.common.entitiycom.sampoom.factory.common.entity
새로운 열거형 정의
src/main/java/com/sampoom/factory/common/entity/Role.java, src/main/java/com/sampoom/factory/common/entity/Workspace.java
Role 열거형(ADMIN, USER) 및 Workspace 열거형(MD, SALES, INVENTORY, PRODUCTION, PURCHASE, HR, AGENCY) 추가
JWT 제공자 및 필터
src/main/java/com/sampoom/factory/common/config/jwt/JwtProvider.java, src/main/java/com/sampoom/factory/common/config/jwt/JwtAuthFilter.java
JWT 토큰 파싱, 접근 토큰 해석, 필터 체인 처리 및 인증 유형별 처리 로직(서비스/새로고침/사용자 토큰) 구현
보안 설정 및 예외 처리
src/main/java/com/sampoom/factory/common/config/security/SecurityConfig.java, src/main/java/com/sampoom/factory/common/config/security/CustomAuthEntryPoint.java, src/main/java/com/sampoom/factory/common/config/security/CustomAccessDeniedHandler.java, src/main/java/com/sampoom/factory/common/config/security/RoleHierarchyConfig.java
상태 비저장 JWT 기반 보안 필터 체인, 역할 계층 구조, 커스텀 인증 진입점 및 접근 거부 핸들러 구성
커스텀 예외 및 오류 상태
src/main/java/com/sampoom/factory/common/exception/CustomAuthenticationException.java, src/main/java/com/sampoom/factory/common/response/ErrorStatus.java
CustomAuthenticationException 클래스 및 13개의 새로운 오류 상태 상수 추가(공개 키/토큰 검증 관련)

시퀀스 다이어그램

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: 다음 필터/엔드포인트 진행
Loading

예상 코드 리뷰 소요 시간

🎯 3 (중간) | ⏱️ ~25분

  • 주의 필요 영역:
    • JwtAuthFilter.doFilterInternal(): 토큰 유형별 처리 로직 및 예외 처리 흐름 검증 필요
    • JwtProvider.parse(): RSA 공개키 검증 및 JWT 파싱 오류 처리 완전성 확인
    • SecurityConfig.filterChain(): 요청 경로별 권한 규칙(예: /internal/**SVC_AUTH 필요) 설정 검토
    • ErrorStatus 새로운 상수들: 오류 메시지 및 HTTP 상태 코드 정확성 확인
    • 여러 파일의 패키지/임포트 오타 수정: 모든 참조가 올바르게 업데이트되었는지 검증

관련된 가능성 있는 PR

제안 리뷰어

  • CHOOSLA
  • Lee-Jong-Jin
  • vivivim
  • yangjiseonn
  • Sangyoon98

🐰 JWT의 문을 활짝 열어,
보안의 숲을 지나며,
역할과 권한이 춤을 추고,
토큰이 반짝반짝 빛나네.
이제 우리는 안전한 곳으로 ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '[FEAT] jwt 인증 추가'는 주요 변경사항인 JWT 인증 기능 추가를 명확하고 간결하게 요약하고 있습니다.
✨ 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 SPM-505

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.

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

CSRF vulnerability due to protection being disabled.

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.

Copy link

@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: 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_DENIED11430 코드를 사용하는데, 다른 JWT 관련 FORBIDDEN 에러들과 일관성을 위해 124xx 또는 403xx 범위를 사용하는 것을 고려하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3691e86 and 9c8bddd.

📒 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: 패키지 경로 오타 수정 완료!

entitiyentity로 올바르게 수정되었습니다.

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: 패키지 경로 오타 수정 완료!

entitiyentity로 올바르게 수정되었습니다.

src/main/java/com/sampoom/factory/common/entity/Role.java (1)

3-6: 기본 역할 정의 확인

ADMINUSER 두 가지 기본 역할이 정의되어 있습니다. 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.java enum에는 ADMINUSER만 정의되어 있습니다.

중간 레벨 역할들이 다른 곳(예: 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 에러 상태들이 명확하게 정의되어 있습니다. 각 에러 메시지가 구체적이어서 디버깅에 유용합니다.

Copy link

@yangjiseonn yangjiseonn left a comment

Choose a reason for hiding this comment

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

확인요

@taemin3 taemin3 merged commit e8a74ff into main Nov 11, 2025
8 of 9 checks passed
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