본문 바로가기
Spring/MVC

서블릿 필터

by o3oppp 2024. 7. 11.
서블릿 필터
  • 필터는 웹과 관련된 공통 관심사(로그인, 사용자 권한 등)를 처리할 때 주로 사용
  • 지정한 URL 패턴에 대해 거름막 역할을 해주는 기능

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

필터 제한
  • HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 // 로그인 사용자
  • HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단 시 서블릿 호출 X) // 비 로그인 사용자

필터 체인
  • HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
  • 필터는 체인으로 구성
  • 중간에 필터를 자유롭게 추가 가능(순서 지정 가능)

Filter 인터페이스
public interface Filter {
     public default void init(FilterConfig filterConfig) throws ServletException
 {}
     public void doFilter(ServletRequest request, ServletResponse response,
             FilterChain chain) throws IOException, ServletException;
     public default void destroy() {}
}
  • 필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성, 관리
  • init() : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출
  • doFilter() : 고객의 요청이 올 때 마다 해당 메서드가 호출됨. 해당 메서드에 필터 로직 구현
  • destroy() : 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출

서블릿 필터를 사용하여 요청 로그 남기기

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");
        Filter.super.destroy();
    }
}

2. 필터 적용

@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean logFilter(){
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LogFilter()); // 적용 할 필터 넣기
        filterFilterRegistrationBean.setOrder(1); // 순서
        filterFilterRegistrationBean.addUrlPatterns("/*"); // 필터 넣을 url 패턴

        return filterFilterRegistrationBean;
    }
}

3. 실행


서블릿 필터를 이용하여 인증 체크

1. 필터 구현

@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 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; // 컨트롤러 호출하지 않고 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);
    }
}

2. 필터 적용

@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean logFilter(){
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LogFilter()); // 적용 할 필터 넣기
        filterFilterRegistrationBean.setOrder(1); // 순서
        filterFilterRegistrationBean.addUrlPatterns("/*"); // 필터 넣을 url 패턴

        return filterFilterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean loginCheckFilter(){
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LoginCheckFilter()); // 적용 할 필터 넣기
        filterFilterRegistrationBean.setOrder(2); // 순서
        filterFilterRegistrationBean.addUrlPatterns("/*"); // 필터 넣을 url 패턴

        return filterFilterRegistrationBean;
    }
}

3. 실행

미인증 사용자
인증 사용자

필터를 이용한 redirect

httpRequest.getRequestURI()를 통해 사용자가 요청한 url을 저장하여 필터 통과 시 redirect

@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; // 인증 전 url로 redirect
    }

'Spring > MVC' 카테고리의 다른 글

서블릿 예외 처리  (0) 2024.07.20
스프링 인터셉터  (0) 2024.07.11
Bean Validation  (0) 2024.06.29
Validator  (0) 2024.06.28
BindingResult  (0) 2024.06.27