[TodayILearn] doFilter 메소드란?

2024. 4. 15. 14:39· 🗄️Backend/SpringBoot
목차
  1. TIL - doFilter 메소드에 대해서
  2. QnA
  3. 레퍼런스
반응형

TIL - doFilter 메소드에 대해서

개발하면서 매우 궁금했던 게 바로 chain.doFilter(request, response); 메소드였다. JwtAuthorizationFilter에 존재하는 이 doFilter는 도대체 무엇을 하는 애일까? 내 첫 번째 TIL에서 공부했던 게 바로 doFilter 안의 인자로 들어가는 HttpServletRequest request와 HttpServletResponse response였는데, 얘들은 일종의 요청과 응답을 나타내는 객체라는 것을 알게되었다. 그러나 이제 궁금한 점은 그렇다면 요청과 응답을 동시에 가지고 있는 doFilter 메소드는 무엇을 하는 것 인지였다.

oracle에 따르면, 필터는 리소스(서블릿 또는 정적 콘텐츠)에 대한 요청이나 리소스의 응답, 또는 둘 다에 대해 필터링 작업을 수행하는 객체라고 한다. 여기서 약간의 힌트를 얻을 수 있다. 우리는 JWT 토큰을 request에 대해서만 검증하면 되긴 하지만, 일반적으로 필터의 일종인 JwtAuthorizationFilter은 리소스의 요청/응답 모두에 필터링을 할 수 있는 객체인 것이다. 조금 더 구체적인 메소드의 동작 방식은 다음과 같다:

  1. request를 검토한다.
  2. 선택적으로 request 객체(HttpServletRequest request)를 사용자 정의 구현으로 래핑하여 입력에서 contents 또는 header를 필터링한다.
  3. 선택적으로 response 객체(HttpServletResponse response)를 사용자 정의 구현으로 래핑하여 출력에서 콘텐츠 또는 헤더를 필터링한다.
  4. FilterChain 개체(chain.doFilter())를 사용하여 체인의 다음 엔터티를 호출하거나, 또는 request 처리를 차단하기 위해 request/response 쌍을 필터 체인의 다음 엔터티로 전달하지 않는다.
  5. 필터 체인에서 다음 엔터티를 호출한 후 응답에 헤더를 직접 설정한다. 나는 아래와 같은 JwtAuthorizationFilter 클래스에서 doFilter를 호출했었다.
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        /* 헤더 추출 및 정상적인 헤더인지 확인 */
        String jwtHeader = request.getHeader("Authorization");
        if (jwtHeader == null || !jwtHeader.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        /* 헤더 안의 JWT 토큰을 검증해 정상적인 사용자인지 확인 */
        String jwtToken = jwtHeader.substring(7);

        try {
            Member tokenMember = jwtTokenProvider.validJwtToken(jwtToken);

            if(tokenMember != null){ //토큰이 정상일 경우
                AuthDetails authDetails = new AuthDetails(tokenMember);

                /* JWT 토큰 서명이 정상이면 Authentication 객체 생성 */
                Authentication authentication = new UsernamePasswordAuthenticationToken(authDetails, null, authDetails.getAuthorities());

                /* 시큐리티 세션에 Authentication 을 저장 */
                SecurityContextHolder.getContext().setAuthentication(authentication);
           }

            chain.doFilter(request, response);

        } catch (TokenExpiredException e){
            log.error(e + " EXPIRED_TOKEN");
            //request.setAttribute("exception", ErrorCode.EXPIRED_TOKEN.getCode());
            setResponse(response, ErrorCode.EXPIRED_TOKEN);
        } catch (SignatureVerificationException e){
            log.error(e + " INVALID_TOKEN_SIGNATURE");
            setResponse(response, ErrorCode.INVALID_TOKEN_SIGNATURE);
        }
    }

그리고 다음과 같은 SecurityConfig에서 필터 체인을 정의했었다.

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf(csrf -> csrf.disable())
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .formLogin(formLogin -> formLogin.disable())
                .httpBasic(httpBasic -> httpBasic.disable())
                .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtTokenProvider(), refreshTokenService))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(),  jwtTokenProvider(), authDetailService))
                .authorizeHttpRequests(requests -> requests
                        .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
                        .anyRequest().permitAll()
                );
        return httpSecurity.build();
    }

이 코드에 따르면, doFilter은 JwtAuthorizationFilter의 로직을 종료하고 필터 체인의 다음 엔티티로 넘겨주는 역할을 한다. 이러한 디자인 패턴을 Chain-of-responsibility 패턴이라고 한다. 이 체인이 종료되어야지만 요청/응답 쌍이 원래 목표했던 타겟 리소스/서블릿으로 이동이 가능한 것이다. 아마 DB와 긴밀하게 연관되어 있는 타겟 리소스/서블릿으로 이동하기 전이나 후에 데이터의 유효성을 검증하고 싶기 때문에 필터라는 것을 만든 것 같다. 프론트엔드에도 필터를 만들 수 있지만, JS 비활성화로 간단하게 무시될 수도 있기 때문이다. 그래서 필터는 아래 그림에서 볼 수 있듯이, DispatcherServlet으로 이동하기 전에 작동한다.

