Skip to content

[Feat] JWT 인증 필터 구현 - #15

Merged
kosy00 merged 6 commits into
developfrom
feat/10-jwt-filter
Jun 23, 2026
Merged

[Feat] JWT 인증 필터 구현#15
kosy00 merged 6 commits into
developfrom
feat/10-jwt-filter

Conversation

@kosy00

@kosy00 kosy00 commented Jun 22, 2026

Copy link
Copy Markdown
Owner

관련 이슈

closes #10

구현 내용

  • JwtProvider: Access/Refresh Token 생성 및 검증
  • CustomUserDetails: Spring Security 인증 정보 객체
  • JwtAuthenticationFilter: 요청마다 JWT 토큰 검증
  • SecurityConfig: JWT 필터 등록

주요 구현 포인트

JwtProvider

  • Access Token: userId, role, type 클레임 포함 (30분)
  • Refresh Token: userId, type 클레임 포함 (7일)
  • 만료 토큰과 유효하지 않은 토큰 예외 구분 처리

JwtAuthenticationFilter

  • OncePerRequestFilter 상속으로 요청당 1회 실행 보장
  • Authorization 헤더에서 Bearer 토큰 추출
  • 토큰 검증 실패 시 @ExceptionHandler 미작동으로 response 직접 401 응답

CustomUserDetails

  • userId, role을 바로 꺼낼 수 있도록 커스텀 구현
  • 컨트롤러에서 @AuthenticationPrincipal로 바로 사용 가능

체크리스트

  • 코드 구현 완료
  • 동작 확인
  • 불필요한 코드/주석 제거

Summary by CodeRabbit

릴리스 노트

  • New Features
    • JWT 기반 요청 인증이 적용되어 Authorization Bearer 토큰을 자동으로 검증하고 사용자 권한 정보를 활성화합니다.
    • 액세스/리프레시 토큰 발급 및 검증 기능이 추가되었습니다.
    • 만료 또는 유효하지 않은 토큰에 대해 401 Unauthorized를 즉시 반환하고, 오류 정보를 JSON으로 제공합니다.
    • 토큰에서 사용자 식별자와 역할을 추출해 인증 객체를 생성합니다.
  • Refactor
    • 보안 필터 체인 동작이 업데이트되어 토큰 처리 흐름이 우선 적용됩니다.

@kosy00 kosy00 self-assigned this Jun 22, 2026
@kosy00 kosy00 added the feat 새 기능 label Jun 22, 2026
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

JwtProvider(토큰 생성/검증/파싱), CustomUserDetails(UserDetails 구현), JwtAuthenticationFilter(OncePerRequestFilter) 세 클래스를 신규 추가하고, SecurityConfigfilterChain 빈에 JWT 필터를 주입받아 UsernamePasswordAuthenticationFilter 앞에 등록했습니다.

Changes

JWT 인증 필터 구현

Layer / File(s) Summary
JwtProvider 핵심 로직
src/main/java/com/ilson/spotwork/infra/security/jwt/JwtProvider.java
@Value로 시크릿·만료시간을 주입받아 HMAC 키를 생성하고, 액세스/리프레시 토큰 생성(generateAccessToken, generateRefreshToken), 서명 검증(validateToken), userId·Role 추출(getUserId, getRole), Authentication 반환(getAuthentication) 메서드를 구현. ExpiredJwtExceptionEXPIRED_TOKEN, 그 외 JWT 예외는 INVALID_TOKENCustomException으로 변환.
CustomUserDetails — UserDetails 구현체
src/main/java/com/ilson/spotwork/infra/security/jwt/CustomUserDetails.java
userIdrole을 필드로 저장하는 UserDetails 구현체. roleROLE_ 접두사의 SimpleGrantedAuthority로 변환하고, 비밀번호는 빈 문자열, username은 userId 문자열로 반환. 계정 상태 메서드는 모두 true로 고정.
JwtAuthenticationFilter — 요청별 JWT 검증
src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java
OncePerRequestFilter를 상속. Authorization 헤더에서 Bearer 토큰을 추출(resolveToken)하고, JwtProvider.validateTokengetAuthentication을 호출해 SecurityContextHolder에 인증을 설정. 예외 발생 시 컨텍스트를 초기화하고 401 Unauthorized JSON 응답을 즉시 반환(sendUnauthorizedResponse).
SecurityConfig 필터 체인 등록
src/main/java/com/ilson/spotwork/infra/security/SecurityConfig.java
filterChain 빈 시그니처를 (HttpSecurity, JwtAuthenticationFilter)로 변경하고, addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)로 JWT 필터를 폼 인증 필터 앞에 등록.

