ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 서블릿 필터
    스프링/스프링 MVC 패턴 2023. 3. 1. 15:42

    로그인이 된 사용자만 상품 관리 페이지에 들어갈 수 있어야 하는데 로그인을 하지 않은 사용자가 URL을 직접 호출하면 상품 관리 화면에 들어가서 기능들을 사용할 수 있다.

     

    웹에 관련된 공통 관심사를 처리할 때에는 서블릿 필터나 스프링 인터셉터를 사용하는 것이 좋다. 이 글에서는 서블릿 필터에 대해 배우도록 하겠다.

     

    필터 흐름

    HTTP 요청   --->   WAS   --->   필터   --->   서블릿   --->   컨트롤러

     

    필터를 적용하면 피터가 호출된 다음에 서블릿이 호출되며 특정 URL 패턴에 적용할 수 있다.

    로그인 한 사용자는 필터, 서블릿을 건너서 컨트롤러까지 호출하는데 로그인을 하지 않은 사용자는 필터에서 요청을 끝내버릴 수 있다. 로그인 여부 등을 체크하기에 좋은 기능이다.

     

    필터는 체인으로 구성되기 때문에 필터를 여러 개 지정할 수 있다.

     

     

    LogFilter 클래스

    @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;
            // Http만 나오도록 다운캐스팅
            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");
        }
    }

    LogFilter 클래스이다. 필터가 시작되고 끝났을 때의 로그를 출력해주는 코드이다.

     

     

     

     

    LoginCheckFilter 클래스

    @Slf4j
    public class LoginCheckFilter implements Filter {
        private static final String[] whitelist = {"/", "/members/add", "/login", "/logout","/css/*"};
        // whitelist는 인증 체크 필요 x
    
        @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);
        }
    }

    다음으로 로그인체크 필터 클래스이다. init과 destroy 메서드를 오버라이딩 하지 않았는데, 그 이유는 default값을 서블릿에서 지원하기 때문에 없어도 되어서 삭제했다. whitelist라는 목록이 있는데, 우리가 상품 관리를 하는 페이지는 로그인을 해야지 접근가능하지만 로그인 페이지와 회원가입 홈 페이지는 세션이 없더라도 접근이 가능해야하기 때문에 화이트리스트를 따로 설정해두어서 세션이 없어도 접근 가능하게 만들었다.

    try catch 문을 보면 session이 null이면 로그인 페이지로 리다이렉트 한다. httpResponse.sendRedirect("/login?redirectURL=" + requestURI);로 리다이렉트 하고 return;을 통해 더 이상 진행하지 않고 나가게 된다.

    session이 올바르게 진입했으면 chain.doFilter로 다음 chian으로 가거나 서블릿으로 향하는데 여기서는 서블릿으로 향할 목적이라서 chain.doFilter(request, response);를 입력했다.

     

     

     

    WebConfig 클래스

    @Configuration
    public class WebConfig {
    
        @Bean
        public FilterRegistrationBean logFilter() {
            FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(new LogFilter());
            filterRegistrationBean.setOrder(1); // 체인 1번
            filterRegistrationBean.addUrlPatterns("/*"); // 모든 Url에 다 적용
    
            return filterRegistrationBean;
        }
    
        @Bean
        public FilterRegistrationBean loginCheckFilter() {
            FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(new LoginCheckFilter());
            filterRegistrationBean.setOrder(2); // 체인 2번
            filterRegistrationBean.addUrlPatterns("/*");
            return filterRegistrationBean;
        }
    
    우리가 만든 필터들이 적용될 수 있도록 하는 @Configuraiton이다. logFilter를 첫 번째 체인으로, loginCheckFilter를 두 번째 체인으로 지정했다.
     
     
     
     
    LoginController
    @PostMapping("/login")
    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";
        }
    
        // 로그인 성공 처리
        // 세션이 있으면 있는 세션 반환, 없으면 신규 세션을 생성
        HttpSession session = request.getSession();
        // 세션에 로그인 회원 정보 보관
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
    
        return "redirect:" + redirectURL;
    }

    로그인 컨트롤러에도 변화가 있었는데, 아래 return을 보면 redirect 뒤에 redirectURL이 있다. 이 뜻은 만약 사용자가 상품 등록을 하다 로그인이 안되어있어서 로그인을 했는데 홈 화면 페이지로 이동하면 불편한 서비스이니까 로그인을 한 뒤에도 상품 등록 페이지로 연결할 수 있도록 하는 코드이다.

     

    위에 url을 보면 redirectURL=/items로 남겨져 있다.

    LoginController에서 @RequestParam 으로 redirectURL을 받고 있어서 로그인을 하게 되면

    홈 화면이 아니라 바로 items 화면으로 넘어가게 된다.

Designed by Tistory.