[SpringBoot] Filter vs Interceptor

이의찬·2023년 7월 6일
0

Springboot

목록 보기
1/12

점프 투 스프링부트 3-05 파트에 스프링 시큐리티 내용이 나온다.

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'

스프링 시큐리티는 인증과 권한에 대해 Filter 흐름에 따라 처리하고 있다. Filter에 대해 알아보고 추가로 Interceptor와 비교해보자.

필터 (Filter)


필터는 디스패처 서블릿에 요청이 전달되기 전/후에 url 패턴에 맞는 모든 요청에 대해 부가적인 작업을 처리할 수 있다.

💡 디스패처 서블릿(In)

클라이언트에서 요청을 받게 되면 톰캣 같은 서블릿 컨테이너가 요청을 받게된다. 이 요청을 프론트 컨트롤러인 디스패처 서블릿이 받고 적합한 컨트롤러에게 위임해준다.

https://mangkyu.tistory.com/18

스프링 컨테이너가 아닌 톰캣과 같은 서블릿 컨테이너에 의해 관리되는 것이고 디스패처 서블릿 전/후에 처리하는 것이다.

필터는 기본적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할 수 있다. 대표적으로 보안 공통 작업이 있다. 올바른 요청이 아닐 경우 차단하기 때문에 스프링 컨테이너까지 전달되지 못하기 때문에 안정성을 높일 수 있다. 또한 이미지나 데이터 압축, 문자 인코딩, 비밀번호 암호화 등 있다.

필터를 추가하기 위해서는 jakarta.servlet의 Filter 인터페이스를 구현해야 하고 3가지 메서드를 가지고 있다.

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

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

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

    }

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

    init 메서드는 필터 객체를 초기화하고 서비스에 추가하기 위한 메서드이다. 서블릿 컨테이너는 init 메서드를 한 번 호출하여서 필터 객체를 초기화 한다. 이후 요청은 doFilter()를 통해 처리된다.

  2. doFilter()

    doFilter 메서드는 디스패처 서블릿으로 전달되기 전 url 패턴에 맞는 모든 요청이 서블릿 컨테이너에 의해 실행되는 메서드이다.

  3. destroy()

    destroy 메서드는 필터 객체를 제거하고 자원을 반환한다. 서블릿 컨테이너에 의해 한 번 호출되며 doFilter에 의해 처리되지 않는다.

인터셉터 (Interceptor)


인터셉터는 Spring이 제공하는 기술로, 디스패처 서블릿이 컨트롤러를 호출하기 전/후에 요청과 응답을 참조하고 가공하는 기능을 제공한다.

필터는 서블릿 컨테이너에서 동작, 인터셉터는 스프링 컨텍스트에서 동작

*실제로 인터셉터는 컨트롤러로 요청을 보내지 않고 순서만 나타냈다.

디스패처 서블릿은 인터셉터가 등록되어 있다면 순차적을 인터셉터를 거쳐 컨트롤러를 실행되도록 한다.
실제 코드는 예제에서 알아보자.

예제


필터와 인터셉터를 추가하여 localhost:8080/test에 요청을 보내보자.

Controller

@RestController
public class MainController {
    
		@GetMapping("/test")
    public String test() {
        log.info("3. /test controller 호출");
        return "ok";
    }
}

Filter

Filter를 스프링 IoC컨테이너에 등록한다. @Component

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

import java.io.IOException;

@Slf4j
@Component
public class FilterExample implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        log.info("1. filter 전처리, 요청 url = {}", httpServletRequest.getRequestURL());

        chain.doFilter(httpServletRequest, httpServletResponse);

        log.info("5. filter 후처리, 응답");
    }
}

Interceptor

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Slf4j
public class InterceptorExample implements HandlerInterceptor {

		@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("2. interceptor 전처리, 요청된 url = {}", request.getRequestURL());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("4. interceptor 후처리");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

먼저 HandlerInterceptor를 구현한다.

  1. **preHandle** 메서드는 interceptor 전처리를 한다. 컨트롤러에 접근하기 직전에 실행되는 메서드이다.
  2. **postHandle**은 컨트롤러 후, View로 결과를 전달하기 전 실행되는 메서드이다.

다음은 인터셉터가 작동할 수 있도록 빈으로 등록해 주어야한다.

ConfigExample

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class ConfigExample implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new InterceptorExample())
                .excludePathPatterns("/css/**", "/images/**");
    }
}

WebMvcConfigurer의 addInterceptors 메서드를 구현하면된다.

  1. **addInterceptor**에 위에 만들어 둔 Interceptor를 등록해준다.
  2. **excludePathPatterns**는 제외할 url pattern을 적어준다.

서버를 실행하여 localhost:8080/test 호출해자

결과

위에 있는 그림과 같이 필터 전처리인터셉터 전처리controller인터셉터 후처리필터 후처리 순으로 실행되는 것을 알 수 있다.

정리


필터인터셉터
컨테이너서블릿 컨테이너스프링 컨테이너
스프링 예외처리 여부xo
Request/Response 객체 조작 여부ox
용도1. 공통된 보안 및 인증/인가 작업
2. 모든 요청에 대한 로깅
3. 이미지/데이터 압축 문자열 인코딩

1. 세부적인 보안 및 인증/인가 작업
2. API 호출에 대한 로깅
3. Controller로 넘겨주는 데이터 가공

💡 Request/Response 객체 조작 여부

여기서 조작한다는 것은 일부 변경이 아니라 아예 다른 객체로 바꾼다는 의미이다.
인터셉터의 preHandle을 보면 리턴 타입이 boolean이기 때문에 바꿀 수 없지만 필터는 doFilter 직접 Request/Response 객체를 넣을 수 있다.

0개의 댓글