새소식

TIL

[TIL] 230531 <Spring> Filter

  • -

 


  • 일반적인 자바 웹 애플리케이션에서 클라이언트(사용자의 요청)는 HTTP나 HTTPS 프로토콜을 사용해서 서버의 자원에 접근하고
    클라이언트(사용자) 요청은 서버의 서블릿에서 처리
  • 서블릿은 HTTP 요청을 받아 처리한 후 HTTP 응답을 클라이언트에게 반환함
    • 일정을 등록하고 id를 반환
    • 일정을 조회해서 반환
  • 스프링은 DispatcherServlet이 서블릿 역할을 담당하고 모든 요청을 처리함
  • 요청-응답 처리과정 중, 중요한 역할을 하는 주요 컴포넌트가 "필터"
  • 아래의 그림과 같이 서블릿 앞에 위치해 요청과 응답을 가로채서 변경가능
    • 한 개 이상의 필터는 필터 체인으로 구성 되어 있으며, 필터 체인에 속한 모든 필터는 요청/응답을 가로채서 변경가능

 

특징

  • Spring에서 모든 요청을 하나의 서블릿인 DispatcherServlet 에서 처리하는 것처럼
    하나의 특별한 필터인 DelegatingFilterProxy에 의해 활성화됨
  • DelegatingFilterProxy는 스프링 부트의 자동 구성으로 컨테이너에 등록되고 모든 요청을 가로챔

 


Filter 인터페이스

package jakarta.servlet;

import java.io.IOException;

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

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

 

Filter의 라이프 사이클

  • init()
    • 서블릿이 필터를 등록하는 초기화 과정에서 호출
  • doFilter()
    • 필터 기능을 하는 메소드
    • 요청, 응답, FilterChain 객체에 접근할 수 있음
    • FilterChain 객체는 다음 필터를 호출
  • destory()
    • 서블릿 컨테이너가 필터를 제거할 때 호출

 

사용 예시 (LoggingFilter)

package com.sparta.springauth.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j(topic = "LoggingFilter")
@Component
@Order(1)
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 전처리
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();
        log.info(url);

        chain.doFilter(request, response); // 다음 Filter 로 이동

        // 후처리
        log.info("비즈니스 로직 완료");
    }
}

 


FilterChain

  • 필터 체인은 말그대로 연쇄적으로 거쳐서 흘러가게 드는 역할
  • 필터는 자신의 작업이 완료되면 FilterChain을 호출해서 요청이 다음 필터를 통과하게 만듦

 

▶ 책임 연쇄 패턴

각각의 인스턴스의 책임이 체인처럼 연쇄되어 있음
잠재적 수신자의 동적 체인을 따라 수신자 중 하나에 의해 요청이 처리될 때까지 요청을 순차적으로 전달

장점

  • 요청을 체인 아래로 더 이상 전달하지 않고 추가 처리를 사실상 중지하는 결정 내릴 수 있음
  • 요청의 처리 순서를 제어 가능
  • 단일 책임 원칙 → 작업을 호출하는 클래스들을 작업을 수행하는 클래스와 분리가능
  • 개방/폐쇄 원칙  기존 클라이언트 코드를 손상하지 않고 새 핸들러들을 도입가능

 

 

FilterChain 인터페이스

package jakarta.servlet;

import java.io.IOException;

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ㄴ Filter의 doFilter() 메소드 내부에 filterChain의 doFilter 메소드를 무조건 호출해야함!

 


  • Spring Security는 두가지 필터인 DelegatingFilterProxyFilterChainProxy 를 사용
    ㄴ 두 필터는 스프링 시큐리티 인프라 스트럭처를 통과하게 만드는 진입점 역할
  • SecurityFilterChain 인터페이스도 존재

DelegatingFilterProxy

  • 스프링 시큐리티는 DelegatingFilterProxy를 통해 서블릿 필터와 스프링과의 간극을 줄임
  • DelegatingFilterProxy는 동일하게 서블릿 필터이며 서블릿 컨테이너가 관리
    ㄴ 서블릿 컨테이너가 관리하는 Filter 구현체는 스프링 프레임 워크의 기능을 활용할 수 없음!!!
    • 별도의 Filter 구현체를 하나 더 정의해 빈으로 등록
    • 이 Bean을 DelegatingFilterProxy 안에서 대리인 역할을 하는 delegate(대리자)로 설정
  • 즉, DelegatingFilterProxy는 서블릿 필터로서 런타임으로부터 가로챈 요청을 delegate인 bean에 위임해서 처리함!!!!!

FilterChainProxy

  • 위에서 말한 것처럼 Bean에 등록되어 대리인 역할을 하는 Filter InterFace의 구현체!!
  • 하나 이상의 SecurityFilterChain 을 가질 수 있음
  • DelegatingFilterProxy가 가로챈 요청을 FilterChainProxy에서 SecurityFilterChain에 요청을 흘려 보내 필터를 통과하게 만듦

