


JWT 인증은 Spring Security FilterChain을 따라 순차적으로 수행된다.
클라이언트 → DispatcherServlet 전에 → Security Filter Chain 작동
[Client] → [JwtAuthenticationFilter] → [UsernamePasswordAuthenticationFilter]
→ [JwtAuthorizationFilter] → [SecurityContextHolder]
→ [Controller @RestController]
JWT를 쓴다는 건
각각의 필터에서 처리한다는 것


| 구분 | 설명 | 예시 |
|---|---|---|
| Header | 토큰 유형, 서명 알고리즘 | { "alg": "HS512", "typ": "JWT" } |
| Payload | 사용자 정보(Claims) | { "sub": "jiyoung@example.com", "auth": "ROLE_USER" } |
| Signature | 서버 비밀키로 서명 | HMACSHA512(base64(header+payload), secretKey) |

Access / Refresh Token을 “발급”하는 단계
AuthenticationManager에게 인증 위임UserDetailsService 를 호출해 DB의 유저 정보와 비교Authentication 객체를 생성하고 SecurityContextHolder 에 저장예시:
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
인증 성공 시 JWT를 생성하고 응답 헤더나 바디에 담는다.
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) {
String email = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
String token = jwtProvider.createAccessToken(email);
response.addHeader("Authorization", "Bearer " + token);
}

사용자가 API 요청할 때 Access Token 검증하는 과정
이때 작동하는 핵심이 바로 JwtAuthorizationFilter다.
GET /api/user/profile
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9...
OncePerRequestFilter 계열 필터 작동Authorization 헤더 존재 여부 확인JwtProvider를 통해 서명 검증 및 Claims 파싱예시 코드:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = jwtProvider.resolveToken(request); // "Bearer ..." 추출
if (token != null && jwtProvider.validateToken(token)) {
Authentication auth = jwtProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
public String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (bearer != null && bearer.startsWith("Bearer ")) {
return bearer.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(SKEY).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
JWT에서 사용자 정보를 읽어 SecurityContextHolder 에 저장할 Authentication 생성
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(SKEY)
.build()
.parseClaimsJws(token)
.getBody();
String email = claims.getSubject();
List<GrantedAuthority> authorities =
Arrays.stream(claims.get("auth").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
UserDetails principal = new User(email, "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
SecurityContextHolder는 현재 요청의 “인증 상태”를 저장하는 ThreadLocal 저장소다.Controller나 @PreAuthorize가 인증 객체를 꺼낼 수 있게 해준다.예시:
@GetMapping("/profile")
public ResponseEntity<?> getProfile() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();
...
}
| 순서 | 필터 | 역할 | 동작 시점 |
|---|---|---|---|
| 1 | UsernamePasswordAuthenticationFilter | 로그인 시도 감지 | 로그인 요청 시 |
| 2 | JwtAuthenticationFilter | JWT 생성 및 발급 | 로그인 성공 시 |
| 3 | JwtAuthorizationFilter | JWT 검증 및 Authentication 주입 | 매 요청마다 |
| 4 | ExceptionTranslationFilter | 인증/인가 예외 처리 | 전역 |
| 5 | FilterSecurityInterceptor | URL 권한 체크 | 컨트롤러 진입 전 |
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthorizationFilter jwtAuthorizationFilter;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/signup").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAt(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
✅ 핵심: addFilterBefore, addFilterAfter, addFilterAt 으로 필터 순서 조정 가능.
Authorization 필터는 항상 “요청 진입 초반”에 위치시켜야 함.
JWT 인증 성공 → Authentication 생성 → SecurityContextHolder 저장 → Controller 호출 시 @AuthenticationPrincipal 사용 가능
예시:
@GetMapping("/user/info")
public ResponseEntity<?> info(@AuthenticationPrincipal UserDetails user) {
return ResponseEntity.ok(user.getUsername());
}
@AuthenticationPrincipal 은 SecurityContext 안에 담긴 Principal(UserDetails)을 바로 꺼내주는 어노테이션이다.
blacklist:{token} 형태로 등록[Client] → POST /login(ID, PW)
↓
[JwtAuthenticationFilter]
↓ (인증 성공)
[JwtProvider] → Access + Refresh 발급
↓
[Client] ← 응답 (Authorization: Bearer ...)
↓
GET /api/profile (Authorization 헤더 포함)
↓
[JwtAuthorizationFilter] → validateToken() → setAuthentication()
↓
[SecurityContextHolder] → Controller