Servlet - 필터 (Filter)

컴업·2021년 11월 16일
0

서블릿 필터 - 소개

서블릿 필터는 공통 관심 사항을 처리하기 알맞은 기술입니다.

모든 페이지에서 로그인 유무를 확인해야 할 경우 각 컨트롤러마다 로그인 유무를 체크하는 로직을 작성해야합니다. 이를 공통 관심사 (cross-cuttinf concern)라고 합니다.

이러한 공통 관심사를 모든 컨트롤러에서 해결하는 것은 매우 비효율 적일 것입니다. 또한 향후 로그인과 관련된 로직이 변경될 경우 작성한 모든 로직을 수정해야 하는 불편함이 있습니다.

따라서 컨트롤러 로직이 실행되기 전, 공통로직을 한 곳에서 미리 처리할 수 있다면 훨씬 유지보수가 용이한 서비스를 만들 수 있습니다.

필터는 서블릿이 지원하는 수문장으로 다음과 같은 흐름을 가지고 있습니다.

필터는 위 그림처럼 체인 구조를 띄고 있어 단계적으로 공통 관심 사항을 처리할 수 있습니다. 예를 들어 로그를 찍는 로직을 수행한 뒤에 로그인 여부를 체크하는 로직을 수행하도록 할 수 있습니다.

또한 필터는 부적절한 요청(예를 들어 권한 없는 사용자의 요청)일 경우 서블릿을 호출하지 않을 수 있어, 로그인 유무를 체크하는데 굉장히 편리합니다.

서블릿 필터 인터페이스

서블릿 필터는 다음과 같이 Filter인터페이스를 구현하여 만듭니다.


import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TempFilter 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();
    }
}

이 인터페이스를 구현하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고 관리합니다.

필터는 아래 3가지 메서드를 가집니다.

  • init() : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 대 호출된다.
  • doFilter() : 필터의 로직이 실행되는 곳, HTTP 요청이 올 때마다 호출된다.
  • destroy() : 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.

서블릿 필터의 사용

개발자는 doFilter() 메서드에 필요한 로직을 작성하여 필터를 만들면 됩니다.


import hello.login.web.SessionConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.PatternMatchUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@Slf4j
public class LoginCheckFilter implements Filter {

    private static final String[] whiteList = {"/", "/members/add", "/login", "/logout", "/css/*"};

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

        HttpServletRequest httpReqest = (HttpServletRequest) request;
        String requestURI = httpReqest.getRequestURI();

        HttpServletResponse httpResponse = (HttpServletResponse) response;

        try {
            log.info("인증 체크 필터 시작 {}", requestURI);

            if (isLoginCheckPath(requestURI)) {
                log.info("인증 체크 로직 실행 {}", requestURI);
                HttpSession session = httpReqest.getSession(false);
                if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
                    log.info("미인증 사용자 요청 {}", requestURI);
                    // 로그인으로 redirect;
                        // 로그인 실패 -> 로그인 페이지 -> 로그인 성공 -> 아까 실패했던 페이지로 redirect
                    httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
                    return;
                }
            }

            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {
            log.info("인증 체크 필터 종료 {}", requestURI);
        }
    }

    /**
     * 화이트 리스트의 경우 인증 체크 x
     */

    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whiteList, requestURI);
    }
}
  • 인증 필터를 적용해도 홈, 회원가입, 로그인 화면 그리고 css와 같은 리소스에는 접근 할 수 있어야 합니다. 따라서 화이트 리스트에 이러한 경로를 정리해 두었습니다.

  • 서블릿 필터는 ServleteRequest, ServleteResponse를 파라미터로 받습니다.
    이는 HTTP 요청이 아닌경우를 고려해 만들어 진 인터페이스 이기 때문입니다.
    우리는 HTTP 요청만을 받을 것 이기 때문에 HttpServeltRequest로 다운캐스팅 해주었습니다.

  • isLoginCheckPath 메서드에서 화이트 리스트를 제외한 모든 경우에 인증 체크 로직을 적용합니다.

  • 미인증 사용자 요청시 로그인 화면으로 Redirect합니다. 단 로그인 후 사용자가 접근하려고 했던 페이지로 다시 갈 수 있도록 URL정보를 쿼리 파라미터로 넘겨주었습니다. 그 후 return;을 통해 필터를 종료시켜 서블릿을 호출 하지 않습니다.

  • chain.doFileter(requset, response)는 다음 필터 체인 혹은 서블릿을 호출합니다. 정상적으로 인증체크가 된 경우 이를 실행해 다음 단계로 넘어갈 수 있습니다.

서블릿 필터의 등록


import hello.login.web.filter.LoginCheckFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginCheckFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");

        return filterRegistrationBean;
    }
}

스프링 부트를 사용한다면 FilterRegistrationBean을 사용해서 등록할 수 있다.

  • setFilter(new LoginCheckFilter()); : 등록할 필터를 지정한다.

  • setOrder(1) : 필터체인의 순서를 지정한다. 낮을 수록 먼저 실행됨.

  • addUrlPatterns("/*") : 필터를 적용할 URL 패턴을 지정한다.
    (우리는 white list를 이용해 필터링 하기 때문에 여기서는 모든 Url을 다 받도록 설정하였다.)


<출처>
Inflearn 김영한 선생님, Spring MVC 1

profile
좋은 사람, 좋은 개발자 (되는중.. :D)

0개의 댓글