Sequence Diagram(s)

sequenceDiagram
    participant Client as 클라이언트
    participant JwtAuthFilter as JwtAuthenticationFilter
    participant JwtProvider
    participant SecurityContext as SecurityContextHolder
    participant NextFilter as 다음 필터/컨트롤러

    Client->>JwtAuthFilter: HTTP 요청 (Authorization: Bearer <token>)
    JwtAuthFilter->>JwtAuthFilter: resolveToken() — Bearer 토큰 추출
    alt 토큰 존재
        JwtAuthFilter->>JwtProvider: validateToken(token)
        alt 유효한 토큰
            JwtProvider-->>JwtAuthFilter: true
            JwtAuthFilter->>JwtProvider: getAuthentication(token)
            JwtProvider->>JwtProvider: getUserId(token), getRole(token)
            JwtProvider-->>JwtAuthFilter: UsernamePasswordAuthenticationToken
            JwtAuthFilter->>SecurityContext: setAuthentication(authToken)
            JwtAuthFilter->>NextFilter: filterChain.doFilter()
        else 만료/무효 토큰
            JwtProvider-->>JwtAuthFilter: CustomException (EXPIRED_TOKEN / INVALID_TOKEN)
            JwtAuthFilter->>SecurityContext: clearContext()
            JwtAuthFilter-->>Client: 401 Unauthorized (JSON)
        end
    else 토큰 없음
        JwtAuthFilter->>NextFilter: filterChain.doFilter()
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • kosy00/ilson#14: 동일한 SecurityConfigfilterChain 빈 시그니처를 변경하고 JwtAuthenticationFilter를 등록하는 작업으로, 이번 PR과 직접적으로 연결되는 변경입니다.

Poem

🐰 토큰을 꺼내 서명을 확인하니,
Bearer 헤더 속 비밀이 풀리네.
만료되면 401, 무효면 401,
SecurityContext에 토끼발자국 남기고,
필터 체인 따라 깡충깡충 달려가네! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 JWT 인증 필터 구현이라는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 필수 섹션(관련 이슈, 구현 내용, 체크리스트)을 모두 포함하고 있으며 상세한 구현 포인트를 제시하고 있습니다.
Linked Issues check ✅ Passed PR이 연결된 이슈 #10의 모든 작업 목록을 완료했습니다: JwtProvider, JwtAuthenticationFilter, SecurityConfig 필터 등록, 토큰 검증 동작 확인.
Out of Scope Changes check ✅ Passed 모든 변경사항이 JWT 인증 필터 구현이라는 PR 목표에 직접적으로 관련되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/10-jwt-filter

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/main/java/com/ilson/spotwork/infra/security/jwt/JwtProvider.java (2)

14-14: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

사용되지 않는 import 제거 필요.

User 클래스가 import되었지만 코드에서 사용되지 않습니다.

-import org.springframework.security.core.userdetails.User;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtProvider.java` at line
14, Remove the unused import statement for the User class from
org.springframework.security.core.userdetails in the JwtProvider class. Search
through the entire JwtProvider.java file to verify that the User class is not
referenced anywhere in the code, then delete the import line.

63-75: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

validateToken 메서드 시그니처가 실제 동작과 불일치.

메서드가 boolean을 반환하지만, 검증 실패 시 항상 예외를 던지므로 false를 반환하는 경우가 없습니다. 이로 인해 호출자가 반환값을 검사하는 대신 예외를 catch해야 합니다.

반환 타입을 void로 변경하거나, 예외를 던지지 않고 false를 반환하는 방식 중 하나를 선택하세요.

♻️ 옵션 1: void 반환으로 변경
-     public boolean validateToken(String token) {
+     public void validateToken(String token) {
        try {
            getClaims(token);
-            return true;
        } catch (ExpiredJwtException e) {
            log.warn("[JWT] 만료된 토큰: {}", e.getMessage());
            throw new CustomException(ErrorCode.EXPIRED_TOKEN);
        } catch (Exception e) {
            log.warn("[JWT] 유효하지 않은 토큰: {}", e.getMessage());
-            throw  new CustomException(ErrorCode.INVALID_TOKEN);
+            throw new CustomException(ErrorCode.INVALID_TOKEN);
        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtProvider.java` around
