프레임워크(Framework)/Spring

[Spring Security] 필터와 필터 체인(Filter Chain) 구현하기

잇트루 2022. 12. 27. 23:40
반응형

필터(Filter)

서블릿 필터(Servlet Filter)는 서블릿 기반 애플리케이션의 엔드포인트에 요청이 도달하기 전에

해당 요청을 가로채어 특정 처리를 할 수 있도록 해주는 Java 컴포넌트이다.

  • 위 이미지는 서블릿 기반 애플리케이션에서 서플릿 필터의 위치를 나타내고 있다.
  • 클라이언트가 요청을 전송하면 서블릿 필터에서 특정 처리를 한다.
  • 필터에서 모든 처리가 완료된 이후 DispatcherServlet에서 핸들러 매핑 작업을 진행한다.

 

필터 체인(Filter Chain)

필터 체인은 여러 개의 필터가 체인처럼 엮인 것을 말한다.

모든 요청(request)이 필터 체인을 거친 후에 서블릿 서비스에 도달할 수 있도록 한다.

 

필터와 필터 체인의 특성

  • 서블릿 필터 체인은 요청 URI path를 기반으로 HttpServletRequest를 처리한다.
    • 클라이언트가 요청을 보내면 서블릿 컨테이너는 요청 URI 경로를 기반으로 어떤 필터와 서블릿을 매핑할지 결정한다.
  • 필터는 필터 체인 안에서 순서를 지정할 수 있으며, 순서에 따라 동작하게 만들 수 있다.
    • 필터 체인에서 필터의 순서를 구성하는 것은 매우 중요하다.

 

스프링 부트에서 필터 순서를 지정하는 방법

  • 스프링 빈(Spring Bean)으로 등록되는 필터에 @Order 어노테이션 또는 Orderd 인터페이스를 구현하여 필터의 순서를 지정한다.
  • FilterRegistrationBean을 이용하여 필터의 순서를 명시적으로 지정한다.

 

 

Filter 인터페이스

필터를 구현하기 위한 핵심적인 역할을 하는 인터페이스이다.

직접 사용할 필터 클래스는 javax.servlet.Filter 인터페이스를 상속받아 기능들을 구현할 수 있다.

 

Filter 인터페이스

public interface Filter {

    public default void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    public default void destroy() {}
}

 

Filter 인터페이스를 상속받은 구현체

import javax.servlet.*;
import java.io.IOException;

public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 작업
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // (1) request를 이용하여 요청에 대한 필터 작업
        // ...

        // (2) 체인의 다음 필터를 처리
        chain.doFilter(request, response);

        // (3) response를 이용하여 응답에 대한 필터 작업
        // ...
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

init()

init() 메서드에서는 구현할 필터에 대한 초기화 작업을 진행한다.

 

doFilter()

doFilter() 메서드에서는 필터가 처리할 구체적인 로직을 구현한다.

  • (1) doFilter 메서드의 파라미터인 requset를 이용하여 chain.doFilter(request, response)가 호출되기 전에 할 수 있는 작업에 대한 코드를 작성한다. (전처리 작업)
  • (2) request에 대한 필터 작업을 거친 후 chain.doFilter(request, response)를 호출하여 요청에 대한 필터링 결과를 다음 필터에게 전달하게 된다. (다음 필터 호출)
  • (3) doFilter 메서드의 파라미터인 response를 이용하여 chain.doFilter(request, response)가 호출된 이후에 할 수 있는 작업에 대한 코드를 작성한다. (후처리 작업)

 

destory()

destory() 메서드는 필터가 컨테이너에서 종료될 때 호출되는 메서드이다. 필터가 사용한 자원을 반납하기 위한 처리 로직을 작성할 때 사용된다.

 

 

필터 체인 직접 구현

스프링 부트 환경에서 직접 필터를 만들어 어떻게 동작하는지 알아보도록 한다.

 

FilterController

요청과 응답을 보내기 위한 아주 간단한 컨트롤러를 작성한다.

@RestController
@RequestMapping("/filter")
@Slf4j
public class FilterController {

    @PostMapping
    public String filterTest(@RequestBody String message) {

        log.info("message={}", message);

        return "ok";
    }
}
  • 단순 테스트용으로 작성한 Controller로 입력으로 들어온 메시지를 로그로 출력하고 “ok”를 응답하게 된다.

 

FirstFilter

첫 번째 필터 역할을 하는 필터이다.

import javax.servlet.*;
import java.io.IOException;

public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("FirstFilter 생성");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("FirstFilter 시작");
        chain.doFilter(request, response);
        System.out.println("FirstFilter 종료");
    }

    @Override
    public void destroy() {
        System.out.println("FirstFilter destroy() 메서드 실행");
        Filter.super.destroy();
    }
}

 

SecondFilter

두 번째 필터 역할을 하는 필터이다.

import javax.servlet.*;
import java.io.IOException;

public class SecondFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("SecondFilter 생성");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("SecondFilter 시작");
        chain.doFilter(request, response);
        System.out.println("SecondFilter 종료");
    }

    @Override
    public void destroy() {
        System.out.println("SecondFilter destroy() 메서드 실행");
        Filter.super.destroy();
    }
}

 

FilterConfiguration

FirstFilter와 SecondFilter를 적용하기 위해 FilterConfiguration을 작성한다.

@Configuration
public class FilterConfiguration {

    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegister() {

        FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>(new FirstFilter());
        registrationBean.setOrder(1);

        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<SecondFilter> secondFilterRegister() {

        FilterRegistrationBean<SecondFilter> registrationBean = new FilterRegistrationBean<>(new SecondFilter());
        registrationBean.setOrder(2);

        return registrationBean;
    }
}
  • 두 개의 필터를 각각 스프링 빈으로 등록한다.
    • 스프링 부트에서의 서블릿 필터는 FilterRegistrationBean의 생성자로 Filter 인터페이스의 구현체를 등록할 수 있다.
  • 각 필터에 setOrder() 메서드를 통해 순서를 지정할 수 있다.
    • setOrder()의 값이 적을수록 먼저 실행된다.
    • registrationBean.setOrder(1) : FirstFilter가 첫 번째 순서로 적용된다.
    • registrationBean.setOrder(2) : SecondFilter가 두 번째 순서로 적용된다.

 

실행결과

프로젝트를 실행하면 스프링 부트가 빈들을 등록하면서 필터도 등록한다.

이때 FirstFilter와 SecondFilter의 init() 메서드가 실행되면서 초기화한다.

...
2022-11-19 23:36:32.598  INFO 14688 --- [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1059 ms
SecondFilter 생성
FirstFilter 생성
...

 

이후 Postman을 통해 요청을 보내면 다음과 같은 결과를 출력한다.

FirstFilter 시작
SecondFilter 시작
2022-11-19 23:38:59.354  INFO 14260 --- [nio-8080-exec-1] com.codestates.filter.FilterController   : message=hello
SecondFilter 종료
FirstFilter 종료
  • 요청이 Controller에 도달하기 전에 필터가 실행된다.
  • serOrder() 메서드로 지정한 순서대로 필터가 적용되는 것을 알 수 있다.
  • request에 대하여 모든 필터가 적용된 이후 Controller가 실행되어 message의 로그가 출력된다.
  • 이후 응답으로 반환되기 전에 response에 대하여 필터가 적용된다.
    • 이때 SecondFilter와 FirstFilter 순으로 처리하게 된다. (응답 시에는 역순)

 

이후 프로그램을 종료하면 destroy() 메서드가 실행된다.

SecondFilter destroy() 메서드 실행
FirstFilter destroy() 메서드 실행

Process finished with exit code 130
반응형