[MVC2] 7. 로그인 처리 2- 필터, 인터셉터

kiwonkim·2021년 11월 21일
0

[ 이전 포스팅 ]

HttpSession 으로 클라이언트의 세션을 생성하고, session.setAttribute(User) 를 통해 세션에 유저를 저장하였다. 그리고 HomeController 에서 @SessionAttribute 로 유저를 가져와, 로그인된 유저에게 다른 뷰를 제공할 수 있게되었다. 그런데 로그인 하지 않은 사용자도 URL 을 통해 로그인한 사람만 접근 가능한 상품관리 등의 페이지에 접근이 가능하다. 상품관리 등 모든 페이지에 일일이 로그인 확인 로직을 추가해도 되겠지만 이는 매우 번거롭다. 이러한 웹 관련 공통 관심사 처리는 필터나 인터셉트를 활용한다.

Session 으로 로그인 유지는 가능해졌다. 그런데 로그인한 사람만 접근하도록 제어는 불가능하다. 이는 필터나 인터셉터로 처리한다.


[ 서블릿 필터 ]

필터란

HTTP 요청 -> WAS -> 필터 -> 디스패쳐 서블릿 -> 컨트롤러들

필터는 서블릿이 지원하는 수문장이다. 필터는 디스패쳐 서블릿 호출전에 호출된다. 또한 필터 체인을 통해 여러 필터를 호출할 수도 있고, 서블릿을 호출하지 않도록 중단시킬 수도 있다.

필터 사용

필터를 사용하려면 Filter 인터페이스를 구현하고, FilterRegistrationBean 에 등록하면된다.

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;
                }
            }

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

    }

    /**
     * 화이트 리스트의 경우 인증 체크X
     */
    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
    }
}

위는 로그인 필터의 구현이다. doFilter 에서 ServletRequest 와 ServletResponse 객체로 세션을 확인해 로그인 검증을 수행하고, 성공하면 chain.doFilter 로 다음 필터/서블릿을 호출한다.

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

        return filterRegistrationBean;
    }

그 후 Configuration 클래스를 만들어서 FilterRegistrationBean 을 빈으로 등록하고. 그 안에 setFilter 로 필터를 추가하면 된다.


[ 스프링 인터셉터 ]

스프링 인터셉터도 서블릿 필터와 같이 웹 관련 공통 관심사를 효과적으로 해결할 수 있다. AOP도 처리 가능하지만 웹 관련 관심사는 request, response 객체를 사용 가능한 필터나 인터셉트를 사용한다.

인터셉터란

HTTP 요청 -> WAS -> 필터 -> 디스패쳐 서블릿 -> 인터셉터 -> 컨트롤러

우선 인터셉터는 디스패쳐 서블릿 이후 컨트롤러 호출 직전 호출된다. doFilter 하나의 메서드만 오버라이딩하여 사용하는 필터와 달리 인터셉터는 세가지 메서드를 오버라이딩 가능하다.

  • preHandle : 컨트롤러 호출 직전 호출된다.
  • postHandle : 컨트롤러가 ModelAndView를 반환한 후에 호출된다. 컨트롤러에서 예외가 발생하면 호출되지 않는다.
  • afterCompletion : 뷰가 렌더링 된 이후에 호출된다. 예외가 발생해도 호출된다.
    인터셉터는 스프링 MVC 구조 특화 필터기능을 제공하므로, 스프링에서 특별한 경우가 아니면 인터셉터를 사용한다.

인터셉터 사용

인터셉터의 사용은 HandlerInterceptor 인터페이스를 오버라이딩하여 인터셉를 생성하고.
WebMvcConfigurer 에서 addInterceptros 를 오버라이딩하여 그 안에서 인터셉터를 등록하면 된다.

인터셉터 생성

public class LoginCheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();

        log.info("인증 체크 인터셉터 실행 {}", requestURI);

        HttpSession session = request.getSession();

        if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
            log.info("미인증 사용자 요청");
            //로그인으로 redirect
            response.sendRedirect("/login?redirectURL=" + requestURI);
            return false;
        }

        return true;
    }
}

인터셉터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LoginCheckInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/members/add", "/login", "/logout",
                        "/css/**", "/*.ico", "/error");
    }

로그인은 컨트롤러 호출 전 수행되므로 preHandle 만 오버라이딩하면 된다. 인터셉터는 등록시 excludePathPatterns 로 제외할 URL 을 추가할 수 있기에 whitelist 를 인터셉터 내부에 둘 필요가 없다.


[ 필터 vs 인터셉터 ]

필터

  • 서블릿이 제공하는 기능
  • Filter 인터페이스를 구현하여 생성
  • 디스패쳐 서블릿 이전에 호출
  • doFilter 메서드만 오버라이딩 가능
  • FilterRegistrationBean 을 빈으로 추가하며 내부에서 등록
  • excludePatterns 를 제공하지 않아 필터 내부에서 처리해야함

인터셉터

  • 스프링이 제공하는 기능
  • HandlerInterceptor 인터페이스를 구현하여 생성
  • 디스패쳐 서블릿 이후에 호출
  • 세가지 메서드 오버라이딩 가능
    - preHandle : 컨트롤러 호출 직전
    • postHandle : 컨트롤러 반환 후
    • afterCompletion : 뷰 렌더링 후
  • WebMvcConfigurer 를 구현한 클래스에서 addInterceptores 를 오버라이딩하며 내부에서 등록
  • excludePatterns 를 제공해서 특정 Url 에 수행되도록 유연하게 설정가능

0개의 댓글