-
[Spring Security] Filter 에 대한 오해와 진실 (doFilter의 정확한 작동방식에 대한 심층분석과 Filter의 흐름) (Spring 200 OK but No Content)Back End/Spring Security 2021. 12. 8. 22:00
# [만연한 오해]
doFilter() 기준으로 위쪽이 API 호출 전처리 과정이고, 아래쪽이 API 호출 후처리 과정이다.
(틀린 말은 아니나 상황에 따라 아닌 경우가 있으므로, 이에대해 확실히 알고 넘어갈 필요가 있다.)
@Component //default 제일 마지막 필터로 public class TestFilter implements Filter{ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // API 호출 전 로직? chain.doFilter(request, response); // API 호출 후 로직? } }
# doFilter()의 작동 원리 (재귀호출 패턴)
doFilter() 메소드의 Implementation 을 따라가면, 크게 3가지 종류가 나온다.
[VirtualFilterChain, PassThroughFilterChain, MockFilterChain] 이다.
눈여겨 보아야 할 것은 VirtualFilterChain과 PassThroughFilterChain이며,
doFilter()의 종료시점에 우리가 흔히 알고 있는 방식인 Filter -> API Controller 로 Request와 Response가 전달된다.
(물론 Controller 앞단엔 Interceptor가 위치한다.)
그런데 VirtualFilterChain 의 doFilter()의 종료시점은 doFilter()의 호출횟수가 FilterChain에 등록된 Filter의 갯수와 같아지는 시점이다.
이상적으로는 맨 마지막 Filter의 doFilter()의 종료 시점부터 재귀호출이 종료되듯이 하나씩 return 되며 후처리 로직이 실행된다.
** doFilter()의 호출횟수가 Filter의 갯수보다 많을 경우, Thread 하나가 죽어버리며 오류를 뱉어내지만 Spring은 멀티쓰레드 환경이기에 서비스는 유지되며 API호출도 정상적으로 이루어진다.
** doFilter()의 호출횟수가 Filter의 갯수보다 적을 경우, API 에 도달하지 못하며 그 상태로 하나씩 return 되기 시작하면서 결국 default status code 인 200과 텅빈 Body 가 Response로 Client에게 내려간다. (Response 를 변형하지 않았을 경우)
** 재귀호출의 특성으로 인해, 아래의 예시가 탄생된다
# API 호출 이후, 작동하는 doFilter()이후 로직은 Filter(1)과 Filter(2)만 해당된다. (CASE 1 & CASE 2)
(Filter(3)의 doFilter() 이후 로직은 API호출전에 실행된다.)
이러한 특성으로 인해, Filter단에서 Controller로의 진입시기를 커스터마이징 할 수 있으며 return 키워드를 썼을때의 결과또한 가늠해볼 수 있다.
물론 진입시기를 커스터마이징하는건 이해하기 쉬운 로직을 위해 지양하는 것이 좋다.
하지만, 이 과정을 알아야 적절한 return 키워드 삽입으로 예기치 못한 오류를 피할 수 있다.
이러한 특성을 모르고 return 을 아무데나 끼워넣다간 '200 OK' 와 함께 텅빈 Body가 함께오는걸 목격할 수 있다. 😥
# [VirtualFilterChain, PassThroughFilterChain, MockFilterChain] 코드분석
[VirtualFilterChain]
사진에는 나와있지 않지만 this.currentPosition=0 으로 시작한다.
아래 코드를 보면, currentPosition 과 size 가 같으면 originalChain으로 넘어가게 된다.
그게 아닐 시, doFilter()가 호출될때마다 currentPosition++ 되는 형태이다.
(필터가 남아있으면 currentPosition++ 을 노린거겠으나, 그건 각 필터가 doFilter()를 한번씩만 호출했을 경우다)
** @Component 로 작성하는 필터들과 SpringSecurityFilterChian 에 있는 필터들은 VirtualFilterChain 방식으로 작동한다.
[MockFilterChain]
MockFilterChain의 종료시점은 Iterator가 더이상 다음 Filter를 가지지 않았을 때다.
해당 클래스명으로 검색결과, 테스팅에 쓰는 용도인 것 같다.
[PassThroughFilterChain]
PassThroughFilterChain 은 종료시점에 servlet.service에게 request 와 response 를 전달한다.
# 테스트 코드
@Component // default order = 2147483647 // 가장 뒤에 위치한 필터 @Slf4j public class TestFilter1 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("TestFilter1 호출됨"); //로직이 끝났으니 return 되어 TestFilter1을 호출한 TestFilter2의 chain.doFilter 가 종료된다. } } @Component @Slf4j @Order(value = 2147483646) public class TestFilter2 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("TestFilter2 호출됨"); chain.doFilter(request, response); // 실행순서 2번 log.info("TestFilter2 호출됨(after DoFilter[1])"); // API 호출 전 실행 chain.doFilter(request, response); // 실행순서 3번 log.info("TestFilter2 호출됨(after DoFilter[2])"); // API 호출 후 실행 //모든 로직 실행 후 return -> TestFilter3의 chain.doFilter()로 진행 } } @Component @Slf4j @Order(value = 2147483645) public class TestFilter3 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("TestFilter3 호출됨"); chain.doFilter(request, response); // 실행순서 1번 log.info("TestFilter3 호출됨(after DoFilter)"); // API 호출 후 실행 } }
# Reference 는 나의 삽질
728x90'Back End > Spring Security' 카테고리의 다른 글