여기서 HttpServletResponse response가 무엇인지를 잠깐 보자. oracle에 따르면 이는 HTTP 관련 기능을 제공하도록 servlet에 의해 생성된 객체이며 doGet, doPost 등의 메소드에 전달되는 인수라고 한다. 상태 코드를 비롯해서, addCookie나 addHeader 등의 다양한 메소드를 포함하고 있다.

QnA

이렇게 스터디를 진행했는데 다음과 같은 질문을 받았다.

그럼 filter가 SecurityFilterChain에 addFilter 메소드로 등록된다는 건 알겠는데, SecurityFilterChain는 어떻게 스프링에 등록되나요? 자동으로 실행 가능하게 등록되는 건가요?

이 부분은 나도 잘 모르겠어서 추가로 찾아봐야겠다는 생각이 들었다. 우선 SecurityFilterChain의 위에 @Bean이 있으므로 실행 가능하게 만들어지는 이유는 명확하다. 여기서 이게 중요한 것은 어떻게 이게 가장 먼저 돌아가는지를 스프링이 아는지이다. 찾아보니, 스프링 시큐리티는 서비스 설정(Configuration)에 라서 filter를 순서대로 실행할 수 있다고 한다. 서블릿 필터를 사용할 때는 당연히 web.xml에 해당 필터들을 선언해야 한다(그렇지 않으면 서블릿 컨테이너에 의해서 무시된다). web.xml에 선언된 DelegatingFilterProxy이 바로 web.xml과 애플리케이션 컨텍스트 사이의 연결을 제공하는 역할을 한다.

<filter> 
<filter-name> myFilter </filter-name> 
<filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> 
</filter>

