KKanging

[SpringSecurity] security filter 커스텀 기초 와GenericFilterBean,OncePerRequestFilter 본문

백엔드/SpringSecurity

[SpringSecurity] security filter 커스텀 기초 와GenericFilterBean,OncePerRequestFilter

천방지축 개발자 2024. 5. 22. 14:04

SecurityFilterChain 의 Filter들은 어떻게 구현되어 있을까?

기본 디폴트로 Security 에서 제공하는 Filter들의 상속도를 그려봤다.

위에 구조를 자세히 보면 제일 최상위 서블릿에서 제공하는 Filter 와 Filter를 상속받는
GenericFilterBean 그리고 GenericFilterBean를 상속 받는 OncePerRequestFilter 가 있는 것을 볼 수 있다.

위에 의존관계가 복잡해 보이지만 패턴이 보인다.

SecurityFilterChain에 구현체인 Filter들은 모두 GenericFilterBean 또는  OncePerRequestFilter 
를 상속 받음을 볼 수 있다.

그래서 우리가 기본 디폴트로 제공되는 Filter 말고 커스텀을 할려면 위에처럼 
GenericFilterBean 또는  OncePerRequestFilter를 상속 받으면 된다.

그럼 GenericFilterBean 또는  OncePerRequestFilter 중에 무엇을 상속받아야 할까?

그것은 GenericFilterBean OncePerRequestFilter의 역할을 알아야한다.

 

GenericFilterBean ,OncePerRequestFilter 기능

GenericFilterBean

큰 기능이 추가된 것은 아니고 Filter 인터페이스는 Servlet에서 지원해주는 인터페이스이고 GenericFilterBean은 Spring에서 지원하는 추상 클래스이다.

기존 Filter에서 Spring 의 Bean을 활용할 수 있다는 점이 추가되었다.

 

OncePerRequestFilter

OncePerRequestFilter와 GenericFilterBean 과의 가장 큰 차이점은 1개의 요청당 1번의 호출만
한다는 점이다.

OncePerRequestFilter또한 GenericFilterBean를 상속 받아서 Spring Bean의 기능과 Filter 기능을
사용할 수 있고 거기에 추가적으로 Request 당 한번만 호출되는 기능을 추가로 구현하였다.

OncePerRequestFilter는 위에 상수로 이미 Filtered 된 것을 확일할 접미사를 멤버로 가진다. 

 

OncePerRequestFilter 의 doFilter를 파헤쳐 보자

우선 doFilter의 초반에는 Http 요청을 검증한다.

 

현재 Filter의 이름을 가지고와서 Filtered 접미사를 붙인다.

그리고 이미 해당 필터이름+접미사 를 지난 request인지 확인한다.

 

skipDispatch 는 비동기 통신인지 확인한다 ( 한사용자 당 하나의 쓰레드를 주는 환경에서의 체크이므로) 추가적으로 요청에 대한 에러 요청인지 검증한다.

만약 이미 현재 필터를 지났었던 request라면 Filter 로직을 실행하지 않는다.

여기서 의문점이 있다.

doFilterNestedErrorDispatch 라는 메서드이다 

이는 밑에서 한번 더 다루겠다.

위에 조건식을 다 통과한 request라면 이 Filter를 수행했다는 값을 request 변수에 담고

핵심 로직을 구현한 doFilterInternal 을 실행한다.

즉 OncePerRequestFilter 를 상속받은 Filter를 구현한다면 Filter의 핵심로직은  doFilter가 아닌 doFilterInternal 에 구현해야한다

 

GenericFilterBean ,OncePerRequestFilter를 상속 받을 때 주의점

  • OncePerRequestFilter의 핵심 로직은 doFilterInternal 에
  •  

위에서 설명 했듯이 doFilterInternal에 핵심 로직을 구현해야한다.

  • redirect 상황에서 조심하자

GenericFilterBean 은 특별한 추가 기능이 없으므로 그냥 쓰면된다.

하지만

OncePerRequestFilter를 상속 받는다는 것은 기본적으로 1개의 요청당 1번만 실행했으면 바라는 상황이라는 점이다.

그냥 평범한 로직이 실행된다면 바라는 대로 해당 필터는 1번만 실행될 것이다.

하지만 문제는 redirect 와 forward 상황에서 발생한다.

 

forward 상황은 애플리케이션 내부에서 재전송이 일어난다. Controller에서 응답으로 “forward:/” 나 아니면 ExceptionHandler로 Handle 되지 않은 Exception이 forward 상황에 해당한다.

 

내부적으로 forward 되기 때문에 같은 쓰레드 환경에서 발생한다. 따라서 forward 되는 상황에서는 OnecePerRequestFilter 로 상속받은 필터는 실행되지 않는다

→ 여기까지 문제되지 않는다.

하지만 redirect 상황에서는 다르다

redirect 는 3xx 상태코드를 반환하여 클라이언트가 다시 요청을 보내도록 유도한다.

이때는 이전 요청과는 독립적인 요청이므로 OnecePerRequestFilter 의 필터가 실행됨을 유의해야한다.

  • 에러 반환?

위에 코드에서 다음과 같은 코드를 본적있다.

위 코드는 이미 지났었던 request면 Filter로직을 실행하지 않는다는 점이다.

하지만 DispatcherType이 Error 면 다음 Filter로 넘기는 함수가 아닌 이상한 함수를 실행한다.

 

구현된 코드를 보니까 그냥 똑같이 다음 Filter로 넘기는 코드가 적혀 있다.

무슨 차이일까

우선 DispatcherType 코드이다.

ERROR 와 FORWARD 가 구분되어 있다.

ERROR는 애플리케이션에서 Handle 되지 않은 Exception이 톰켓까지 전달되어 톰켓 내부에서 재전송 된 것이고

FORWARD는 Controller 에서 forward를 반환한 것이다.

같은 애플리케이션에서 재전송이 일어난 것이지만 엄연히 다른 Type으로 분류한 것이다.

그래서 만약 DispathcerType 이 ERROR 에서 다르게 로직을 실행하고 싶으면 doFilterNestedErrorDispatch를 오버라이딩 하면 된다.

추가

원래 필터는 Default 로 DispatcherType 이 REQUEST 타입의 요청만 걸린다.

하지만 자기가 커스텀한 필터가 ERROR 에도 걸리길 원한다면

@Configuration
public class DebugConfig {

    @Bean
    public FilterRegistrationBean<DebugFilter> debugFilter() {
        FilterRegistrationBean<DebugFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new DebugFilter());
        filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE + 100);
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST
                , DispatcherType.ERROR
        );
        return filterRegistrationBean;
    }
}

위에 내용 처럼 등록을 해주어야한다.

참고로 SpringSecurity의 기반이 되는 DelegatingFIlterProxy 이건 ERROR에도 동작하게 등록되어 있다.

Tomcat 상세 동작, 예외 처리 심화 (코드 까보기) (velog.io)

참고 문헌

Tomcat 상세 동작, 예외 처리 심화 (코드 까보기) (velog.io)