Spring

Spring Security 기본 5

RangA 2023. 6. 8. 21:50

Spring Security

Filter와 FilterChain 구현

Filter


  • 서블릿 필터(Servlet Filter)는 서블릿 기반 애플리케이션의 엔드포인트에 요청이 도달하기 전에 중간에서 요청을 가로챈 후 어떤 처리를 할 수 있도록 해주는 Java의 컴포넌트
  • 클라이언트가 서버 측 애플리케이션으로 요청을 전송하면 제일 먼저 Servlet Filter를 거치게 되며, Filter에서의 처리가 모두 완료되면 DispatcherServlet에서 클라이언트의 요청을 핸들러에 매핑하기 위한 다음 작업을 진행함

FilterChain

  • 여러 개의 Filter가 체인을 형성하고 있는 Filter의 묶음을 의미함

Filter와 FilterChain의 특성

  • Servlet FilterChain은 요청 URI path를 기반으로 HttpServletRequest를 처리하기 때문에 클라이언트가 서버 측 애플리케이션에 요청을 전송하면 서블릿 컨테이너는 요청 URI의 경로를 기반으로 어떤 Filter와 어떤 Servlet을 매핑할지 결정함
  • Filter는 Filter Chain 안에서 순서를 지정할 수 있으며 지정한 순서에 따라서 동작하게 할 수 있음
  • Filter Chain에서 Filter의 순서는 매우 중요하며 Spring Boot에서 여러 개의 Filter를 등록하고 순서를 지정하기 위해서는 두 가지 방법을 적용할 수 있음
    1. Spring Bean으로 등록되는 Filter에 @Order 애너테이션을 추가하거나 Orderd 인터페이스를 구현해서 Filter의 순서를 지정할 수 있음
    2. FilterRegistrationBean을 이용해 Filter의 순서를 명시적으로 지정할 수 있음

Filter 인터페이스

  • Servlet Filter의 기본 구조
    • (1)의 init() 메서드에서는 생성한 Filter에 대한 초기화 작업을 진행할 수 있음
    • (2)의 doFilter() 메서드에서는 해당 Filter가 처리하는 실질적인 로직을 구현함
      • (2-1)에는 request를 이용해 (2-2)의 chain.doFilter(request, response)가 호출되기 전에 할 수 있는 전처리 작업에 대한 코드를 구현할 수 있음
      • (2-3)에는 response를 이용해 (2-2)의 chain.doFilter(request, response)가 호출된 이후에 할 수 있는 후처리 작업에 대한 코드를 구현할 수 있음
    • (3)의 destroy() 메서드는 Filter가 컨테이너에서 종료될 때 호출되는데 주로 Filter가 사용한 자원을 반납하는 처리 등의 로직을 작성하고자 할 때 사용됨
public class FirstFilter implements Filter {
     // (1) 초기화 작업
     public void init(FilterConfig filterConfig) throws ServletException {

     }

     // (2)
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                          throws IOException, ServletException {
        // (2-1) 이곳에서 request(ServletRequest)를 이용해 다음 Filter로 넘어가기 전처리 작업을 수행한다.

        // (2-2)
        chain.doFilter(request, response);

        // (2-3) 이곳에서 response(ServletResponse)를 이용해 response에 대한 후처리 작업을 할 수 있다.
     }

     // (3)
     public void destroy() {
        // (5) Filter가 사용한 자원을 반납하는 처리
     }
  }



Filter 예제

1. 첫 번째 Filter 구현

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("========First 필터 시작========");
        chain.doFilter(request, response);
        System.out.println("========First 필터 종료========");
    }

    @Override
    public void destroy() {
        System.out.println("FirstFilter Destory");
        Filter.super.destroy();
    }
}

2. FirstFilter를 적용하기 위한 FilterConfiguration 구성

import book.study.security.FirstFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfiguration {

    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegister()  {
        FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>(new FirstFilter());
        return registrationBean;
    }
}

3. 애플리케이션 실행

  • 애플리케이션 실행 시, 가장 먼저 init() 메서드가 실행되며 아래와 같은 로그가 출력됨
FirstFilter 생성됨

  • 컨트롤러의 핸들러 메서드로 요청을 보낼 경우
    • doFilter -> controller 동작 -> destroy 메서드의 형태로 Filter가 동작하면서 아래 코드와 유사한 로그가 출력됨
========First 필터 시작========
Controller 호출
========First 필터 종료========

4. 두 번째 Filter 구현

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("==========Second 필터 시작==========");
        chain.doFilter(request, response);
        System.out.println("==========Second 필터 종료==========");
    }

    @Override
    public void destroy() {
        System.out.println("SecondFilter가 사라짐");
        Filter.super.destroy();
    }
}

5. FilterConfiguration에 두 번째 Filter 등록

  • 두 개의 Filter가 지정된 순서로 실행되도록 (1), (2)와 같이 registrationBean.setOrder() 메서드로 순서를 지정할 수 있음
  • registrationBean.setOrder()의 파라미터로 지정한 숫자가 적은 숫자일수록 먼저 실행됨
import book.study.security.FirstFilter;
import book.study.security.SecondFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegister()  {
        FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>(new FirstFilter());
        registrationBean.setOrder(1); // (1)
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<SecondFilter> secondFilterRegister()  {
        FilterRegistrationBean<SecondFilter> registrationBean = new FilterRegistrationBean<>(new SecondFilter());
        registrationBean.setOrder(2); // (2)
        return registrationBean;
    }

}

6. 애플리케이션 재실행

========First 필터 시작========
==========Second 필터 시작==========
Controller 호출
==========Second 필터 종료==========
========First 필터 종료========