Spring Security 위에서 돌아가는 커스텀 JWT 인증 필터 구현기
✨ 왜 이걸 했는가
프로젝트에서 JWT 인증을 구현하려 했을 때, 단순히 OncePerRequestFilter를 사용하는 방식은 나에겐 불충분했다.
- Spring Security의 인증 흐름을 제대로 타고 싶었고
- 인증 실패 시 에러 응답을 직접 제어하고 싶었으며
- 인증 성공 시 SecurityContext에 자연스럽게 주입되기를 바랐다
그래서 나는 AbstractAuthenticationProcessingFilter 를 상속받아 필터를 만들기로 결정했다. 처음부터 쉽지는 않았지만, 지금 돌아보면 가장 확장성 있고 안정적인 선택이었다.
🌎 전체 구성
| 파트 | 역할 |
|---|---|
| JwtAuthenticationToken | 인증 전/후 상태를 나누는 Token 객체 |
| JwtAuthenticationProvider | 토큰 검증 및 인증 주체 반환 |
| JwtFilter | 실제 필터. 토큰 파싱, Provider 연결 |
| SecurityConfig | 필터 체인 구성 및 Provider 주입 |
⚡ 각 파트 설명과 기능
1. JwtAuthenticationToken
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
private final String token;
private final User principal;
...
}
이유: 인증 전에는 token만 존재하고, 인증 후에는 User가 존재하는 두 상태가 필요했다.
기능:
- 인증 전/후 구분을 위한 생성자 2개 사용
- 인증 정보가 SecurityContext에 저장될 때 사용됨
2. JwtAuthenticationProvider
public class JwtAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) { ... }
}
이유: 토큰을 해석하고 인증 객체를 만들어 줄 실제 로직이 필요했다.
기능:
- 토큰 유효성 검증 (validate)
- 토큰에서 userId 추출
- DB 또는 캐시에서 유저 조회
- 인증된 토큰 객체 반환
3. JwtFilter
public class JwtFilter extends AbstractAuthenticationProcessingFilter { ... }
이유: Spring Security의 필터 체인을 따라 동작하면서, 토큰을 읽고 검증해주는 필터가 필요했다.
기능:
Authorization헤더에서 토큰 파싱- 검증 후 Provider로 인증 위임
- 성공 시 SecurityContextHolder에 주입
- 실패 시 직접 JSON 응답 반환
주요 포인트:
setAuthenticationManager()에ProviderManager를 수동 주입해야 함
4. SecurityConfig
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationProvider provider) throws Exception {
JwtFilter filter = new JwtFilter(new ProviderManager(provider));
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
...
return http.build();
}
이유: 커스텀 필터를 시큐리티 필터 체인에 정확히 위치시켜야 했다.
기능:
- 인증 제외 경로(PERMIT_ALL_URLS) 처리
ProviderManager를 통해 인증 매니저 구성- 커스텀 필터 등록
🤔 시행착오 & 느낀 점
시행착오들
JwtAuthenticationProvider를 필터에서 주입받지 못해 NPE 발생setAuthenticationManager()가 필터 생성자에 없으면 인증이 안 됨supports()를 구현하지 않으면 Provider가 동작 안 함- 필터 실패 시 Spring이
/error로 이동해서 원하는 응답 못 주는 문제 (직접 핸들링 추가)
느낀 점
- Spring Security는 커스터마이징이 어렵지만, 원리를 이해하면 굉장히 강력하다
- SecurityContext와 Provider의 개념을 정확히 잡아야 흐름이 명확해진다
- 직접 컨트롤 가능한 구조를 만들면 이후 유지보수와 확장에 유리하다
🚀 결론
이 글은 JWT 인증을 Spring Security 구조 안에서 깔끔하게 구현하고 싶은 사람들에게 도움이 될 것이다.
나처럼 필터를 커스터마이징하고 싶다면, AbstractAuthenticationProcessingFilter를 사용하는 방법도 고려해보길 바란다.
인증은 보안의 시작이고, 좋은 구조는 협업과 유지보수의 시작이다.