🌱 오늘의 주제 : Interceptor란?
🌱 Interceptor란?
컨트롤러(Controller)의 '핸들러(Handler)'를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할수 있는 일종의 필터
interceptr 란 단어는 '낚아채다'라는 의미이다. 해당 단어의 의미와 같이 사용자 요청에 의해 서버에 들어온
Request 객체를 컨트롤러의 핸들러(사용자가 요청한 url에 따라 실행되어야 할 메서드, 이하 핸들러)로
도달하기전에 낚아채서 개발자가 원하는 추가적인 작업을 한후 핸들러로 보낼수 있도록 해주는것이 인터셉터 이다.
🌱 사용하는 이유
개발자는 특정 Controller의 핸들러가 실행되기 전이나 후에 추가적인 작업을 원할때 Interceptor를 사용한다.
(추가적인 작업으로는 로그인체크, 권한 체크 등이 있다.)
권한 체크 예를 통해서 개발자가 인터셉터의 어떠한 이점때문에 사용하기를 원하는지 살펴보겠다.
개발자가 관리자 계정만이 실행할수 있는 Controller 핸들러를 작성한다고 가정하겠다.
개발자는 오직 관리자 계정만 실행할수 있도록 하기 위해 핸들러에 접근하는 사용자가 관리자 인지 확인하는
세션 체크 코드를 각 핸들러에 작성해줘야한다.
작성해주어야 할 핸들러수가 적다면 문제가 되지않는다. 하지만 적용해야할 핸들러가 수천개가 된다면 어떻게될까?
크게 두가지 문제가 생긴다.
1) 메모리 낭비, 서버의 부하가 늘어난다. 적용해야할 핸들러만 수만큼 세션체크 코드를 작성함으로써 반복되는 코드들이 매우 많아지기 때문이다.
2) 코드의 누락에 대한 걱정이다. 사람이 작성을 하는것이기 때문에 누락과 같은 실수가 발생할수 밖에없다.
만약 회원정보에 접근하는 핸들러가 세션체크가 누락되어서 관리자 인지 확인을 안한다면, 자격이 없는 사용자가
접근할수 있게되어 보안적으로 큰 문제를 가지게 된다.
이러한 문제점을 줄이기 위한 수단으로, 개발자는 인터셉터를 사용할수 있다.
인터셉터를 사용하게 되면 개발자는 핸들러 수 만큼 작성했던 세션 체크 코드를 인터셉터 클래스에 한번만 작성하면된다. 이로 인해 코드의 량이 현저히 줄기 대문에 메모리 낭비를 줄일수 있다.
그리고 인터셉터 적용의 유무 기준이 되는 url을 servlet-context.xml에 설정해주게 되면 스프링에서 일괄적으로
해당 url 경로의 핸들러에 인터셉터를 적용해주기 때문에 누락에 대한 위험이 상당히 줄게된다.
🌱 구현 수단
스프링에서 제공하는 org.springframework.web.servlet.HandlerInterceptor 인터페이스를 구현하거나,
org.springframework.web.servlet.handler.HandlerInterceptorAdapter 추상클래스를 오버라이딩 함으로써 자신만의 인터셉터를 만들수 있다. HandlerInterceptorAdapter 추상클래스 경우 HandlerInterceptor 인터페이스를 상속받아 구현되었다.
🌱 메서드
스프링이 제공해주는 HandlerInterceptor 인터페이스와 HandlerInterceptorAdapter 추상클래스에 정의되어 있는
메서드는 preHandle(), postHandle(), afterCompletion() 3가지이다.
1) preHandle()
- 컨트롤러가 호출되기 전에 실행됨
- 컨트롤러가 실행 이전에 처리해야 할 작업이 있는경우 혹은 요청정보를 가공하거나 추가하는경우 사용
- 실행되어야 할 '핸들러'에 대한 정보를 인자값으로 받기때문에 '서블릿 필터'에 비해 세밀하게 로직을 구성할수 있음
- 리턴값이 boolean이다. 리턴이 true 일경우 preHandle() 실행후 핸들러에 접근한다. false일경우 작업을 중단하기 때문에 컨트롤러와 남은 인터셉터가 실행되지않는다.
2) postHandle()
- 핸들러가 실행은 완료 되었지만 아직 View가 생성되기 이전에 호출된다.
- ModelAndView 타입의 정보가 인자값으로 받는다. 따라서 Controller에서 View 정보를 전달하기 위해 작업한 Model 객체의 정보를 참조하거나 조작할수 있다.
- preHandle() 에서 리턴값이 fasle인경우 실행되지않음.
- 적용중인 인터셉터가 여러개 인경우, preHandle()는 역순으로 호출된다.
- 비동기적 요청처리 시에는 처리되지않음.
3) afterCompletion()
- 모든 View에서 최종 결과를 생성하는 일을 포함한 모든 작업이 완료된 후에 실행된다.
- 요청 처리중에 사용한 리소스를 반환해주기 적당한 메서드 이다.
- preHandle() 에서 리턴값이 false인경우 실행되지 않는다.
- 적용중인 인터셉터가 여러개인경우 preHandle()는 역순으로 호출된다.
- 비동기적 요청 처리시에 호출되지않음.
🌱 인터셉터 동작 위치 및 순서
1) 사용자는 서버에 자신이 원하는 작업을 요청하기 위해 url을 통해 Request 객체를 보낸다.
2) DispatcherServlet은 해당 Request 객체를 받아서 분석한뒤 '핸들러 매핑(HandlerMapping)' 에게
사용자의 요청을 처리할 핸들러를 찾도록 요청 한다.
3) 그결과로 핸들러 실행체인(HandlerExectuonChanin)이 동작하게 되는데, 이 핸들러 실행체인은 하나이상의 핸들러 인터셉터를 거쳐서 컨트롤러가 실행될수 있도록 구성되어 있다.
(핸들러 인터셉터를 등록하지 않았다면, 곧바로 컨트롤러가 실행된다. 반대로 하나이상의 인터셉터가 지정되어 있다면 지정된 순서에 따라서 인터셉터를 거쳐서 컨트롤러를 실행한다)
🌱 인터셉터 적용 예시
- WebMvcConfig
package com.language.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.language.Interceptor.PermissionInterceptor;
import com.language.common.FileManagerService;
@Configuration // 설정을 위한 spring bean
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private PermissionInterceptor interceptor;
// interceptor 설정 추가
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(interceptor)
.addPathPatterns("/**") // /** - 자식들까지(아래 디렉토리까지) 전부 확인
.excludePathPatterns("/favicon.ico", "/error", "/static/**", "/user/sign_out_view")
;
}
}
- Interceptor - PermissionInterceptor
package com.language.Interceptor;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class PermissionInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws IOException {
// 요청 URL을 가져온다.
String uri = request.getRequestURI();
logger.info("[$$$$$$$$$ preHandler] uri:{}", uri); // /user/sign_in_view
// 로그인 여부 확인 - 세션 확인
HttpSession session = request.getSession();
Integer userId = (Integer) session.getAttribute("userId");
// 비로그인 && /grammar로 온 경우 => 로그인 페이지로 리다이렉트, return false(기존 컨트롤러 수행 방지)
if (userId == null && uri.startsWith("/grammar")) {
response.sendRedirect("/user/sign_up_view");
return false;
}
// 로그인 && /user로 온 경우 => 커뮤니티 페이지로 리다이렉트, return false(기존 컨트롤러 수행 방지)
if (userId != null && uri.startsWith("/user")) {
response.sendRedirect("/community/community_view");
return false; // 컨트롤러 수행 안함.
}
return true; // 컨트롤러 수행
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler, ModelAndView mav) {
// view 객체가 존재한다는 것은 아직 jsp가 HTML로 변환되기 전이다.
logger.info("[######### grammarHandle]");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
// jsp가 HTML로 최종 변환된 후
logger.info("[@@@@@@@@@@ afterCompletion]");
}
}
'Spring' 카테고리의 다른 글
Spring - OAuth2.0 + Social Login (0) | 2023.08.22 |
---|---|
Spring - web-socket (0) | 2023.08.06 |
Spring - @RequestBody / @ResponseBody 어노테이션 이란? (0) | 2023.05.13 |
Spring - MyBatis (0) | 2023.04.17 |
Spring - DB 연동(MyBatis) Cycle (0) | 2023.04.10 |