필터

바그다드·2023년 5월 19일
0
  • 쇼핑몰을 예로 들어보자. 로그인 기능을 구현한다고 한다면, 상품 등록, 수정, 삭제 기능 각각에 로그인 여부를 확인해야 하고, 각 기능에 로그인 기능을 구현하면 중복되는 코드가 늘어날 것이다. 이처럼 여러 로직에서 공통으로 관심이 있는 것을 공통 관심사라고 한다.
    웹과 관련된 공통 관심사를 처리할 때는 http헤더나 URL정보들이 필요한데, 서블릿 필터나 스프링 인터셉터는 HttpServletRequest를 제공한다.

필터

서블릿에서 제공하는 기능으로, Dispatcher Servlet으로 요청이 전달되기 전후에 URL패턴에 맞는 모든 요청에 대한 부가작업을 처리할 수 있는 기능을 제공한다.

필터 흐름

로그인 : HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
비 로그인 : HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X)

필터 체인

HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
저번에 Oauth2를 이용하여 sns로그인 기능을 구현해 보았을 때 필터 체인이 나와서 무엇인지 몰랐는데, 필터는 체인으로 구성되어 여러가지 필터를 추가할 수 있다. 필터 순서에 따라서 로그인 필터를 먼저 적용하고, 로그를 남기는 필터를 그 다음에 적용하는 등으로 필터 체인을 구성할 수 있다.

그럼 로그를 남기는 필터를 생성해보자!!

1. 로그 필터

1. 필터 생성

@Slf4j
public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("log filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("log filter doFilter");

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

        String uuid = UUID.randomUUID().toString();

        try {
            log.info("REQUEST [{}][{}]",uuid,requestURI);
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        }finally {
            log.info("RESPONSE [{}][{}]", uuid, requestURI);
        }
    }

    @Override
    public void destroy() {
        log.info("log filter destroy");
    }
}
  • javax.servlet을 상속받자
  • 필터를 생성한 후에는 필터 체인에 등록해줘야 한다.
  • 특히 chain.doFilter()를 호출해줘야 하는데, 만약 다음 필터가 있으면 다음 필터로 진행되고, 없다면 서블릿을 호출한다.
    이걸 하지 않으면 다음 단계로 진행이 되지 않는다.

2. 필터 등록

@Configuration
public class WebConfig {

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

        return filterRegistrationBean;
    }
}

이렇게 등록까지 마치고 상품 등록을 시도해보면


콘솔에 로그가 찍혀 있는 것을 확인할 수 있다.

  • 실행 흐름은
    필터 - 서블릿 - 컨트롤러 - 필터
    로 이어진다.

2.로그인 필터

  • 이제 로그인을 처리하는 필터를 구현해보자

1. 필터 생성

@Slf4j
public class LoginCheckFilter implements Filter {

    // 로그인이 필요없는 페이지
    private static final String[] whiteList = {"/", "/members/add", "/login", "/css/*"};
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        HttpServletResponse httpResponse = (HttpServletResponse) response;

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

            if (isLoginCheckPath(requestURI)) {
                log.info("인증 체크 로직 실행 {}", requestURI);
                HttpSession session = httpRequest.getSession(false);
                if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {

                    log.info("미인증 사용자 요청 {}", requestURI);
                    // 로그인으로 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);
    }
}
  • whiteList는 로그인이 필요하지 않는 페이지 목록이다.
  • if문은 로그인을 한 사용자인지 비로그인 상태인 사용자인지 구분하는 조건문으로, 세션이 없거나, 로그인 관련 세션이 존재하지 않는다면 로그인 페이지로 리다이렉트를 시킨다.
    이때, 로그인 후에는 원래 요청했던 페이지로 이동하도록 요청URI를 파라미터로 함께 보낸다.
  • 또한 doFilter를 호출해버리면 다음 필터나 컨트롤러가 호출되어 버리므로 return을 호출하여 다음 단계로 진행되지 않도록 하자!!
  • isLoginCheckPath는 요청URI가 로그인이 필요한 페이지인지 아닌지 구분하기 위한 메서드이다.

2. 필터 등록

  • 기존에 생성해두었던 config파일에 필터를 빈으로 등록해주자
    @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginCheckFilter());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns("/*");

        return filterRegistrationBean;
    }

3. 리다이렉트 설정(컨트롤러)

  • 필터에서 보내준 리다이렉트URL을 이용하여 로그인 성공 시에 요청했던 URL페이지로 이동하도록 리다이렉트를 설정해주자
    public String loginV4(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult,
                          @RequestParam(defaultValue = "/") String redirectURL,
                          HttpServletRequest request) {
        if (bindingResult.hasErrors()) {
            return "login/loginForm";
        }

        Member loginMember = loginService.login(form.getLoginId(), form.getPassword());

        if (loginMember == null) {
            bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
            return "login/loginForm";
        }

        //로그인 성공 처리
        // getSession(true) - 있으면 기존 세션 반환, 없으면 생성해서 반환
        // getSession(false) - 있으면 기존 세션 반환, 없으면 null반환
        HttpSession session = request.getSession();
        // 세션에 로그인 회원 정보 보관
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

        return "redirect:" + redirectURL;


  • /items를 요청할 때 로그인폼으로 이동하는 대신 redirectURI를 함께 보내고, 이를 이용해 로그인을 성공했을 시에 /items로 리다이렉트 된 것을 확인할 수 있다.

이것으로 필터에 대해서 알아보고 구현도 해보았다. 다음 포스팅에서는 스프링에서 제공하는 인터셉터에 대해 알아보자!!!

profile
꾸준히 하자!

0개의 댓글