본문 바로가기

Development/Spring

[Spring] Spring Security + JWT: JwtAuthenticationFilter

JwtAuthenticationFilter

JwtAuthenticationFilter는 JWT 토큰으로 인증하고 SecurityContextHolder에 추가하는 필터를 설정하는 클래스이다.
스프링 부트에서는 Filter를 여러 방법으로 구현할 수 있는데, 가장 편한 구현 방법은 Filter를 상속받아 사용하는 것이다.
대표적으로 GenericFilterBeanOncePerRequestFilter 두 가지 상속 객체를 사용한다.

GenericFilterBean을 사용한 구현

public class JwtAuthenticationFilter extends GenericFilterBean {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) servletRequest);

        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }
}
  • 토큰 추출: jwtTokenProvider.resolveToken 메서드를 호출하여 요청 헤더 또는 파라미터에서 JWT 토큰을 가져온다.
  • 토큰 검증: 가져온 토큰이 null이 아니고 유효한지 jwtTokenProvider.validateToken 메서드를 이용하여 검증한다.
  • 인증 설정: 유효한 토큰일 경우 jwtTokenProvider.getAuthentication 메서드를 이용하여 토큰으로부터 인증 정보(Authentication) 객체를 생성한다. 생성된 인증 정보를 SecurityContextHolder.getContext().setAuthentication 메서드를 통해 Spring Security 컨텍스트에 설정한다.
  • 필터 체인 진행: filterChain.doFilter 메서드를 호출하여 다음 필터로 요청을 전달한다.

OnePerRequestFilter을 사용한 구현

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest servletRequest,
        HttpServletResponse servletResponse,
        FilterChain filterChain) throws ServletException, IOException {
        String token = jwtTokenProvider.resolveToken(servletRequest);
        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
  • 토큰 추출: jwtTokenProvider.resolveToken 메서드를 호출하여 요청 헤더 또는 파라미터에서 JWT 토큰을 가져온다.
  • 토큰 검증: 가져온 토큰이 null이 아니고 유효한지 jwtTokenProvider.validateToken 메서드를 이용하여 검증한다.
  • 인증 설정: 유효한 토큰일 경우 jwtTokenProvider.getAuthentication 메서드를 이용하여 토큰으로부터 인증 정보(Authentication) 객체를 생성한다. 생성된 인증 정보를 SecurityContextHolder.getContext().setAuthentication 메서드를 통해 Spring Security 컨텍스트에 설정한다.
  • 필터 체인 진행: filterChain.doFilter 메서드를 호출하여 다음 필터로 요청을 전달한다.

GenericFilterBean vs OncePerRequestFilter

  • GenericFilterBean은 기존 필터에서 가져올 수 없는 스프링의 설정 정보를 가져올 수 있게 확장된 추상 클래스이다.
  • 다만 서블릿은 사용자의 요청을 받으면 서블릿을 생성해서 메모리에 저장해두고 동일한 클라이언트의 요청을 받으면 재활용하는 구조여서, GenericFilterBean을 상속받으면 RequestDispathcer에 의해 다른 서블릿으로 디스패치되면서 필터가 두 번 실행 되는 현상이 발생할 수 있다.
  • GenericFilterBean은 매 요청마다 실행된다.
  • 이 문제를 해결하기 위해 OncePerRequestFilter가 등장했다.
  • 이 클래스는 GenericFilterBean을 상속받고 있지만, 이 클래스를 상속받아 구현한 Filter는 매 요청마다 한 번만 실행되게끔 구현되어 있다.

1. 요청당 실행 횟수

  • GenericFilterBean: 매 요청마다 실행
  • OncePerRequestFilter: 요청당 한 번만 실행

2. 동작 방식

  • GenericFilterBean:
    • doFilter 메서드를 직접 구현
    • FilterChain.doFilter를 호출하여 다음 필터로 요청을 전달
    • 서블릿마다 매 요청마다 호출
  • OncePerRequestFilter:
    • doFilterInternal 메서드를 구현
    • doFilter 메서드에서 doFilterInternal 메서드를 한 번만 호출
    • 스레드 기반으로 요청당 한 번만 호출

3. 용도

  • GenericFilterBean:
    • 요청 전/후 처리, 로그 기록, 인증/권한 처리 등 다양한 용도로 사용
    • 간단한 필터 구현에 적합
  • OncePerRequestFilter:
    • 중복 실행을 방지해야 하는 필터에 적합
    • 예: 인증 필터, CSRF 토큰 검증 필터

4. 상속 관계

  • GenericFilterBean: javax.servlet.Filter 인터페이스를 구현
  • OncePerRequestFilter: GenericFilterBean을 상속

5. 주의 사항

  • GenericFilterBean:
    • 매 요청마다 실행되므로 성능에 영향을 줄 수 있다.
    • 스레드 안전하지 않을 수 있다.
  • OncePerRequestFilter:
    • 요청당 한 번만 실행되므로 성능 향상에 도움
    • 스레드 안전

참고