<filter-mapping> 
<filter-name> myFilter </filter-name> 
<url-pattern> /* </url-pattern> 
</filter-mapping>

DelegatingFilterProxy가 하는 일은 Spring 애플리케이션 컨텍스트에서 가져온 빈(springSecurityFilterChain)을 통해 필터의 메서드를 위임하는 것이다. Spring Security의 웹 인프라는 FilterChainProxy의 인스턴스로 위임함으로써 사용되어야 한다. 물론 필요한 각 Spring Security 필터 빈을 애플리케이션 컨텍스트 파일에 선언하고, 각 필터에 대한 해당 DelegatingFilterProxy 항목을 모두 web.xml에 추가해줄 수 있겠지만, 이는 너무 번거롭다. 이때 FilterChainProxy를 사용하면 web.xml에 단일 항목만 추가해도 웹 보안 빈을 관리하기 위해 완전히 애플리케이션 컨텍스트 파일을 처리할 수 있다. 이는 앞서 언급한 예제와 마찬가지로 DelegatingFilterProxy를 사용하여 연결되지만, filter-name이 빈 이름 "filterChainProxy"로 설정된다. 그런 다음 아래와 같이 필터 체인은 동일한 빈 이름으로 애플리케이션 컨텍스트에 선언된다:

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
	<list>
	<sec:filter-chain pattern="/restful/**" filters="
		securityContextPersistenceFilterWithASCFalse,
		basicAuthenticationFilter,
		exceptionTranslationFilter,
		filterSecurityInterceptor" />
	<sec:filter-chain pattern="/**" filters="
		securityContextPersistenceFilterWithASCTrue,
		formLoginFilter,
		exceptionTranslationFilter,
		filterSecurityInterceptor" />
	</list>
</constructor-arg>
</bean>

정리하자면 다음과 같다.

  1. web.xml에 선언된 DelegatingFilterProxy은 서블릿 컨테이너의 라이프사이클(web.xml)과 애플리케이션 컨텍스트 사이의 연결을 제공한다. 즉, ApplicationContext에서 Filter의 Bean을 찾아서 호출할 수 있다.
  2. FilterChainProxy는 SecurityFilterChain을 통해 많은 필터 인스턴스 목록에 위임을 허용하는 특별한 필터이다. FilterChainProxy는 Bean이므로 일반적으로 DelegatingFilterProxy에 래핑 가능하다.
  3. SecurityFilterChain은 순서대로 필터를 호출한다. 호출된 필터는 이렇게 작동된다.
    1. request를 검토한다.
    2. 선택적으로 request 객체(HttpServletRequest request) 또는 response 객체(HttpServletResponse response)를 사용자 정의 구현으로 래핑하여 입력/출력에서 contents 또는 header를 필터링한다.
    3. FilterChain 개체(chain.doFilter())를 사용하여 체인의 다음 엔터티를 호출하거나, 또는 request 처리를 차단하기 위해 request/response 쌍을 필터 체인의 다음 엔터티로 전달하지 않는다.
    4. 하나의 필터가 끝나면 필터 체인에서 다음 엔터티를 호출한 후 응답에 헤더를 직접 설정한다.

그림으로 표현하자면 아래와 같이 적용된다:

레퍼런스

Filter

 

Filter (Java EE 6 )

 void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)           The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request f

docs.oracle.com

 

What is chain.doFilter doing in Filter.doFilter method?

In a Filter.doFilter method I made this call chain.doFilter. What is doFilter doing inside a doFilter? Isn't it a recursive call?

stackoverflow.com

 

Java Servlet Filter with Example - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

Architecture :: Spring Security

The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like authentication, authorization, exploit protection, and more. The filters are executed in a spec

docs.spring.io

 

Demystifying Filters and Interceptors in Spring Boot

Spring Boot, a popular framework for building Java-based web applications, provides developers with powerful tools to enhance and customize the request-response lifecycle. Two such tools, filters and interceptors, often create confusion due to their simila

www.linkedin.com

 

[Spring] 스프링 필터의 동작과정

목적 스프링 필터의 동작과정을 이해하기 위함 목차 필터의 동작과정 1. 필터의 동작과정 1. Application의 doFilter() 서버로 요청이 들어오면 StandardWrapperValve 클래스의 invoke메서드가 실행된다. 이 메

emgc.tistory.com

 

HttpServletResponse 및 에러 핸들

 

HttpServletResponse (Java EE 6 )

 void setStatus(int sc, java.lang.String sm)           Deprecated. As of version 2.1, due to ambiguous meaning of the message parameter. To set a status code use setStatus(int), to send an error with a description use sendError(int, String).

docs.oracle.com

 

DefaultErrorAttributes (Spring Boot 3.2.4 API)

getError Return the underlying cause of the error or null if the error cannot be extracted. Specified by: getError in interface ErrorAttributes Parameters: webRequest - the source request Returns: the Exception that caused the error or null

docs.spring.io

 

HandlerExceptionResolver (Spring Framework 6.1.5 API)

Interface to be implemented by objects that can resolve exceptions thrown during handler mapping or execution, in the typical case to error views. Implementors are typically registered as beans in the application context. Error views are analogous to JSP e

docs.spring.io

 

SecurityFilterChain

 

SpringSecurity FilterChain이 만들어지는 과정 살펴보기

SpringSecurity FilterChain이 만들어지는 과정 살펴보기

thecodinglog.github.io

 

13. The Security Filter Chain

Spring Security’s web infrastructure is based entirely on standard servlet filters. It doesn’t use servlets or any other servlet-based frameworks (such as Spring MVC) internally, so it has no strong links to any particular web technology. It deals in H

docs.spring.io

https://www.baeldung.com/spring-delegating-filter-proxy

 

Spring Security | 01 | DelegatingFilterProxy, FilterChainProxy

이번 사이드 프로젝트를 통해 Spring Security 를 사용해보았습니다. 🍔 Spring Security , Authentication, Authorization Spring Security Reference >## Spring Security Spring

velog.io

 

반응형
저작자표시 (새창열림)

'🗄️Backend > SpringBoot' 카테고리의 다른 글

[TodayILearn] @Scheduled 어노테이션에 대해서  (0) 2024.05.06
[TodayILearn] FilterExceptionHandler란?(feat. JWT 토큰 관련 클라이언트 에러 Filter에서 Handling해 에러 코드 반환하기)  (1) 2024.05.02
[TodayILearn] Spring Bean Life Cycle이란?  (0) 2024.04.08
[TodayILearn] 스프링 서블릿(Servlet)에 대해(HttpServletRequest, HttpServletResponse)  (0) 2024.04.03
Spring Boot에서 @AuthUser 커스텀 어노테이션 생성(@interface, @Target, @Retention)  (0) 2024.03.23
  1. TIL - doFilter 메소드에 대해서
  2. QnA
  3. 레퍼런스
'🗄️Backend/SpringBoot' 카테고리의 다른 글
  • [TodayILearn] @Scheduled 어노테이션에 대해서
  • [TodayILearn] FilterExceptionHandler란?(feat. JWT 토큰 관련 클라이언트 에러 Filter에서 Handling해 에러 코드 반환하기)
  • [TodayILearn] Spring Bean Life Cycle이란?
  • [TodayILearn] 스프링 서블릿(Servlet)에 대해(HttpServletRequest, HttpServletResponse)
Researcher Mode
Researcher Mode
Researcher Mode
Codename Cathy
Researcher Mode
전체
오늘
어제
  • 분류 전체보기 (91)
    • 🤖AIML (11)
    • 🗄️Backend (29)
      • SpringBoot (23)
      • [GDSC] SpringBoot 프로젝트 (6)
      • [GDSC] Solution Challenge (0)
    • 🎨Frontend (1)
    • 👷‍♂️DevOps (14)
      • Kubernetes (2)
      • [구글 K8S 스터디 잼] 입문반 (6)
      • [구글 K8S 스터디 잼] 중급반 (5)
      • Cloud(AWS&GCP) (1)
    • 📱Kotlin (22)
      • [GDSC] Kotlin in Action 스터디 (22)
    • 🧩Algorithm (4)
    • 🖥️Languages (5)
      • 👩‍💻Git (1)
      • 🐍Python (2)
      • ☕JAVA (2)
    • 📖 시험 (1)
    • 🎉연말결산 및 잡담 (3)
    • 💻 컴퓨터 문제 (1)

최근 글

hELLO · Designed By 정상우.v4.2.2
Researcher Mode
[TodayILearn] doFilter 메소드란?
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.