SecurityFilterChain

  • 해당 인터페이스는 matches()getFilters() 메소드를 가지고 있다.
  • matches()
    • SecurityFilterChain 구현체가 요청을 처리하는데 적합한지를 판별
    • 스프링 시큐리티는 RequestMatcher 인터페이스와 여러 구현체를 제공
    • AnyRequestMatcher를 사용하면 모든 HTTP 요청이 해당 SecurityFilterChain 구현체를 통과
    • Url 기반으로 판별하도록 AntPathRequestMatcher도 제공
  • getFilters()
    • matches()에서 true를 반환하면 SecurityFilterChain에 있는 모든 필터를 거침
  • 스프링 시큐리티의 기본 설정을 한다면 SecurityFilterChain 구현체인 DefaultSecurityFilterChain 이 사용됨

 


1.  @Configuration

  • 설정을 위한 별개의 파일(@Configuration이 붙은 객체)이 필요
  • setOrder()를 통해 순서를 지정 가능
  • addUrlPatterns()을 통해 베이스 URL 및 Whitelist를 설정가능
  • 직접 Filter를 Bean으로 등록하여 컨트롤 할 수 있다는 점에서 가장 관리측면에서 편리
@Configuration
public class FilterConfiguration {

  /**
   * 로그 관련 필터 추가.
   *
   * @return
   */
  @Bean
  public FilterRegistrationBean loggingFilter() {
    FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.setFilter(new LoggingFilter());
    filterRegistrationBean.setOrder(1);
    filterRegistrationBean.addUrlPatterns("/*");

    return filterRegistrationBean;
  }
}

 

2.  @Component

  • 설정을 위한 별개의 파일이 필요없음
  • @Order 애노테이션을 이용해 순서설정가능
  • 기본 URL Pattern이 /* 이며 설정불가
@Slf4j
@Component // 추가된 두 가지
@Order(1)  // 추가된 두 가지 
public class LoggingFilter implements Filter {

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
      FilterChain filterChain) throws IOException, ServletException {
    // 전처리
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    String url = httpServletRequest.getRequestURI();
    log.info(url);

    filterChain.doFilter(servletRequest, servletResponse); // 다음 Filter 로 이동
  }
}

 

3.  @WebFilter + @ServletComponentScan

  • 설정을 위한 별개의 파일이 필요없음
    • 대신, 애플리케이션 실행되는 메인 객체위에 @ServletComponentScan을 사용해야함
  • @Order를 이용한 순서 등록을 사용불가
    • 각 필터에 대한 순서를 보장불가
  • @WebFilter의 value나 urlPatterns 파라미터를 이용해 whitelist 방식으로 베이스 URL을 설정가능
    • @WebFilter("/filter/*")
    • @WebFilter({"/login", "/items"})
    • @WebFilter(urlPatterns = "/filter/*")
    • @WebFilter(urlPatterns = {"/login", "/items"})
@Slf4j
@WebFilter
public class LoggingFilter implements Filter {

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
      FilterChain filterChain) throws IOException, ServletException {
    // 전처리
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    String url = httpServletRequest.getRequestURI();
    log.info(url);

    filterChain.doFilter(servletRequest, servletResponse); // 다음 Filter 로 이동
  }
}
@ServletComponentScan
@SpringBootApplication
public class FilterPatternApplication {

  /**
   * main.
   *
   * @param args argument.
   */
  public static void main(String[] args) {
    SpringApplication.run(FilterPatternApplication.class, args);
  }

}

 

4.  @WebFilter + @Component

  • 2번과 3번을 모두 사용
  • 설정을 위한 별개의 파일이 필요없음
    • 컴포넌트 스캔 방식을 사용하기 때문에 @ServletComponentScan도 필요없음!
  • @Order 애노테이션을 이용해 순서를 설정가능
  • @WebFilter의 value나 urlPatterns 파라미터를 이용해 베이스 URL이나 Whitelist 방식으로 설정가능
    • @WebFilter("/filter/*")
    • @WebFilter({"/login", "/items"})
    • @WebFilter(urlPatterns = "/filter/*")
    • @WebFilter(urlPatterns = {"/login", "/items"})
  • 어노테이션이 기본으로 3개가 필요
@Slf4j
@WebFilter
@Component
@Order(1)
public class LoggingFilter implements Filter {

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
      FilterChain filterChain) throws IOException, ServletException {
    // 전처리
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    String url = httpServletRequest.getRequestURI();
    log.info(url);

    filterChain.doFilter(servletRequest, servletResponse); // 다음 Filter 로 이동
  }
}
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.