Feat/2 jwt authentication - #13
Conversation
JWT 토큰 검증 및 클레임 파싱 책임을 담당하는 컴포넌트. JwtTokenProvider: - HMAC HS256 알고리즘 (대칭키 방식) - jwt.secret 환경변수에서 키 초기화 (256비트 이상 보장) - validateToken(): 서명/만료/형식 검증, 5가지 예외 구분 로깅 - parseClaims(): sub(UUID) + role(String) 추출 JwtClaims (record): - 불변 DTO, 파싱 결과 전달용 application.yml: - jwt.secret 환경변수 패턴 추가 (운영 시 외부 주입) - 기본값은 개발 전용 build.gradle: - jjwt 0.12.6 의존성 추가 (api/impl/jackson) - ext 변수 jjwtVersion 으로 단일 관리 Gateway는 토큰 검증만 책임, 생성은 User(Auth) Service 담당. 양쪽이 동일한 JWT_SECRET 공유. Related to: #5
JWT 인증 필터를 Spring Cloud Gateway의 GlobalFilter로 구현. JwtProperties (record): - @ConfigurationProperties로 jwt 섹션 자동 바인딩 - secret + publicPaths 통합 관리 AuthenticationFilter: - Public Path 매칭 시 인증 우회 (AntPathMatcher 사용) - Bearer 형식 강제 (RFC 6750) - 검증 실패 시 401 + JSON 에러 응답 - 검증 성공 시 X-User-Id, X-User-Role 헤더 주입 - @order HIGHEST_PRECEDENCE+100 으로 가장 일찍 실행 application.yml: - jwt.public-paths 추가 (회원가입/로그인/actuator/webhook) JwtTokenProvider: - @value 주입에서 JwtProperties 의존으로 변경 (일관성) GatewayApplication: - @EnableConfigurationProperties(JwtProperties.class) Related to: #6
Related to: #12
📝 Walkthrough개요Spring Cloud Gateway에 JWT 인증 필터를 도입하여 요청에 인증된 사용자 정보(UUID, Role)를 헤더로 주입합니다. JJWT 의존성 추가, 토큰 검증 및 클레임 파싱 로직 구현, 공개 경로 화이트리스트 설정, CI/CD 파이프라인 구성이 포함됩니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant Client as 클라이언트
participant Gateway as API Gateway<br/>(AuthenticationFilter)
participant Provider as JwtTokenProvider
participant Service as 백엔드 서비스
Client->>Gateway: HTTP 요청<br/>(Authorization: Bearer token)
Gateway->>Gateway: 경로 확인<br/>(공개 경로 여부)
alt 공개 경로
Gateway->>Service: 요청 전달
Service->>Client: 응답
else 보호된 경로
Gateway->>Gateway: Authorization 헤더<br/>검증
alt Bearer 토큰 없음
Gateway->>Client: 401 UNAUTHORIZED
else Bearer 토큰 존재
Gateway->>Provider: validateToken(token)
Provider->>Provider: JJWT 파싱 및<br/>시그니처 검증
alt 검증 실패
Provider->>Gateway: false
Gateway->>Client: 401 UNAUTHORIZED
else 검증 성공
Provider->>Gateway: true
Gateway->>Provider: parseClaims(token)
Provider->>Provider: sub 클레임 파싱<br/>(UUID 변환)<br/>role 클레임 추출
Provider->>Gateway: JwtClaims(userId, role)
Gateway->>Gateway: X-User-Id,<br/>X-User-Role 헤더<br/>주입
Gateway->>Service: 요청 전달<br/>(인증 정보 포함)
Service->>Client: 응답
end
end
end
예상 코드 리뷰 소요 시간🎯 3 (중간) | ⏱️ ~25분 시 🐰
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
.github/workflows/auto-assign.yml (1)
16-16: 외부 GitHub Action은 태그 대신 커밋 SHA로 고정하세요.
uses: kentaro-m/auto-assign-action@v1.2.5는 변경 가능한 참조이므로 공급망 리스크가 있습니다. 릴리스 태그가 가리키는 실제 커밋 SHA로 고정하는 것이 안전합니다.수정 예시
- uses: kentaro-m/auto-assign-action@v1.2.5 + uses: kentaro-m/auto-assign-action@<v1.2.5가 가리키는 커밋 SHA>GitHub 저장소에서 릴리스 페이지나 Git 태그를 통해 정확한 SHA를 확인하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/auto-assign.yml at line 16, Replace the mutable tag reference "uses: kentaro-m/auto-assign-action@v1.2.5" with the exact commit SHA for that release; locate the repository release or tag for kentaro-m/auto-assign-action, copy the full commit SHA for the v1.2.5 tag, and update the workflow line to use that SHA (e.g., uses: kentaro-m/auto-assign-action@<commit-sha>) so the external action is pinned to an immutable commit.src/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.java (1)
113-120: 공개 경로 매칭이 HTTP 메서드를 고려하지 않습니다.현재 구현은 경로만 확인하고 HTTP 메서드는 고려하지 않습니다. 연결된 이슈
#3의요구사항에 따르면 특정 메서드만 공개되어야 합니다 (예:POST /api/v1/users).HTTP 메서드까지 고려한 화이트리스트가 필요하다면, 설정 구조와 매칭 로직을 함께 수정해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.java` around lines 113 - 120, The current isPublicPath(String path) only matches paths and ignores HTTP methods; update the API to match method+path: change isPublicPath to accept the HTTP method (e.g., isPublicPath(String method, String path)) and update calls in AuthenticationFilter to pass request.getMethod(); extend jwtProperties.publicPaths() to carry method info (either change to a List<PublicPath> POJO with getMethod()/getPattern() or keep strings like "POST /api/v1/users" and parse them), then adjust the matching logic in isPublicPath to first compare the method (allowing wildcards like ANY or *) and then pathMatcher.match(pattern, path) against the pattern part (or PublicPath.getPattern()). Ensure backward compatibility or validation for existing config entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/com/pagley/gateway/infrastructure/security/JwtClaims.java`:
- Around line 10-14: JwtClaims currently allows role to be null/empty/invalid
which can lead to 500 errors; add validation in the record by implementing a
compact canonical constructor in JwtClaims that checks role is non-null and
non-blank and is one of the allowed values (validate against an existing Role
enum or a defined Set<String> ALLOWED_ROLES), and throw IllegalArgumentException
if the check fails; ensure the constructor still assigns userId and role as
before so the record invariants are enforced.
In `@src/main/java/com/pagley/gateway/infrastructure/security/JwtProperties.java`:
- Around line 15-17: JwtProperties currently exposes publicPaths as
List<String>, which cannot express HTTP method+path combinations; change the
model to represent routes as method+pathPattern pairs (e.g., introduce a
PublicRoute record/class with String method and String pathPattern) and replace
the publicPaths field with List<PublicRoute> publicRoutes in JwtProperties;
update any code that constructs or checks JwtProperties (parsing, filtering, and
authorization checks) to use PublicRoute.method and PublicRoute.pathPattern
instead of raw strings and ensure matching logic honors both method and pattern.
In
`@src/main/java/com/pagley/gateway/infrastructure/security/JwtTokenProvider.java`:
- Around line 87-91: JwtTokenProvider currently reads the role claim into a
nullable String which propagates as the literal "null" downstream; change the
role extraction in JwtTokenProvider (where claims.get(CLAIM_ROLE, String.class)
is used) to normalize nulls (e.g. String role =
Optional.ofNullable(claims.get(CLAIM_ROLE, String.class)).orElse("") or use a
default like "USER"), ensure JwtClaims stores a non-null role, and update
AuthenticationFilter to skip adding the X-User-Role header when
JwtClaims.getRole() is empty (or use the chosen default) so the literal "null"
is never injected into headers.
In `@src/main/resources/.env`:
- Around line 1-20: The committed .env in src/main/resources contains real
secrets (DB_PASSWORD, TTBKEY, JWT_SECRET) and must be removed and rotated:
delete the file from the repo, add src/main/resources/.env to .gitignore,
replace it with a non-secret template named .env.example (showing variable names
DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, AUTH_DB_NAME, TTBKEY, JWT_SECRET but
no values), and instruct ops to immediately revoke/rotate the exposed
credentials (rotate DB_PASSWORD, invalidate/regenerate TTBKEY and JWT_SECRET).
Also purge the secrets from git history using a history-rewrite tool (git
filter-repo or BFG), and update deployment config to read secrets from a secure
store (env vars, Vault/secret manager) instead of committing them.
In `@src/main/resources/application.yml`:
- Around line 89-93: The current public-path matching in
AuthenticationFilter.isPublicPath() only checks URL patterns and therefore
allows all HTTP methods for those paths; update the design so
isPublicPath(HttpServletRequest) also inspects the request method and the
configured public routes include method information (e.g., map of HTTP method →
allowed path patterns) or add a new method-aware matcher (e.g.,
isPublicPathByMethod or extend isPublicPath to accept method) and change the
config shape to specify method+path entries (or tighten application.yml entries
to exact method-specific endpoints like POST /api/v1/users and limit actuator to
safe subsets such as /actuator/health and /actuator/metrics); ensure
AuthenticationFilter uses the new method-aware logic to prevent GET/PUT/DELETE
on POST-only public endpoints and to restrict sensitive actuator endpoints.
In `@src/test/java/com/pagley/gateway/JwtTestTokenGenerator.java`:
- Around line 29-37: In JwtTestTokenGenerator where you load Properties using
new FileInputStream(envPath), replace the manual FileInputStream usage with a
try-with-resources (or otherwise ensure the stream is closed) so the
FileInputStream is automatically closed on exit; update the block that assigns
secret = props.getProperty("JWT_SECRET") inside the try-with-resources and keep
the existing IOException catch unchanged so there is no resource leak.
---
Nitpick comments:
In @.github/workflows/auto-assign.yml:
- Line 16: Replace the mutable tag reference "uses:
kentaro-m/auto-assign-action@v1.2.5" with the exact commit SHA for that release;
locate the repository release or tag for kentaro-m/auto-assign-action, copy the
full commit SHA for the v1.2.5 tag, and update the workflow line to use that SHA
(e.g., uses: kentaro-m/auto-assign-action@<commit-sha>) so the external action
is pinned to an immutable commit.
In
`@src/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.java`:
- Around line 113-120: The current isPublicPath(String path) only matches paths
and ignores HTTP methods; update the API to match method+path: change
isPublicPath to accept the HTTP method (e.g., isPublicPath(String method, String
path)) and update calls in AuthenticationFilter to pass request.getMethod();
extend jwtProperties.publicPaths() to carry method info (either change to a
List<PublicPath> POJO with getMethod()/getPattern() or keep strings like "POST
/api/v1/users" and parse them), then adjust the matching logic in isPublicPath
to first compare the method (allowing wildcards like ANY or *) and then
pathMatcher.match(pattern, path) against the pattern part (or
PublicPath.getPattern()). Ensure backward compatibility or validation for
existing config entries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 354c7629-2e10-47fb-b0f5-5182dc867ad8
📒 Files selected for processing (14)
.github/auto_assign.yml.github/workflows/auto-assign.yml.github/workflows/branch-name-check.yml.github/workflows/ci.yml.gitignorebuild.gradlesrc/main/java/com/pagley/gateway/GatewayApplication.javasrc/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.javasrc/main/java/com/pagley/gateway/infrastructure/security/JwtClaims.javasrc/main/java/com/pagley/gateway/infrastructure/security/JwtProperties.javasrc/main/java/com/pagley/gateway/infrastructure/security/JwtTokenProvider.javasrc/main/resources/.envsrc/main/resources/application.ymlsrc/test/java/com/pagley/gateway/JwtTestTokenGenerator.java
| public record JwtClaims( | ||
| UUID userId, | ||
| String role | ||
| ) { | ||
| } |
There was a problem hiding this comment.
role 무검증 허용으로 유효 토큰 요청이 500으로 터질 수 있습니다.
Line 12의 role이 null/빈값/허용 외 값이어도 통과됩니다. 현재 증상(유효 토큰 500)과도 맞물리는 구간이라, 레코드에서 불변식을 강제해 방어하는 게 안전합니다.
수정 예시
package com.pagley.gateway.infrastructure.security;
+import java.util.Objects;
+import java.util.Set;
import java.util.UUID;
@@
public record JwtClaims(
UUID userId,
String role
) {
+ private static final Set<String> ALLOWED_ROLES = Set.of("USER", "MASTER", "CREATOR");
+
+ public JwtClaims {
+ Objects.requireNonNull(userId, "userId must not be null");
+ if (role == null || role.isBlank()) {
+ throw new IllegalStateException("JWT role 클레임이 비어있습니다.");
+ }
+ if (!ALLOWED_ROLES.contains(role)) {
+ throw new IllegalStateException("지원하지 않는 role 값입니다: " + role);
+ }
+ }
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/pagley/gateway/infrastructure/security/JwtClaims.java`
around lines 10 - 14, JwtClaims currently allows role to be null/empty/invalid
which can lead to 500 errors; add validation in the record by implementing a
compact canonical constructor in JwtClaims that checks role is non-null and
non-blank and is one of the allowed values (validate against an existing Role
enum or a defined Set<String> ALLOWED_ROLES), and throw IllegalArgumentException
if the check fails; ensure the constructor still assigns userId and role as
before so the record invariants are enforced.
| public record JwtProperties( | ||
| String secret, | ||
| List<String> publicPaths |
There was a problem hiding this comment.
공개 경로를 List<String>로만 두면 메서드 제한 요구사항을 충족하지 못합니다.
Line 17 구조로는 POST /api/v1/users만 허용 같은 정책을 표현할 수 없어, 같은 경로의 다른 메서드가 의도치 않게 우회될 수 있습니다. 공개 라우트는 최소 method + pathPattern 조합으로 모델링하는 쪽이 안전합니다.
구조 개선 예시
public record JwtProperties(
String secret,
- List<String> publicPaths
+ List<PublicRoute> publicRoutes
) {
+ public record PublicRoute(
+ String method,
+ String pathPattern
+ ) {}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public record JwtProperties( | |
| String secret, | |
| List<String> publicPaths | |
| public record JwtProperties( | |
| String secret, | |
| List<PublicRoute> publicRoutes | |
| ) { | |
| public record PublicRoute( | |
| String method, | |
| String pathPattern | |
| ) {} | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/pagley/gateway/infrastructure/security/JwtProperties.java`
around lines 15 - 17, JwtProperties currently exposes publicPaths as
List<String>, which cannot express HTTP method+path combinations; change the
model to represent routes as method+pathPattern pairs (e.g., introduce a
PublicRoute record/class with String method and String pathPattern) and replace
the publicPaths field with List<PublicRoute> publicRoutes in JwtProperties;
update any code that constructs or checks JwtProperties (parsing, filtering, and
authorization checks) to use PublicRoute.method and PublicRoute.pathPattern
instead of raw strings and ensure matching logic honors both method and pattern.
| UUID userId = parseUserId(claims.getSubject()); | ||
| String role = claims.get(CLAIM_ROLE, String.class); | ||
|
|
||
| return new JwtClaims(userId, role); | ||
| } |
There was a problem hiding this comment.
role 클레임이 null일 수 있습니다.
role 클레임이 존재하지 않으면 null이 반환되고, 이후 AuthenticationFilter에서 X-User-Role 헤더에 "null" 문자열이 주입됩니다. 다운스트림 서비스에서 문제가 발생할 수 있습니다.
🛡️ 수정 제안
UUID userId = parseUserId(claims.getSubject());
String role = claims.get(CLAIM_ROLE, String.class);
+ if (role == null || role.isBlank()) {
+ throw new IllegalStateException("JWT role 클레임이 비어있습니다.");
+ }
return new JwtClaims(userId, role);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| UUID userId = parseUserId(claims.getSubject()); | |
| String role = claims.get(CLAIM_ROLE, String.class); | |
| return new JwtClaims(userId, role); | |
| } | |
| UUID userId = parseUserId(claims.getSubject()); | |
| String role = claims.get(CLAIM_ROLE, String.class); | |
| if (role == null || role.isBlank()) { | |
| throw new IllegalStateException("JWT role 클레임이 비어있습니다."); | |
| } | |
| return new JwtClaims(userId, role); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/pagley/gateway/infrastructure/security/JwtTokenProvider.java`
around lines 87 - 91, JwtTokenProvider currently reads the role claim into a
nullable String which propagates as the literal "null" downstream; change the
role extraction in JwtTokenProvider (where claims.get(CLAIM_ROLE, String.class)
is used) to normalize nulls (e.g. String role =
Optional.ofNullable(claims.get(CLAIM_ROLE, String.class)).orElse("") or use a
default like "USER"), ensure JwtClaims stores a non-null role, and update
AuthenticationFilter to skip adding the X-User-Role header when
JwtClaims.getRole() is empty (or use the chosen default) so the literal "null"
is never injected into headers.
| public-paths: | ||
| - /api/v1/users | ||
| - /api/v1/auth/** | ||
| - /actuator/** | ||
| - /api/v1/payment/webhook |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# actuator 관련 설정 확인
rg -n "actuator|management" --type=yamlRepository: Pagely-wisely/gateway-server
Length of output: 129
🏁 Script executed:
# Check the full application.yml file to understand the complete context
cat -n src/main/resources/application.ymlRepository: Pagely-wisely/gateway-server
Length of output: 3253
🏁 Script executed:
# Search for Spring Security configuration
find . -type f \( -name "*Security*.java" -o -name "*Security*.kt" -o -name "*Filter*.java" \) | head -20Repository: Pagely-wisely/gateway-server
Length of output: 157
🏁 Script executed:
# Check for any JWT or authentication filter configuration
rg -n "jwt|JwtToken|Filter|Security" --type=java -A 2 | head -100Repository: Pagely-wisely/gateway-server
Length of output: 5858
🏁 Script executed:
cat -n src/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.javaRepository: Pagely-wisely/gateway-server
Length of output: 6775
🏁 Script executed:
# Check if there's Spring Security configuration with method-level restrictions
find . -type f \( -name "*.java" -o -name "*.kt" \) -exec grep -l "HttpMethod\|@PostMapping\|@GetMapping\|SecurityConfig\|method()" {} \; | head -10Repository: Pagely-wisely/gateway-server
Length of output: 54
🏁 Script executed:
# Check for any WebFlux or MVC security configuration
rg -n "SecurityConfig|WebSecurityConfig|HttpSecurity|authorizeExchanges" --type=java | head -20Repository: Pagely-wisely/gateway-server
Length of output: 54
🏁 Script executed:
# Search for user service controller or route definitions
find . -type f \( -name "*User*Controller.java" -o -name "*Auth*Controller.java" -o -name "*User*Routes.java" \) -o -path "*/user-service/*" 2>/dev/null | head -10Repository: Pagely-wisely/gateway-server
Length of output: 54
🏁 Script executed:
# Check the PR or issue description to understand requirements
git log --oneline -20Repository: Pagely-wisely/gateway-server
Length of output: 121
🏁 Script executed:
# Search for any method-based request matching in Spring Config
rg -n "HttpMethod|POST|GET.*api/v1/users" --type=java | head -30Repository: Pagely-wisely/gateway-server
Length of output: 54
경로 기반 필터링만으로는 HTTP 메서드를 구분하지 않습니다.
AuthenticationFilter의 isPublicPath() 메서드는 경로 패턴만 확인하며 HTTP 메서드를 검사하지 않습니다. 따라서 /api/v1/users는 POST뿐 아니라 GET, PUT, DELETE 등 모든 HTTP 메서드에 대해 인증을 우회하게 됩니다. 이는 사용자 목록 조회 등 인증이 필요한 요청을 노출시킵니다.
/actuator/** 공개 설정도 마찬가지로 모든 actuator 엔드포인트(예: /actuator/env, /actuator/beans)를 인증 없이 접근 가능하게 하여 민감한 정보 노출 위험이 있습니다.
필터 레벨에서 메서드 기반 필터링을 추가하거나, 공개 경로를 더 구체적으로 정의해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/application.yml` around lines 89 - 93, The current
public-path matching in AuthenticationFilter.isPublicPath() only checks URL
patterns and therefore allows all HTTP methods for those paths; update the
design so isPublicPath(HttpServletRequest) also inspects the request method and
the configured public routes include method information (e.g., map of HTTP
method → allowed path patterns) or add a new method-aware matcher (e.g.,
isPublicPathByMethod or extend isPublicPath to accept method) and change the
config shape to specify method+path entries (or tighten application.yml entries
to exact method-specific endpoints like POST /api/v1/users and limit actuator to
safe subsets such as /actuator/health and /actuator/metrics); ensure
AuthenticationFilter uses the new method-aware logic to prevent GET/PUT/DELETE
on POST-only public endpoints and to restrict sensitive actuator endpoints.
| Properties props = new Properties(); | ||
| props.load(new FileInputStream(envPath)); | ||
|
|
||
| // 2. .env 파일에서 JWT_SECRET 키로 값을 가져옴 | ||
| secret = props.getProperty("JWT_SECRET"); | ||
|
|
||
| } catch (IOException e) { | ||
| System.err.println(".env 파일을 찾을 수 없습니다: " + e.getMessage()); | ||
| } |
There was a problem hiding this comment.
리소스 누수: FileInputStream이 닫히지 않습니다.
FileInputStream을 열지만 닫지 않아 리소스 누수가 발생합니다. try-with-resources를 사용하세요.
🔧 수정 제안
- Properties props = new Properties();
- props.load(new FileInputStream(envPath));
-
- // 2. .env 파일에서 JWT_SECRET 키로 값을 가져옴
- secret = props.getProperty("JWT_SECRET");
-
- } catch (IOException e) {
+ Properties props = new Properties();
+ try (FileInputStream fis = new FileInputStream(envPath)) {
+ props.load(fis);
+ }
+
+ // 2. .env 파일에서 JWT_SECRET 키로 값을 가져옴
+ secret = props.getProperty("JWT_SECRET");
+
+ } catch (IOException e) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Properties props = new Properties(); | |
| props.load(new FileInputStream(envPath)); | |
| // 2. .env 파일에서 JWT_SECRET 키로 값을 가져옴 | |
| secret = props.getProperty("JWT_SECRET"); | |
| } catch (IOException e) { | |
| System.err.println(".env 파일을 찾을 수 없습니다: " + e.getMessage()); | |
| } | |
| Properties props = new Properties(); | |
| try (FileInputStream fis = new FileInputStream(envPath)) { | |
| props.load(fis); | |
| } | |
| // 2. .env 파일에서 JWT_SECRET 키로 값을 가져옴 | |
| secret = props.getProperty("JWT_SECRET"); | |
| } catch (IOException e) { | |
| System.err.println(".env 파일을 찾을 수 없습니다: " + e.getMessage()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/com/pagley/gateway/JwtTestTokenGenerator.java` around lines 29
- 37, In JwtTestTokenGenerator where you load Properties using new
FileInputStream(envPath), replace the manual FileInputStream usage with a
try-with-resources (or otherwise ensure the stream is closed) so the
FileInputStream is automatically closed on exit; update the block that assigns
secret = props.getProperty("JWT_SECRET") inside the try-with-resources and keep
the existing IOException catch unchanged so there is no resource leak.
e76c1f1 to
b0d853a
Compare
d8f485a to
247d1c5
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (3)
src/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.java (1)
113-120:⚠️ Potential issue | 🔴 CriticalPublic 경로 매칭이 HTTP 메서드를 구분하지 않습니다.
Line 113~120의
isPublicPath는 path만 검사해서, 예를 들어POST /api/v1/users만 열어야 하는 요구사항이GET/PUT/DELETE /api/v1/users까지 인증 우회로 확장됩니다. 공개 경로는 메서드+패턴 기준으로 매칭하도록 바꿔야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.java` around lines 113 - 120, isPublicPath currently only matches the request path so a public rule like "POST /api/v1/users" will incorrectly allow GET/PUT/DELETE; change the matching to be method-aware by updating isPublicPath to accept the HTTP method (e.g., isPublicPath(String method, String path)) and update jwtProperties.publicPaths() to return method+pattern pairs (or a structure with getMethod()/getPattern()) instead of plain patterns; then iterate those entries and return true only when entry.method.equalsIgnoreCase(method) && pathMatcher.match(entry.pattern, path) (or parse "METHOD pattern" strings and compare both parts) so both method and path are required to match.src/main/java/com/pagley/gateway/infrastructure/security/JwtTokenProvider.java (1)
87-90:⚠️ Potential issue | 🟠 Major
role클레임 null/blank 검증이 필요합니다.Line 88에서
role을 nullable로 읽어 바로 반환하면, downstream 헤더 주입 시 500 또는"null"전파가 발생할 수 있습니다. 여기서 명시적으로 예외 처리해 401 흐름으로 보내는 게 안전합니다.🔧 제안 변경
UUID userId = parseUserId(claims.getSubject()); String role = claims.get(CLAIM_ROLE, String.class); + if (role == null || role.isBlank()) { + throw new IllegalStateException("JWT role 클레임이 비어있습니다."); + } return new JwtClaims(userId, role);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/pagley/gateway/infrastructure/security/JwtTokenProvider.java` around lines 87 - 90, JwtTokenProvider에서 claims로부터 읽은 role(CLAIM_ROLE)을 그대로 반환하면 null/blank가 downstream으로 전파되어 500이나 "null" 헤더가 생길 수 있으므로, parseUserId(...) 호출 직후 JwtTokenProvider#CLAIM_ROLE로 읽은 role 값을 null 또는 blank 체크하고 유효하지 않으면 JwtClaims를 반환하지 않고 인증 실패를 유도하는 예외(예: JwtAuthenticationException 또는 적절한 AuthenticationException)를 던져 401 흐름으로 보내도록 변경하세요; 참고 지점: 클래스 JwtTokenProvider, 상수 CLAIM_ROLE, 메서드 parseUserId(...) 및 반환 타입 JwtClaims를 수정해 role 검증 로직을 추가하세요.src/main/resources/application.yml (1)
89-93:⚠️ Potential issue | 🟠 MajorActuator 전체 공개 경로는 과도합니다.
Line 92의
/actuator/**는 민감한 운영 정보 노출로 이어질 수 있습니다. 최소한health/info등 필요한 엔드포인트만 화이트리스트로 제한해 주세요.🔧 제안 변경
jwt: public-paths: - /api/v1/users - /api/v1/auth/** - - /actuator/** + - /actuator/health + - /actuator/info - /api/v1/payment/webhook🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/application.yml` around lines 89 - 93, The public-paths entry currently exposes all actuator endpoints via the `/actuator/**` pattern; restrict this by removing `/actuator/**` and explicitly whitelist only the safe actuator endpoints required (e.g., `/actuator/health`, `/actuator/info`, and any specific readiness/liveness endpoints you actually need) in the `public-paths` list in application.yml so sensitive operational endpoints are not publicly accessible; update the `public-paths` array to include only those specific actuator paths instead of the wildcard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@src/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.java`:
- Around line 113-120: isPublicPath currently only matches the request path so a
public rule like "POST /api/v1/users" will incorrectly allow GET/PUT/DELETE;
change the matching to be method-aware by updating isPublicPath to accept the
HTTP method (e.g., isPublicPath(String method, String path)) and update
jwtProperties.publicPaths() to return method+pattern pairs (or a structure with
getMethod()/getPattern()) instead of plain patterns; then iterate those entries
and return true only when entry.method.equalsIgnoreCase(method) &&
pathMatcher.match(entry.pattern, path) (or parse "METHOD pattern" strings and
compare both parts) so both method and path are required to match.
In
`@src/main/java/com/pagley/gateway/infrastructure/security/JwtTokenProvider.java`:
- Around line 87-90: JwtTokenProvider에서 claims로부터 읽은 role(CLAIM_ROLE)을 그대로 반환하면
null/blank가 downstream으로 전파되어 500이나 "null" 헤더가 생길 수 있으므로, parseUserId(...) 호출 직후
JwtTokenProvider#CLAIM_ROLE로 읽은 role 값을 null 또는 blank 체크하고 유효하지 않으면 JwtClaims를
반환하지 않고 인증 실패를 유도하는 예외(예: JwtAuthenticationException 또는 적절한
AuthenticationException)를 던져 401 흐름으로 보내도록 변경하세요; 참고 지점: 클래스 JwtTokenProvider,
상수 CLAIM_ROLE, 메서드 parseUserId(...) 및 반환 타입 JwtClaims를 수정해 role 검증 로직을 추가하세요.
In `@src/main/resources/application.yml`:
- Around line 89-93: The public-paths entry currently exposes all actuator
endpoints via the `/actuator/**` pattern; restrict this by removing
`/actuator/**` and explicitly whitelist only the safe actuator endpoints
required (e.g., `/actuator/health`, `/actuator/info`, and any specific
readiness/liveness endpoints you actually need) in the `public-paths` list in
application.yml so sensitive operational endpoints are not publicly accessible;
update the `public-paths` array to include only those specific actuator paths
instead of the wildcard.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c68e1251-d456-4fa5-9b51-d22a612787d5
📒 Files selected for processing (12)
.github/auto_assign.yml.github/workflows/auto-assign.yml.github/workflows/branch-name-check.yml.github/workflows/ci.yml.gitignoresrc/main/java/com/pagley/gateway/GatewayApplication.javasrc/main/java/com/pagley/gateway/infrastructure/security/AuthenticationFilter.javasrc/main/java/com/pagley/gateway/infrastructure/security/JwtClaims.javasrc/main/java/com/pagley/gateway/infrastructure/security/JwtProperties.javasrc/main/java/com/pagley/gateway/infrastructure/security/JwtTokenProvider.javasrc/main/resources/application.ymlsrc/test/java/com/pagley/gateway/JwtTestTokenGenerator.java
✅ Files skipped from review due to trivial changes (7)
- .gitignore
- src/main/java/com/pagley/gateway/infrastructure/security/JwtClaims.java
- .github/auto_assign.yml
- .github/workflows/auto-assign.yml
- .github/workflows/branch-name-check.yml
- .github/workflows/ci.yml
- src/main/java/com/pagley/gateway/infrastructure/security/JwtProperties.java
🚧 Files skipped from review as they are similar to previous changes (2)
- src/test/java/com/pagley/gateway/JwtTestTokenGenerator.java
- src/main/java/com/pagley/gateway/GatewayApplication.java
📝 작업 내용
다운스트림 서비스로 가는 요청에 인증된 유저 정보를 헤더(X-User-Id, X-User-Role)로 주입.
🚀 주요 변경 사항
✅ 자체 체크리스트 (필수)
./gradlew build실행 결과 정상 (인증샷 첨부)📸 테스트 인증샷
💬 리뷰어 전달사항 (선택)
📎 참고 자료
Summary by CodeRabbit
릴리스 노트
New Features
Chores