GitHub

https://github.com/Choidongjun0830

Spring

서블릿 필터

gogi masidda 2024. 1. 9. 17:16

서블릿 필터

현재는 로그인하지 않은 사용자도 /items나 /item/{id}에 들어가면 수정이 가능하고 삭제도 가능하다. 이를 막으려면 서블릿 필터를 사용해야 한다.

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

필터를 적용하면 필터가 호출된 다음에 서블릿이 호출된다. 그래서 모든 고객의 요청 로그를 남기는 요구사항이 있다면 필터를 사용하면 된다.  여기서 서블릿은 스프링의 디스패처 서블릿으로 생각하면 된다. 필터는 수문장 역할!!

필터는 특정 URL 패턴에 적용할 수 있다. 

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

요청 로그

//LogFilter.java

@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");
        //ServletRequest는 HTTP요청이 아닌 경우를 위해 만든거라 Http를 사용하는 경우에는 HttpServletRequest로 다운캐스팅해야함.
        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");
    }
}
//WebConfig.java

@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1); //순서 정해주기
        filterRegistrationBean.addUrlPatterns("/*"); //모든 URL에 적용

        return filterRegistrationBean;
    }
}

인증 체크

@Slf4j
public class LoginCheckFilter implements Filter {
    //init과 destroy는 default가 있어서 구현하지 않아도 됨

    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) {//세션이 null이거나 세션의 attribute가 null이면
                    log.info("미인증 사용자 요청 {}", requestURI);
                    //로그인으로 redirect
                    httpResponse.sendRedirect("/login?redirectURL =" + requestURI); 
                    //로그인을 하면 다시 이 페이지로 돌아오게 정보 넘겨주기. 이 기능은 따로 구현해야함
                    return ;
                }
            }
            //체크하지 않아도 되는 경로이면
            chain.doFilter(request, response);

        } catch (Exception e) {
            throw e; //예외 로깅 가능 하지만, 톰캣까지 예외를 보내주어야 함
        } finally {
            log.info(("인증 체크 필터 종료"));
        }
    }

    /**
     * 화이트리스트의 인증체크 X
     */
    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI); //whitelist에 없는 것은 로그인 체크해야함
    }

}
  • whitelist를 통해 미래에 페이지를 더 만들어도 필터가 적용이 될 수 있도록 할 수 있다. whitelist를 제외한 모든 페이지에 필터를 적용한다.
  • httpResponse.sendRedirect("/login?redirectURL =" + requestURI);는 미인증 사요자는 로그인 화면으로 이동하게 하고, 이동하면 redirectURL로 로그인 이후에 다시 그 페이지로 돌아갈 수 있도록 정보를 전달해준다. 이는 컨트롤러에서 구현해야한다.
  • return;을 통해 필터를 더는 진행하지 않도록 한다. redirect를 사용했기 때문에 redirect가 응답으로 적용되고 끝난다.
@Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginCheckFilter());
        filterRegistrationBean.setOrder(2); //순서 정해주기
        filterRegistrationBean.addUrlPatterns("/*"); //모든 URL에 적용. whitelist를 넣어놔서 LoginCheckFilter에서 걸러짐

        return filterRegistrationBean;
    }
  • 앞에서 logFilter에 이어서 동작하는 Filter이므로 setOrder를 2로 해준다. 
  • filterRegistrationBean.addUrlPatterns("/*"); 는 모든 URL에 적용하도록한다.  whitelist를 넣어놔서 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";
        }

        //로그인 성공 처리
        //세션이 있으면 있는 세션 반환, 신규 세션을 생성 <- request.getSession()의 기능
        HttpSession session = request.getSession();
        //세션에 로그인 회원 정보 보관
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

        return "redirect:" + redirectURL;
    }

@RequestParam redirectURL을 받아 로그인 이후에 리다이렉트 될 수 있도록 한다.

728x90

'Spring' 카테고리의 다른 글

예외 처리, 오류 페이지  (1) 2024.01.11
스프링 인터셉터  (1) 2024.01.09
쿠키와 세션  (1) 2024.01.08
검증2 - Bean Validation  (0) 2024.01.06
검증1-Validation  (2) 2024.01.04