OncePerRequestFilter를 상속 받으면 HttpServletRequest를 사용하는 이유?

조은지·2023년 8월 19일

삽질이였다.

출처

서론

스프링 시큐리티는 Filter를 사용해서 인증, 인가 작업을 해주는 프레임워크
필터의 개념이 부족해서 공부하던 중 필터는 ServletRequestServletResponse를 사용해서 구현한다고 했다.

그런데 일부 스프링시큐리티 구현예제에서는 HttpServletRequestHttpServletResponse를 이용하고 있었다.

GenericFilterBean을 상속받은 Filter 클래스는 ServletRequest/Response 객체를 받고, OncePerRequestFilter에서는 HttpServletRequest/Response 객체를 받고 있었다.

Filter

HTTP 요청이 DispatcherServlet에 가기 전에 요청을 가로채서 필터 체인을 통해 처리할 수 있는 컴포넌트이다.
응답의 경우는 WAS에 가기 전에 가로채서 처리한다고 한다.

doFilter 메소드

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

        // do Filter 실행 전 로직
        println("Before")
        chain?.doFilter(request, response) ?: throw Exception()
        // do Filter 실행 후 로직
        println("After")

    }
  • doFilter 메소드를 통해 서블릿이 호출되기 전 / 후에 별도의 로직을 수행할 수 있다.
  • 이러한 Filter를 확장하여 Spring의 설정정보를 가져올 수 있게 만들어진 것이 GenericFilterBean

Filter와 GenericFilterBean의 문제점

  • Filter와 GenericFilterBean은 둘 다 매 서블릿마다 호출된다.
  • 예를 들어 포워딩이 발생하면 다른 서블릿으로 dispatch가 된다. 필터 체인이 다시 동작하면서, 한 번만 필요한 처리를 중복으로 처리하게 된다.
  • 인증의 경우 중간에 포워딩이 발생해도 한번만 처리되도 되기 때문에 불필요한 작업이 이루어지는 것

=> 모든 서블릿에 일관된 요청을 처리하기 위해 만들어진 것이OncePerRequestFilter이다.

OncePerRequestFilter

doFilter 메소드

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            HttpServletRequest httpRequest = (HttpServletRequest)request;
            HttpServletResponse httpResponse = (HttpServletResponse)response;
            String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
            boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
            if (!this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
                if (hasAlreadyFilteredAttribute) {
                    if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
                        this.doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
                        return;
                    }
                    filterChain.doFilter(request, response);
                } else {
                    request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
                    try {
                        this.doFilterInternal(httpRequest, httpResponse, filterChain); //이 메소드를 개발자가 커스텀한다.
                    } finally {
                        request.removeAttribute(alreadyFilteredAttributeName);
                    }
                }
            } else {
                filterChain.doFilter(request, response);
            }
        } else {
            throw new ServletException("OncePerRequestFilter just supports HTTP requests");
        }
    }

OncePerRequestFilter이 동작하면 서블릿 객체에 alreadyFilteredAttribute라는 값을 true로 넣어둔다.

그래서 다음에 포워딩이 발생해도 전에 서블릿 객체에서 해당 attribute값이 true인지를 확인하여 한번만 동작하는 것을 보장해준다.

만약에 filter가 실행된 적이 없으면 doFilterInternal 메소드를 호출하여 자신의 기능을 수행한다.


doFilterInternal과 HttpServletRequest가 파라미터로 넘어가는 이유

		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

doFilter 메소드를 보면 ServletRequest 객체를 HttpServletRequest로 타입 변환을 시키는 것을 볼 수 있다.

ServletRequest를 상속받은게 HttpServletRequest이기 때문에 변환 가능하고, 요청 객체는 생각해보니 Filter 개념과는 별개였다...

쨌든 doFilter 메소드에서 HttpServletRequest로 형변환하고 속성 있는지 없는지 확인하기 때문에 doFilterInternal 은 HttpServletRequest/Response 를 파라미터로 받는 것이였다!!

잘못 알고 있던 점 / 몰랐던 점

1. DispatcherServlet은 Spring Container 밖에 있다


DispatcherServlet은 전달받은 설정 파일을 이용해서 스프링 컨테이너를 생성한다. 스프링 컨테이너에는 HandlerMapping, HandlerAdapter, Controller, ViewResolver 등이 존재

인프런 강의 헛으로 들었네... 저번에도 이거 헷갈렸었는데 맨날 까먹음 레전드

2. ServletRequest 밑에는 HttpServletRequest밖에 없다.

필터에서 ServletRequest를 받는 이유는 해당 인터페이스를 상속받는 여러 인터페이스들이 있어서인 줄 알았는데 한 개밖에 없다고 한다.
왜 만들엇노

https://tinkerbellbass.tistory.com/1

위의 출처에도 적었지만 해당 블로그 글을 읽어보면, 나중에 Http보다 더 나은 프로토콜이 생겼을 때 유연성을 위해서 GenericServlet 클래스를 두었다고 한다.

결론

OncePerRequestFilter의 doFilter에는 ServletRequest를 받아서 처리하고 있고, 내가 상속받아서 처리하는 건 doFilterInternal 메소드였기 때문에 HttpServletRequest를 받아야 했었다.

0개의 댓글