본문 바로가기

Spring Boot/Security

필터 아키텍처 : 서블릿 필터 기본 개념

- 서블릿 필터와 필터 체인의 구조, 동작 방식

- 직접 Servlet Filter를 구현하며 동작 원리 이해하기

- FilterChain의 실행 순서, 순서 지정 방법

- Spring Boot 환경에서 Filter를 등록하고 적용하는법

- 필터의 전처리 / 후처리 개념을 통해 보안 및 로깅과 같은 활용 방안 익히기


Servlet Filter, Filter Chain

1. Filter 개념

Servlet Filter는 서블릿 기반 애플리케이션의 엔드포인트에 요청이 도달하기 전에 중간에서 요청을 가로채 특정 작업을 수행할 수 있는 Java 컴포넌트

서블릿 기반 애플리케이션에서 Filter의 위치

 

 - 클라이언트가 서버로 요청을 보낼 때 가장 먼저 Filter를 거친다

 - Filter에서 전처리 / 후처리 로직을 실행한 후 DispatcherServlet으로 요청이 전달된다

 

 

 

2. Filter Chain 개념

Filter Chain은 여러개의 Filter가 연결되어 체인을 이루는 구조를 의미한다

 - 요청이 들어오면 체인 형태로 나열된 Filter를 순서대로 통과한다

 - 모든 Filter 처리가 끝난 뒤 Servlet이 실행된다

 

 

 

3. Filter와 Filter Chain 특성

 - 요청 URI path 기반 처리 : 서블릿 컨테이너는 요청 URI를 기반으로 어떤 Filter와 Servlet을 맵핑할지 결정한다

 - 순서 지정 가능 : Filter는 체인 내에서 실행 순서를 지정할 수 있다

 - Spring Boot 순서 지정 방법

  1. Filter에 @Order 애너테이션 추가 또는 Ordered 인터페이스 구현하기
  2. FilterRegistrationBean의 setOrder() 메서드 이용하기

클라이언트 응답에 따른 Filter 와 Servlet 흐름


Filter 인터페이스 구조

 - 일단 Filter를 implements 하는 클래스를 생성 및 구현함

 - Configuration에 등록하기

import jakarta.servlet.Filter; // jakarta.servlet.Filter임

public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        filterChain.doFilter(servletRequest, servletResponse);
    }

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

 

init() : Filter 초기화 작업을 수행하는 메서드

doFilter() : 전처리 -> 다음 필터 호출 -> 후처리하는 메서드

destroy() : 자원 반납 처리를 하는 메서드

 


Filter 구현

* 서블릿 필터와 리액티브 필터는 서로 다르다

 

1. FirstFilter.java (import를 보면 servlet 기반인 것을 알 수 있음)

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;

import java.io.IOException;

public class FirstFilter implements Filter {
    // Filter 초기화 작업을 하는 init 메서드
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("---- FirstFilter init ----");
    }

    // 필터 동작 로직을 담당하는 doFilter 메서드
    // 전처리 -> 다음 필터 호출 -> 후처리
    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("---- FirstFilter Filter start ----");
        filterChain.doFilter(servletRequest, servletResponse); // 다음 필터로 요청을 전달한다
        System.out.println("---- FirstFilter Filter end ----");
    }

    // 종료시 자원 해제를 하는 destroy 메서드
    // 자원을 반납함
    @Override
    public void destroy() {
        System.out.println("---- FirstFilter destroy ----");
        Filter.super.destroy();
    }
}

 

 

 

2. FilterConfiguration에 등록하기

import com.b1uffer.mysecurity.filter.FirstFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfiguration {
    // 서블릿 기반 Filter라서 리액티브 기반(WebFlux)에서는 필터가 동작하지 않는다
    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegistration() {
        FilterRegistrationBean<FirstFilter> firstFilterRegistration = new FilterRegistrationBean<>(new FirstFilter());
        return firstFilterRegistration;
    }
}

 

 

 

 

* 리액티브(WebFlux) 기반 필터

 

FirstWebFilter.java

import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

public class FirstWebFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        System.out.println("---- WebFilter start ----");
        Mono<Void> filterChain = chain.filter(exchange).doOnTerminate(() -> { // chain.doFilter가 아니라 filter임
            System.out.println("---- WebFilter end ----");
        });
        return filterChain;
    }

 

 

FilterConfiguration.java

import com.b1uffer.mysecurity.filter.webflux.FirstWebFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfiguration {
    @Bean
    public FirstWebFilter firstWebFilter() {
        return new FirstWebFilter();
    }
}

 


다중 Filter 구성

 

1. SecondFilter 클래스 생성 및 구현

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;

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 init ----");
    }

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("---- SecondFilter start ----");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("---- SecondFilter end ----");
    }

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

 

 

 

2. FilterConfiguration에 SecondFilter를 등록하고 순서 정해주기

import com.b1uffer.mysecurity.filter.FirstFilter;
import com.b1uffer.mysecurity.filter.SecondFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfiguration {
    // 서블릿 기반 Filter라서 리액티브 기반(WebFlux)에서는 필터가 동작하지 않는다
    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegistration() {
        FilterRegistrationBean<FirstFilter> firstFilterRegistration = new FilterRegistrationBean<>(new FirstFilter());
        firstFilterRegistration.setOrder(1); // 얘를 먼저 실행하고
        return firstFilterRegistration;
    }

    @Bean FilterRegistrationBean<SecondFilter> secondFilterRegistration() {
        FilterRegistrationBean<SecondFilter> secondFilterRegistration = new FilterRegistrationBean<>(new SecondFilter());
        secondFilterRegistration.setOrder(2); // 얘를 나중에 실행함
        return secondFilterRegistration;
    }
}

 

 

* 필터는 순서가 중요하다. setOrder()의 값이 낮을수록 먼저 실행된다.


Spring Security와 서블릿 필터의 관계

Spring Security는 서블릿 필터 기반으로 동작하는 보안 프레임워크다

  • 요청이 들어오면 Spring Security는 여러개의 보안 관련 필터를 체인으로 연결하여 실행한다
  • 인증(Authentication), 인가(Authorization), 보안 이벤트 처리 등 대부분의 기능이 필터 체인 안에서 이루어진다
  • 따라서 Spring Security를 이해하려면, 서블릿 필터의 동작 원리를 먼저 이해하는게 매우매우매우매우 중요하다

요청 -> Filter Chain을 거침 -> 인증 -> 인가 -> 반환

 

* 필터는 서블릿 컨테이너 레벨에서 동작하기 때문에 컨트롤러(Controller)보다 먼저 실행된다

* 필터에서 예외처리를 잘못하면, 응답 전체가 깨질 수 있으므로 주의가 필요하다

* 로깅, 인증, 인가와 같은 보안 관련 기능은 필터에서 처리하는게 일반적이다

* Spring Security 역시 이러한 구조를 기반으로 설계되었으므로, 필터 개념을 이해하면 Security 동작 원리를 쉽게 이해할 수 있다


핵심

  • Filter는 요청을 가로채서 전처리 / 후처리를 수행할 수 있다
  • 여러개의 Filter를 FilterChain으로 묶어서 순차적으로 실행할 수 있다
  • Spring Boot에서는 FilterRegistrationBean을 통해 Filter로 등록하고, setOrder()나 @Order 애너테이션으로 실행 순서를 지정한다
  • Filter의 실행 순서는 나머지 Filter와 Servlet의 동작에 직접적인 영향을 주므로 반드시 신중하게 설계해야한다