lines 63 - 75, The validateToken method in JwtProvider class has a return type
of boolean but never returns false; it either returns true or throws an
exception. This makes the boolean return type misleading since callers cannot
rely on a false return value for validation failures. Change the return type
from boolean to void since the method uses exceptions for error handling
(throwing CustomException with EXPIRED_TOKEN or INVALID_TOKEN error codes) to
make the method contract match its actual behavior.
src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java (1)

35-38: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

토큰이 두 번 파싱됨 - 비효율적.

validateTokengetAuthentication 모두 내부적으로 getClaims를 호출하여 토큰을 파싱합니다. 매 요청마다 동일한 토큰을 두 번 파싱하는 것은 불필요한 오버헤드입니다.

getAuthentication 호출만으로 검증과 인증 객체 생성을 동시에 처리하거나, JwtProvider에서 파싱 결과를 재사용하는 방식을 고려하세요.

♻️ 제안: validateToken 호출 제거
        if (StringUtils.hasText(token)) {
            try {
-                jwtProvider.validateToken(token);
                Authentication authentication = jwtProvider.getAuthentication(token);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                log.debug("[JWT] 인증 성공 userId = {}", authentication.getName());
            } catch (Exception e) {

getAuthentication 내부에서 getClaims가 호출되므로, 토큰이 유효하지 않으면 예외가 발생합니다. 단, 예외 타입 구분이 필요하다면 getAuthentication에서 예외 처리를 추가해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java`
around lines 35 - 38, The token is being parsed twice in the doFilterInternal
method of JwtAuthenticationFilter - once by validateToken and once by
getAuthentication, both of which call getClaims internally. Remove the redundant
jwtProvider.validateToken(token) call and rely only on the
jwtProvider.getAuthentication(token) method, which will automatically throw an
exception if the token is invalid during the Claims parsing. If exception type
distinction is required for handling different validation failures, add
appropriate exception handling logic within the getAuthentication method or the
surrounding try-catch block to differentiate between validation errors and other
failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java`:
- Around line 61-68: The sendUnauthorizedResponse method uses manual string
replacement to escape only double quotes when building the JSON response, which
does not handle other special characters like backslashes, newlines, and tabs
that require escaping in JSON. This can produce invalid JSON if the message
parameter contains these characters. Replace the manual JSON string construction
with Jackson's ObjectMapper to safely serialize the error response object as
JSON, which will automatically handle all necessary character escaping and
ensure valid JSON output.

In `@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtProvider.java`:
- Around line 82-85: The getRole method in JwtProvider attempts to extract the
"role" claim from tokens without checking if the claim exists, causing a
NullPointerException when called with refresh tokens since they do not contain a
"role" claim. Add token type validation before calling getRole to ensure it is
only invoked with access tokens that contain the role claim. Alternatively, add
null checking in the getRole method to handle cases where the "role" claim is
absent, either by returning a default role value or throwing a more descriptive
exception that clarifies the token type issue.

---

Nitpick comments:
In
`@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java`:
- Around line 35-38: The token is being parsed twice in the doFilterInternal
method of JwtAuthenticationFilter - once by validateToken and once by
getAuthentication, both of which call getClaims internally. Remove the redundant
jwtProvider.validateToken(token) call and rely only on the
jwtProvider.getAuthentication(token) method, which will automatically throw an
exception if the token is invalid during the Claims parsing. If exception type
distinction is required for handling different validation failures, add
appropriate exception handling logic within the getAuthentication method or the
surrounding try-catch block to differentiate between validation errors and other
failures.

In `@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtProvider.java`:
- Line 14: Remove the unused import statement for the User class from
org.springframework.security.core.userdetails in the JwtProvider class. Search
through the entire JwtProvider.java file to verify that the User class is not
referenced anywhere in the code, then delete the import line.
- Around line 63-75: The validateToken method in JwtProvider class has a return
type of boolean but never returns false; it either returns true or throws an
exception. This makes the boolean return type misleading since callers cannot
rely on a false return value for validation failures. Change the return type
from boolean to void since the method uses exceptions for error handling
(throwing CustomException with EXPIRED_TOKEN or INVALID_TOKEN error codes) to
make the method contract match its actual behavior.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4bead06d-1ffb-418f-ac0f-7f524ea0efed

📥 Commits

Reviewing files that changed from the base of the PR and between 0e3f5be and a57e272.

📒 Files selected for processing (4)
  • src/main/java/com/ilson/spotwork/infra/security/SecurityConfig.java
  • src/main/java/com/ilson/spotwork/infra/security/jwt/CustomUserDetails.java
  • src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java
  • src/main/java/com/ilson/spotwork/infra/security/jwt/JwtProvider.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java (1)

43-48: 🩺 Stability & Availability | 🟠 Major

e.getMessage()가 null이면 Map.of가 NullPointerException을 던져 401 응답이 전달되지 않습니다.

두 가지 문제가 있습니다.

  • e.getMessage()는 일부 예외(예: NullPointerException, ClassCastException 등)에서 null을 반환합니다. java.util.Map.of(...)는 null 값을 허용하지 않으므로 line 69의 Map.of("code", 401, "message", message) 호출에서 NullPointerException이 발생하고, 이 NPE가 catch 블록 밖으로 전파되어 401 응답 대신 500 오류가 발생합니다.
  • 예외 메시지를 그대로 응답 바디에 담으면 토큰 검증 관련 내부 정보가 클라이언트에 노출됩니다. 일반화된 메시지를 반환하는 것이 안전합니다.

호출 지점에서 고정 메시지를 전달하면 두 문제가 모두 해소됩니다(상세 사유는 로그에만 유지).

🛡️ 제안 수정
             } catch (Exception e) {
                 log.warn("[JWT] 인증 실패: {}", e.getMessage());
                 SecurityContextHolder.clearContext();
-                sendUnauthorizedResponse(response, e.getMessage());
+                sendUnauthorizedResponse(response, "인증에 실패했습니다.");
                 return;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java`
around lines 43 - 48, In the exception catch block within
JwtAuthenticationFilter, the `sendUnauthorizedResponse(response,
e.getMessage())` call at line 47 passes the raw exception message, which can be
null for certain exceptions like NullPointerException, causing a
NullPointerException in Map.of and exposing internal details to clients. Replace
the call to sendUnauthorizedResponse to pass a fixed generic error message
(e.g., "인증 실패") instead of e.getMessage(), while keeping the detailed error
message in the log.warn statement for debugging purposes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java`:
- Around line 43-48: In the exception catch block within
JwtAuthenticationFilter, the `sendUnauthorizedResponse(response,
e.getMessage())` call at line 47 passes the raw exception message, which can be
null for certain exceptions like NullPointerException, causing a
NullPointerException in Map.of and exposing internal details to clients. Replace
the call to sendUnauthorizedResponse to pass a fixed generic error message
(e.g., "인증 실패") instead of e.getMessage(), while keeping the detailed error
message in the log.warn statement for debugging purposes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 16760f33-a036-4d0c-824d-48098b1b3e39

📥 Commits

Reviewing files that changed from the base of the PR and between a57e272 and 599808d.

📒 Files selected for processing (1)
  • src/main/java/com/ilson/spotwork/infra/security/jwt/JwtAuthenticationFilter.java

@kosy00
kosy00 merged commit fa7d5c1 into develop Jun 23, 2026
2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 24, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새 기능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]JWT 인증 필터 구현

1 participant