로그인-필터, 인터셉터
업데이트:
서블릿 필터
공통 관심 사항
- 애플리케이션 여러 로직에서 공통으로 관심이 있는 것을 공통 관심사(cross-cutting concern) 라고 한다.
- 공통 관심사는 스프링의 AOP로도 해결할 수 있지만, 웹과 관련된 공통관심사는 서블릿 필터 또는 스프링 인터셉터를 사용하는 것이 좋다.
- 웹과 관련된 공통 관심사를 처리할 때는 HTTP의 헤더나 URL의 정보들이 필요한데, 서블릿 필터나 스프링 인터셉터는 HttpServletRequest를 제공한다.
필터 흐름
- HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
- 필터를 적용하면 필터가 호출 된 다음에 서블릿이 호출된다.
- 필터는 특정 URL 패턴에 적용할 수 있다.
필터 제한
- HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 (로그인 사용자)
- HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단하여 서블릿 호출X, 비 로그인 사용자)
필터 체인
- HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
- 필터는 체인으로 구성되고 중간에 자유롭게 추가가 가능하다.
- 데코레이터 패턴 비슷해 보임
필터 인터페이스
- 필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤으로 생성하고 관리한다.
- init() : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출 된다.
- doFilter() : 고객의 요청이 올떄마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.
- destroy() : 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.
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() {}
}
필터 사용 및 등록 방법
- 필터를 사용하려면 필터를 구현해야 한다.
- HTTP 요청이 오면 doFilter가 호출된다.
- HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스다.
- HTTP를 사용하려면 다운 케스팅 하면 된다.
- chain.doFilter(request, response);
- 가장 중요한 부분으로 다음 필터가 있으면 필터를 호출하고, 없으면 서블릿을 호출한다.
- 이 로직을 호출하지 않으면 다음 로직으로 진행이 되지 않는다.
@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 doFilter");
}
}
- 스프링 부트 사용시 FilterRegistrationBean을 등록해서 사용하면 된다.
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
//실행 순서 지정
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
@ServletComponentScan, @WebFilter(filterName = “”, urlPatterns = “”) 로 등록이 가능하지만 필터 순서 조절이 되지 않는다.
실무에서 HTTP 요청시 같은 요청의 로그에 모두 같은 식별자를 자동으로 남기는 방법은 logback mdc로 검색
인증 체크
- 인증 회원인지 미인증 회원인지 필터 추가를 통해 확인한다.
- whiteList 를 통해 미인증 상황에서도 접근이 가능하도록 체크 해준다. 그렇지 않으면 무한으로 필터가 걸린다.
- redirect를 사용해서 로그인 후 원래 화면으로 돌아가도록 설정한다.
- 필터를 사용함으로써 로그인 정책이 변경 되어도 필터만 수정하면된다(SRP 준수)
- 스프링 시큐리티도 필터를 통한 기능으로 구현되어 있다.
@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);
//로그인으로 reditrec
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);
}
}
빈을 WebConfig에 등록한다
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
댓글남기기