코딩성장스토리

스프링 - 필터 , 인터셉터 본문

백 엔드/spring

스프링 - 필터 , 인터셉터

까르르꿍꿍 2022. 5. 14. 20:42

김영한의 MVC2편 정리

 

필터와 인터셉터는 로그인시 필요한 기능이다. 로그인해야 접근이 가능한 페이지와 안그런 페이지를 구분하기 위해서 필터와 인터셉터가 필요하다.

 

필터

필터 흐름

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

 

필터 제한

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러//로그인 사용자

HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X) //비 로그인 사용자

 

필터 체인

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

 

필터 인터페이스

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(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.

 

WebConfig - 필터 설정

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

setFilter(new LogFilter()) : 등록할 필터를 지정한다.

setOrder(1) : 필터는 체인으로 동작한다. 따라서 순서가 필요하다. 낮을 수록 먼저 동작한다.

addUrlPatterns("/*") : 필터를 적용할 URL 패턴을 지정한다. 한번에 여러 패턴을 지정할 수 있다

 

필터를 이용한 인증체크 코드

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

whitelist = {"/", "/members/add", "/login", "/logout","/css/*"};

인증 필터를 적용해도 홈, 회원가입, 로그인 화면, css 같은 리소스에는 접근할 수 있어야 한다. 이렇게 화이트 리스트 경로는 인증과 무관하게 항상 허용한다. 화이트 리스트를 제외한 나머지 모든 경로에는 인증 체크 로직을 적용한다. isLoginCheckPath(requestURI) 화이트 리스트를 제외한 모든 경우에 인증 체크 로직을 적용한다. httpResponse.sendRedirect("/login?redirectURL=" + requestURI);

미인증 사용자는 로그인 화면으로 리다이렉트 한다. 그런데 로그인 이후에 다시 홈으로 이동해버리면, 원하는 경로를 다시 찾아가야 하는 불편함이 있다. 예를 들어서 상품 관리 화면을 보려고 들어갔다가 로그인 화면으로 이동하면, 로그인 이후에 다시 상품 관리 화면으로 들어가는 것이 좋다. 이런 부분이 개발자 입장에서는 좀 귀찮을 수 있어도 사용자 입장으로 보면 편리한 기능이다. 이러한 기능을 위해 현재 요청한 경로인 requestURI 를 /login 에 쿼리 파라미터로 함께 전달한다. 물론 /login 컨트롤러에서 로그인 성공시 해당 경로로 이동하는 기능은 추가로 개발해야 한다.

return;

여기가 중요하다. 필터를 더는 진행하지 않는다. 이후 필터는 물론 서블릿, 컨트롤러가 더는 호출되지 않는다. 앞서 redirect 를 사용했기 때문에 redirect 가 응답으로 적용되고 요청이 끝난다.

스프링 인터셉터

스프링 인터셉터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿(디스패처 서블릿) -> 스프링 인터셉터 -> 컨트롤러

스프링 인터셉터 제한

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 //로그인 사용자

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

스프링 인터셉터 체인

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러

 

스프링 인터셉터 인터페이스

public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse
response,
 Object handler) throws Exception {}
default void postHandle(HttpServletRequest request, HttpServletResponse
response,
 Object handler, @Nullable ModelAndView modelAndView)
throws Exception {}
default void afterCompletion(HttpServletRequest request, HttpServletResponse
response,
 Object handler, @Nullable Exception ex) throws
Exception {}
}

인터셉터는 컨트롤러 호출 전( preHandle ), 호출 후( postHandle ), 요청 완료 이후( afterCompletion )와 같이 단계적으로 잘 세분화 되어 있다.

인터셉터는 어떤 컨트롤러( handler )가 호출되는지 호출 정보도 받을 수 있다. 그리고 어떤 modelAndView 가 반환되는지 응답 정보도 받을 수 있다.

정상 흐름

preHandle : 컨트롤러 호출 전에 호출된다. (더 정확히는 핸들러 어댑터 호출 전에 호출된다.)

preHandle 의 응답값이 true 이면 다음으로 진행하고, false 이면 더는 진행하지 않는다. false 인 경우 나머지 인터셉터는 물론이고, 핸들러 어댑터도 호출되지 않는다. 그림에서 1번에서 끝이 나버린다.

postHandle : 컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다.)

afterCompletion : 뷰가 렌더링 된 이후에 호출된다.

 

예외흐름

예외가 발생시

preHandle : 컨트롤러 호출 전에 호출된다.

postHandle : 컨트롤러에서 예외가 발생하면 postHandle 은 호출되지 않는다.

afterCompletion : afterCompletion 은 항상 호출된다. 이 경우 예외( ex )를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력할 수 있다.

 

afterCompletion은 예외가 발생해도 호출된다.

예외가 발생하면 postHandle() 는 호출되지 않으므로 예외와 무관하게 공통 처리를 하려면 afterCompletion() 을 사용해야 한다. 예외가 발생하면 afterCompletion() 에 예외 정보( ex )를 포함해서 호출된다.

 

WebConfig - 인터셉터 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
 @Override
 public void addInterceptors(InterceptorRegistry registry) {
 registry.addInterceptor(new LogInterceptor())
 .order(1)
 .addPathPatterns("/**")
 .excludePathPatterns("/css/**", "/*.ico", "/error");
 }
 //...
}

WebMvcConfigurer 가 제공하는 addInterceptors() 를 사용해서 인터셉터를 등록할 수 있다.

 

registry.addInterceptor(new LogInterceptor()) : 인터셉터를 등록한다.

order(1) : 인터셉터의 호출 순서를 지정한다. 낮을 수록 먼저 호출된다.

addPathPatterns("/**") : 인터셉터를 적용할 URL 패턴을 지정한다.

excludePathPatterns("/css/**", "/*.ico", "/error") : 인터셉터에서 제외할 패턴을 지정한다

(filter처럼 따로 제외할 url을 만들고 체크 안해도 됨)

 

인터셉터를 이용한 인증체크 코드

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

return 이 false면 종료 true면 컨트롤러 실행

'백 엔드 > spring' 카테고리의 다른 글

Spring Batch 적용기  (2) 2024.04.05
spring 토이 프로젝트 -로그인 기능, 검증 기능 ,jpa  (0) 2022.06.01
스프링 -쿠키,세션  (0) 2022.05.13
스프링 Bean Validation  (0) 2022.05.09
스프링 vaildation  (0) 2022.05.09