앞선 포스트에서는 Filter에 대해 알아보았고 이번 포스팅에서는 Interceptor를 알아보겠습니다.
Interceptor ( 인터셉터 : 낚아채다 )
Spring에서의 Interceptor 역할은 Client로부터 들어오는 요청( HttpRequest )을 Controller의 Handler로 도달하기 전에 가로채거나 , Controller로 부터 보내는 응답( HttpResponse )을 가로채는 역할을 합니다.
HandlerInterceptor에서 가로채어 원하는 추가적인 로직을 수행한 후에 Controller의 Handler로 보낼 수 있도록 하는 Module입니다.
( Handler : Client가 요청한 url에 따라 실행되어야 할 Method )
아래의 그림은 Dispather Servlet의 내부 처리 과정입니다.
최초에 Dispatcher Servlet에 요청이 들어오면 바로 Handler를 찾고 실행하는 것이 아닌 몇 가지 선행 처리 작업을 실행합니다.
이 과정을 요청 선처리 작업이라고 합니다.
요청 선처리 작업
- Request에 해당하는 Locale을 결정하고 노출한다.
- org.springframework.web.servlet.LocalResolver를 이용해 java.util.Locale을 결정합니다.
- 결정된 Locale을 RequestContextHolder에 저장합니다.
- org.springframework.web.context.request.RequestContextHoler 객체를 이용합니다.
- 프레임워크의 코드가 단계마다 요청 정보를 넘겨받지 않고 손쉽게 요청 정보를 얻기 위해서입니다.
- FlahMapManager를 통해 FlashMap을 생성합니다.
- org.springframework.web.servlet.FlashMapManager 객체를 이용해 FlashMap을 생성합니다.
- Multipart HTTP( headers 정보 : Axios 포스팅 참고 ) 요청 정보 확인 후에 MultipartResolver로 결정합니다.
- Multipart가 맞다면 org.springframework.web.multipart.MultipartHttpServletRequest로 처리합니다.
- HandlerExecutionChain 결정 단계로 넘어갑니다.
이제 DispatcherServlet은 HandlerMapping에게 Client의 HttpRequest 객체를 수행할 Handler를 찾도록 합니다.
이 과정에서 HandlerExecutionChain ( 핸들러 실행 체인 )이 작동합니다.
이때 행해지는 과정을 HandlerExecutionChain 결정이라고 합니다.
HandlerExecutionChain은 하나 이상의 HandlerInterceptor를 거쳐 Controller가 실행되도록 구성되어 있습니다.
HandlerInterceptor에서 Request에 대해 원하는 작업을 수행한 후 Request 객체를 Controller로 전달합니다.
대표적인 HandlerInterceptor 작업
- Login Session 검증
- Header 검증
- (Jwt) Token 검증
- URL Handling
- ...
Interceptor 활용을 통해서 얻을 수 있는 크게 3가지가 있습니다.
- 공통 코드 사용으로 코드 재사용성 증가
- 메모리 낭비, 서버의 부하 감소
- 코드 누락에 대한 위험성 감소
Interceptor의 예시 코드입니다.
/admin에 대한 모든 Handler가 요청 헤더 ( Request Header )의 JWT 검증을 수행합니다.
@RequiredArgsConstructor //생성자 주입으로 느슨한 결합 유도합니다.
@RequestMapping("/admin")
@RestController // 요청에 대한 응답을 JSON 형태로 객체를 반환합니다.
public Class AdminController{
private AdminService adminService;
@PostMapping("/login")
public String adminLogin(@RequestHeader(value = "Authorization") String jwt, UserDTO userDto) throws JwtException {
boolean flag = adminService.login(jwt, userDto);
if(flag){
return "ok";
}
return "no";
}
@GetMapping("/{id}")
public String adminDetail(@PathVariable(name = "id") String id){
boolean flag = adminService.detail(id);
if(flag){
return "ok";
}
return "no";
}
@PostMapping
public String admin... throws JwtException{
...
}
...
}
대다수의 Handler에서 JWT 토큰 검증을 수행하여 JwtException을 throw 하는 로직이 계속해서 발생하게 된다.
( 메모리, 서버의 부하 증가 )
또안 adminDetail Handler와 같은 JWT 검증 로직 누락이 존재하여 위험성이 증가합니다.
이러한 상황에 Interceptor를 활용해 JWT 검증 로직은 Interceptor에서 한 번만 검증하여
@RequestMapping("/admin")에 대한 요청만 Interceptor를 적용하여 메모리, 서버의 부하를 감소시킬 수 있습니다.
코드 누락의 위험성 또한 감소합니다.
Interceptor 구현하기
Spring에서 Interceptor 구현 방법은 두 가지가 있습니다.
- HandlerInterceptor 인터페이스를 구현하는 방법
- HandlerInterceptorAdapter 클래스를 상속 받는 방법
먼저 인터페이스를 통해 구현하는 예시 코드입니다.
// Handler로 보내기 전에 처리하는 인터셉터
@RequiredArgsConstructor
public class adminInterceptorImpl implements HandlerInterceptor{
private AuthService authService;
// 매개변수 Object는 핸들러의 정보를 의미합니다. ( RequestMapping, DefaultServletHandler )
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException. JwtException {
String token = request.getHeader("Authorization");
if(token == null){
throw new AuthenticationException("not exist Token");
}
try{
authService.vertifyJwt(token);
} catch (JwtException e){
throw new AuthenticationException("token is not valid");
}
// 반환값이 false라면 Handler로 보내지 않는다.
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{
// 요청에 대한 응답을 View에 렌더링 하기 전에 호출되는 메소드
// ModelAndView 정보를 알 수 있다.
// 특정 View ( 페이지 )에서 ModelAndView 값을 수정해야할 필요가 있다면
// postHandle 메소드에서 작업을 추가하면 됩니다.
System.out.println("이제 view에 렌더링 할 예정입니다.")
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception e) throws Exception {
// View에 렌더링과 모든 동작이 끝난 후에 작동하는 메소드
}
}
다음은 HandlerInterceptorAdapter 클래스를 상속받는 예제입니다.
@RequiredArgsConstructor
public class adminInterceptor extends HandlerInterceptorAdaper{
private AuthService authService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException, JwtException{
String token = request.getHeader("Authorization");
if(token == null){
throw new AuthenticationException("not exist Token");
}
try{
authService.vertifyJwt(token);
} catch (JwtException e){
throw new AuthenticationException("token is not valid");
}
// 반환값이 false라면 Handler로 보내지 않는다.
return true;
}
}
이러한 Interceptor 로직으로 Controller의 Method에 접근하기 전 후 필요한 작업을 수행할 수 있습니다.
환경 구성으로 pom.xml에 Dependency를 추가합니다.
Maven
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework-version}</version>
</dependency>
Gradle
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
DispatcherServlet의 Context 파일 ( servlet-context.xml )에 Interceptor Class와 적용될 URL을 기재합니다.
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
// Interceptor에서 제외할 경로가 있을 경우
<mvc:exclude-mapping path="/demoPage"/>
<bean id="jwtInterceptor" class="project.config.interceptor.JwtInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
이러한 단순 과정으로도 Spring에서 Interceptor를 활용할 수 있습니다.
다음 포스팅에서는 AOP에 대해 알아보겠습니다.
'Backend > Spring' 카테고리의 다른 글
자주 사용되는 Spring 어노테이션에 대해 알아보자! (0) | 2022.01.21 |
---|---|
Spring AOP의 구동 원리와 Proxy와의 관계 (0) | 2022.01.13 |
AOP, Filter, Interceptor의 정의와 차이점 (3) - AOP편 (0) | 2022.01.11 |
AOP, Filter, Interceptor의 정의와 차이점 (1) - Filter 편 (0) | 2022.01.10 |
Spring( 봄 ) ?? 스프링의 원리와 기초 (0) | 2022.01.10 |