[Spring Boot] 나의 필터가 두 번 적용된 이유

OAuth2.0 구글 로그인 기능을 추가하던 중에 스프링 시큐리티(Spring Security)를 사용하여 JWT 인증을 구현하는 과정에서 한 번 적용되어야 할 필터가 두 번 적용되어 당황했던 경험이 있습니다. 이를 통해 알게 된 내용을 공유합니다.

필터 적용 이유

아래는 Spring Security의 설정을 관리하는 클래스입니다.

JwtAuthenticationFilter Bean을 의존 관계 주입 받아서 사용합니다.

configure 메서드의 가장 아래쪽에 보이는 addFilterBefore 메서드 호출을 통해 필터의 순서를 설정해줄 수 있습니다.
위 코드와 같이 설정하면 필터가 동작하는 순서는 JwtExceptionHandlerFilter → JwtAuthenticationFilter → UsernamePasswordAuthenticationFilter 가 됩니다.

JwtAuthenticationFilter에서 클라이언트의 요청에 포함되어 있는 AccessToken이 유효한 토큰인지 확인합니다.
JwtAuthenticationFilter에서 던진 예외는 JwtExceptionHandlerFilter에서 처리하게 됩니다.
UsernamePasswordAuthenticationFilter를 거치고 나면 사용자 인증이 완료됩니다.

필터 중복 적용 원인

JWT 인증과 관련된 필터를 GenericFilterBean을 상속 받아서 구현했고, 의존 관계 주입을 위해 Bean으로 등록한 것이 원인이었습니다.

아래는 Spring Boot Reference Documentation의 내용 중 일부입니다.

Spring Boot는 Bean들 중에 Filter가 있으면 자동으로 Filter Chain에 등록하도록 동작한다고 합니다.
그래서 SpringBoot에 의해서 Filter가 한 번 등록되었고, 제가 addFilterBefore 메서드를 통해 Filter를 등록하여 또 한 번 등록된 것입니다.

단, Spring Security의 security filter chain이 더 먼저 적용되며 특정한 위치에 JwtAuthenticationFilter가 등록되어야 하는 상황이므로 SpringBoot에 의해서 등록되는 필터는 잘못된 위치에 등록되고 있다고 할 수 있습니다.

Bean으로 등록된 필터를 원하는 위치에 한 번만 적용하는 방법

JwtAuthenticationFilter 구현 시 GenericFilterBean 대신 OncePerRequestFilter을 상속 받아서 구현하는 것으로 해결할 수 있습니다.

OncePerRequestFilter 클래스의 주석


OncePerRequestFilter는 요청 당 한 번만 적용되는 것을 보장해주는 필터입니다. 구현해야 하는 메서드가 GenericFilterBean은 doFilter, OncePerRequestFilter는 doFilterInternal 이며, 파라미터도 다릅니다. 아래의 코드를 참조하세요.

기존 코드 (GenericFilterBean 상속)

GenericFilterBean을 상속 받는 JwtAuthenticationFilter

현재 코드 (OncePerRequestFilter 상속)

OncePerRequestFilter를 상속 받는 JwtAuthenticationFilter

“[Spring Boot] 나의 필터가 두 번 적용된 이유”에 대한 4개의 생각

  1. jwtFilter를 @Component 를 사용해서 Bean에 주입하면 필터가 중복으로 도는 것은 똑같지 않나요?
    마지막 부분에 @Component 어노테이션을 빼는게 맞는거 아닌가 궁금하네요!

  2. 방금 전 댓글 남겼었는데 다시 확인해보니 security filter를 @Bean 으로 등록하지 않으면 @Component를 사용하여도 중복이 일어나지 않겠네요.
    security filter를 Bean 에 등록하는 방법과 jwtfilter를 @Component를 통해 Bean에 등록하는 방법 중 더 낫거나 옳은 방법이 있을까요?

    1. 안녕하세요! 제가 질문을 잘 이해한 것인지 모르겠으나 답변을 드려보겠습니다.

      bean은 스프링 프레임워크가 IoC 컨테이너에 등록하여 관리하는 객체입니다. bean들 사이의 의존 관계 주입까지 스프링 프레임워크가 해주기 때문에 매우 편리합니다. 기본적으로 bean은 하나만 존재할 수 있습니다. 같은 이름의 bean을 두 개 등록하려고 하면 예외가 발생합니다.

      security filter가 포스팅 내용 중 SecurityConfig를 말씀하시는 거라면 security filter는 프레임워크에서 관리하기 위해서 bean으로 등록되어야 합니다. 그리고 OncePerRequestFilter를 상속받아 구현한 jwtfilter을 bean으로 등록하여 security filter에서 jwtfilter를 주입 받아 사용하시는 걸 추천드립니다.
      주입 받은 jwtfilter를 configure 메서드에서 addFilterBefore를 사용해 원하는 위치에 끼워 넣으시면 됩니다.

      필터를 bean으로 등록할 경우 스프링 부트에 의해서 Embedded Servlet Container에 필터가 자동으로 적용됩니다. 자동 등록된 필터는 모든 요청에 적용되는데(/*), 각 single request thread에 한 번씩만 적용하고 싶은 필터가 있다면 OncePerRequestFilter를 상속 받아서 doFilterInternal을 구현하여 사용하시면 됩니다!

      답변을 작성하면서 저도 공부가 되었습니다. 이해를 돕기 위해 포스트에 답변 내용을 추가해야겠네요. 감사합니다.🙂

      1. 답변 주신 내용을 읽으면서 다시 한번 정리하니 이해가 되었습니다.
        이 글 이외에도 좋은 글들이 많은 것 같아 많이 배우고 갑니다!
        종종 궁금한 내용 질문 댓글 달겠습니다!
        답변 감사합니다!

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