반응형
1. Filter, Interceptor 란?
- Filter와 Interceptor은 사용자가 특정 URL에 접근, 요청 시 해당 요청에 대한 응답을 하기 전에 요청을 가로채 확인하고 통과 시킬지 말지를 결정할 수 있게 하는 역할
- 어떤 사용자가 특정 URL에 접근 시 해당 요청에 대한 로그를 남겨야 하는 상황이나 로그인 한 사용자(인증) 혹은 특정 권한이 있는 사용자(인가)만 특정 URL에 접근할 수 있는 상황 등에 사용할 수 있음
2. Filter와 Interceptor의 차이
- Filter와 Interceptor의 사용 목적은 비슷하지만 차이점이 존재
- Filter은 JAVA에서 제공하는 기능이지만, Interceptor은 Spring에서 제공하는 기능
- Filter은 Spring과 무관하게 전역적으로 처리하는 작업들을 수행할 때 사용할 수 있음
- Interceptor은 Controller(Handler)에 관한 요청과 응답에 대해 처리할 때 사용할 수 있음
- Filter와 Interceptor은 실행되는 위치가 다름
- HTTP 요청 -> WAS -> Filter -> Spring의 Dispatcher Servlet -> Spring Interceptor -> Controller
- Filter Chain과 Interceptor Chain을 사용하면 여러개의 Filter, Interceptor 사용 가능
- ex) HTTP 요청 -> WAS -> Filter1 -> Filter2 -> Filter3 -> Dispatcher Servlet -> Interceptor1 -> Interceptor2 -> Controller
- HTTP 요청 -> WAS -> Filter -> Spring의 Dispatcher Servlet -> Spring Interceptor -> Controller
- Filter은 Dispatcher Servlet 앞에서 동작하기 때문에 ServletRequest, ServletResponse를 받아서 처리하지만, Interceptor은 HttpServletRequest, HttpServletResponse를 받아서 처리
- ex) 인증 기능 구현 시
- Filter은 인증에 성공하면 권한을 부여하고 다음 Filter로 진행 (진행하지 않을 수도 있음)
- Interceptor은 인증에 실패하면 다음으로 진행 X
- Interceptor은 Spring에서 제공하는 기능이기 때문에 스프링 MVC 구조에 조금 더 특화되었음
- Interceptor에서는 어떤 Controller(Handler)가 호출 되었는지에 대한 호출 정보도 받을 수 있고, modelAndView도 받을 수 있음
- Interceptor의 afterCompletion을 사용해 예외가 발생해도 호출하는 부분이 있어 예외를 따로 처리할 수 있음
3. Filter 예제
- Filter는 3개의 메서드를 가짐
- init()
- Filter 초기화 메서드
- Servlet Container Filter을 Singleton 객체로 생성하고 관리
- doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- 사용자의 요청이 올때마다 해당 메서드 호출
- 여기에 필터의 로직을 구현하면 됨
- destroy()
- Filter 종료 메서드
- Servlet Container가 종료될 때 호출
- chain.doFilter(request, response)를 해줘야 다음 필터로 이동
3.1. LogFilter 예제
- 사용자의 모든 요청에 대한 로그를 Filter를 사용해 기록해보는 예제
3.1.1. LogFilter 생성
@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 httpServletRequest = (HttpServletRequest) request;
String requestURI = httpServletRequest.getRequestURI();
// 요청의 추적을 위해 UUID 사용
String uuid = UUID.randomUUID().toString();
request.setAttribute("logId", uuid);
try {
log.info("Log Filter : doFilter : REQUEST [{}][{}]", uuid, requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
} finally {
log.info("Log Filter : doFilter : RESPONSE [{}][{}]", uuid, requestURI);
}
}
@Override
public void destroy() {
log.info("Log Filter destroy");
}
}
3.1.2. LogFilter 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
// 구현한 필터 등록
filterFilterRegistrationBean.setFilter(new LogFilter());
// 필터 순서 지정
filterFilterRegistrationBean.setOrder(1);
// 필터가 적용될 URL 지정
filterFilterRegistrationBean.addUrlPatterns("/filter-test/pass");
return filterFilterRegistrationBean;
}
}
3.1.3. FilterController 생성
@Slf4j
@Controller
@RequestMapping("/filter-test")
public class FilterController {
@GetMapping("/all-pass")
public String allPass() {
log.info("all-pass 호출");
return "filterPassPage";
}
@GetMapping("/pass")
public String pass(HttpServletRequest request) {
// Log Filter에서 넣어준 UUID 꺼내와서 출력
log.info("FilterController : REQUEST [{}][{}]", request.getAttribute("logId"), request.getRequestURI());
return "filterPassPage";
}
}
3.1.4. 결과
- /filter-test/pass로 두 번 요청 후 /filter-test/all-pass로 요청 시 결과
- /filter-test/pass로 요청 시 Filter을 거치기 때문에 LogFilter에서 로그가 찍힌것을 확인할 수 있음
- 두 번의 요청의 UUID가 다름 => 이를 활용하여 하나의 요청 추적 가능
- /filter-test/all-pass로 요청 시 Filter을 거치지 않기 때문에 LogFilter에 들리지 않는것을 확인할 수 있고 logId가 null인 것도 확인할 수 있음
4. Interceptor 예제
- Interceptor는 3개의 메서드를 가짐
- preHandle()
- Dispatcher Servlet 호출 이후 Controller 호출 직전에 호출
- 로그인 기능 구현시 이 메서드에서 사용자 권한 체크 후 권한이 없으면 Controller를 호출하지 않으면 됨
- postHandle()
- Controller 호출 이후 View 렌더링 전에 호출
- 만약 Controller에서 Exception이 발생한다면 postHandle은 실행되지 않음
- 이 메서드는 Controller에서 받은 ModelAndView를 출력하는 용도 등으로 사용 가능
- afterCompletion()
- View가 렌더링 된 이후에 호출
- afterCompletion은 Exception이 발생해도 호출되기 때문에 exception에 대한 로그를 찍고 싶은 상황 등에 사용 가능
4.1. LogInterceptor 예제
- 사용자의 모든 요청에 대한 로그를 Interceptor를 사용해 기록해보는 예제
- uuid를 사용하여 로그를 추적하는 기능은 Filter와 같으니 Interceptor에서는 구현 생략
4.1.1. LogInterceptor 생성
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle : REQUEST [{}][{}]", requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle : RESPONSE [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
log.info("afterCompletion : RESPONSE [{}][{}]", requestURI, handler);
if(ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
4.1.2. LogInterceptor 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/interceptor-test/pass")
.excludePathPatterns("/interceptor-test/all-pass");
}
}
- /interceptor-test/pass는 LogInterceptor을 통과
- /interceptor-test/all-pass는 LogInterceptor 통과 X
4.1.3. LogController 생성
@Slf4j
@Controller
@RequestMapping("/interceptor-test")
public class InterceptorController {
@GetMapping("/all-pass")
public String allPass(HttpServletRequest request) {
log.info("REQUEST [{}]", request.getRequestURI());
return "interceptorPassPage";
}
@GetMapping("/pass")
public String pass(HttpServletRequest request, Model model) {
log.info("REQUEST [{}]", request.getRequestURI());
model.addAttribute("Key1", "Value1");
model.addAttribute("Key2", "Value2");
return "interceptorPassPage";
}
}
4.1.4. 결과
- /interceptor-test/pass로 요청 후 /interceptor-test/all-pass로 요청 시 결과
- /interceptor-test/pass로 요청 시 Interceptor을 거치기 때문에 LogInterceptor에서 로그가 찍힌것을 확인할 수 있음
- preHandle()에서는 해당 요청을 처리하는 Handler(InterceptorController)을 알 수 있음
- postHandle()에서는 해당 요청의 Response에 대한 modelAndView를 확인할 수 있음
- afterCompletion()에서도 preHandle()과 마찬가지로 해당 요청을 처리하는 Handler을 알 수 있고, Exception이 발생한다면 exception을 출력해 볼 수도 있음
- /interceptor-test/all-pass로 요청 시 Interceptor을 거치지 않기 때문에 LogInterceptor에 들리지 않는것을 확인할 수 있음
4.2. Filter와 Interceptor을 사용한 인증 기능 구현 예제
- 이 예제에서는 로그인 기능을 제대로 구현하지는 않음
- 하지만 이 예제를 활용해 로그인 기능을 구현할 수 있음
- 만약 로그인이 성공했다면 Request의 Header에 "pass" 값이 true이고, 실패했다면 Header에 "pass"값이 없거나 false라고 가정한 상황
- Filter와 Interceptor에서 request.getHeader("pass")를 사용해 인증 기능을 구현해 보자
- 로그인 기능 구현을 하려면 Header에 Token을 넣어 전송하거나, Cookie, Session 등의 방법 활용하면 됨
4.2.1. AuthFilter 생성
@Slf4j
public class AuthFilter implements Filter {
// 권한 인증을 체크 하지 않을 URL
private static final String[] whiteList = {"/filter-test/all-pass", "/filter-test/fail"};
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String requestURI = httpServletRequest.getRequestURI();
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 요청의 추적을 위해 UUID 사용
String uuid = UUID.randomUUID().toString();
request.setAttribute("logId", uuid);
try {
log.info("doFilter : REQUEST [{}][{}]", uuid, requestURI);
// requestURI가 Auth Check가 필요한 요청이라면 체크
if(isAuthCheckPath(requestURI)) {
log.info("doFilter : 인증 체크 로직 실행 REQUEST [{}][{}]", uuid, requestURI);
boolean auth = Boolean.valueOf(httpServletRequest.getHeader("pass"));
if(auth != true) {
log.info("dofilter : 인증 실패 : REQUEST [{}][{}]", uuid, requestURI);
httpServletResponse.sendRedirect("/filter-test/fail");
return;
}
}
log.info("dofilter : 인증 성공 : REQUEST [{}][{}]", uuid, requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
} finally {
log.info("dofilter : RESPONSE [{}][{}]", uuid, requestURI);
}
}
private boolean isAuthCheckPath(String requestURI) {
return !PatternMatchUtils.simpleMatch(whiteList, requestURI);
}
}
- 이렇게 생성한 Filter은 LogFilter와 같이 등록해주면 사용 가능
- whiteList와 isAuthCheckPath 메서드를 사용해 검증이 필요한 URL 인지 체크하기 때문에 Filter을 등록할 때, 모든 URL이 AuthFilter을 거치게 해도 됨
- FilterController을 그대로 사용하되 로그 출력 형식만 조금 수정
4.2.2. 결과
- AuthFilter을 거치지 않는 URL
- AuthFilter을 거치는 URL => 인증 실패
- /filter-test/pass에 요청 -> 인증 실패 -> /filter-test/fail로 redirect -> 이 요청은 인증이 필요 없음 -> 인증 성공 -> filterFailPage Return
- AuthFilter을 거치는 URL => 인증 성공
4.2.3. AuthInterceptor 생성
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute("logId", uuid);
boolean auth = Boolean.valueOf(request.getHeader("pass"));
if(auth != true) {
log.info("preHandle : 인증 실패 : REQUEST [{}][{}]", uuid, requestURI);
response.sendRedirect("/interceptor-test/fail");
return false;
}
log.info("preHandle : 인증 성공 : REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle : RESPONSE [{}][{}]", request.getAttribute("logId"), modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String) request.getAttribute("logId");
log.info("afterCompletion : RESPONSE [{}][{}][{}]", logId, requestURI, handler);
if(ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
- 이렇게 생성한 Interceptor은 LogInterceptor와 같이 등록해주면 사용 가능
- Authinterceptor은 AuthFilter와 달리 .addPathPatterns로 인증이 필요한 URL을 따로 등록해 주었음
4.2.4. 결과
- AuthInterceptor을 거치지 않는 URL
- AuthInterceptor을 거치는 URL => 인증 실패
- AuthInterceptor을 거치는 URL => 인증 성공
4.3. Filter, Interceptor 동시 적용 예제
- 위에서 만들어놓은 LogFilter, LogInterceptor을 동시에 적용하는 URL을 만들어 순서를 확인해보는 예제
- URL 부분만 수정해주면 되기 때문에 코드 생략
4.3.1. 결과
- 실행 순서 : Filter의 init() -> Filter의 doFilter() try 부분 -> Interceptor의 preHandle() -> Controller -> Interceptor의 postHandle() -> Interceptor의 afterCompletion() -> Filter의 doFilter() finally 부분 -> Filter의 destroy()
- Filter와 Interceptor에서 저장한 logId는 각자 따로 사용
반응형
'Spring Boot > 문법 정리' 카테고리의 다른 글
[Spring Boot] 파일 업로드 (6) | 2022.06.29 |
---|---|
[Spring Boot] Converter & Formatter (0) | 2022.06.28 |
[Spring Boot] Validation, 에러메세지 설정방법 (2) | 2022.06.11 |
[Spring Boot] Thymeleaf - 메세지, 국제화 (0) | 2022.06.10 |
[Spring Boot] Thymeleaf - form 관련 기능 정리 (1) | 2022.06.09 |
↓ 클릭시 이동
1. Filter, Interceptor 란?2. Filter와 Interceptor의 차이3. Filter 예제3.1. LogFilter 예제3.1.1. LogFilter 생성3.1.2. LogFilter 등록3.1.3. FilterController 생성3.1.4. 결과4. Interceptor 예제4.1. LogInterceptor 예제4.1.1. LogInterceptor 생성4.1.2. LogInterceptor 등록4.1.3. LogController 생성4.1.4. 결과4.2. Filter와 Interceptor을 사용한 인증 기능 구현 예제4.2.1. AuthFilter 생성4.2.2. 결과4.2.3. AuthInterceptor 생성4.2.4. 결과4.3. Filter, Interceptor 동시 적용 예제4.3.1. 결과