[Spring] Interceptor란? 구현 예제와 함께 (HandlerInterceptor, HandlerInterceptorAdapter)

gillog·2021년 7월 21일
1

Spring

목록 보기
13/22

🙆‍♂️ import 🙇‍♂️

[Spring Framework] HandlerInterceptorAdapter[jhkang-dev]

[spring] Spring Interceptor 란?(HandlerInterceptor, HandlerInterceptorAdapter)[Kim VamPa]


Interceptor

Interceptor낚아채다의 의미를 가지고있다.

Client에서 Server로 들어온 Request 객체를,

ControllerHandler로 도달하기 전 가로채어,

원하는 추가 작업이나 로직을 수행 한 후 Handler로 보낼 수 있도록 해주는 Module이다.
Handler : 사용자가 요청한 url에 따라 실행되어야 할 Method


아래 그림을 통해 Spring 단에서 살펴보면,

DispatcherServletHandlerMapping에게 Client Request를 수행 할 Handler를 찾도록 요청을 보낸다.

이때 HandlerExecutionChain(핸들러 실행 체인)이 동작하는데,

HandlerExecutionChain하나 이상의 HandlerInterceptor를 거쳐 Controller가 실행되도록 구성되어 있다.
HandlerInterceptor를 등록하지 않았을 경우 바로 Controller 실행

HandlerInterceptor를 거쳐 Request에 대해 원하는 작업, 로직을 수행한 후 ControllerRequest 객체를 전달한다.
Login Session 검증, Header 검증, Token 검증, URL Handling 등등...

Why ??

InterceptorControllerHandler가 실행되기 전이나 후추가적인 작업이 수행되어야 할때 사용한다.

이때 Interceptor를 활용하여 얻을 수 있는 장점은 크게 아래 세 가지와 같다.

  • 공통 코드 사용으로 코드 재사용성 증가

  • 메모리 낭비, 서버 부하 감소

  • 코드 누락에 대한 위험성 감소

각 장점을 아래 예시 코드와 함께 살펴보자.

@AllArgsConstructor
@RequestMapping("admin")
@RestController
public class AdminController {

    private AdminService adminService;

    @PostMapping("/login")
    public ResponseEntity<CommonResponse> loginAdmin(@RequestHeader(value = "Authorization") String jwt, @RequestBody UserDTO userDTO) throws JwtException {
    	...jwt 검증 및 로그인 검사..
        return ResponseEntity.ok().body(new CommonResponse());
    }
}

만약 위와 같이 Request Header에서 JWT를 받아검증 실패시 JwtExceptionthrowJWT 검증을 수행하고,

검증 성공시 Request Body의 정보로 관리자(admin)의 login을 수행하는 Handler가 있다고 가정하자.

초기 개발단계에서는 위와 같은 Handler 하나 뿐이었지만, 만약 /admin에 대한 모든 Handler에서 JWT 검증을 수행해야 한다면 어떨까


@AllArgsConstructor
@RequestMapping("admin")
@RestController
public class AdminController {

    private AdminService adminService;

    @PostMapping("/login")
    public ResponseEntity<CommonResponse> loginAdmin(@RequestHeader(value = "Authorization") String jwt, @RequestBody UserDTO userDTO) throws JwtException {
    	...jwt 검증 및 로그인 검사..
        return ResponseEntity.ok().body(new CommonResponse());
    }
    
    @PostMapping("/register")
    public ResponseEntity<CommonResponse> registerAdmin(@RequestHeader(value = "Authorization") String jwt, @RequestBody UserDTO userDTO) throws JwtException {
    	...jwt 검증 및 회원가입 수행..
        return ResponseEntity.ok().body(new CommonResponse());
    }
 
    ....
 
     @GetMapping("/{id}")
    public ResponseEntity<CommonResponse> detailAdmin(@PathVariable(name="id") String id){
    	...jwt 검증 및 세부 정보 ..
        return ResponseEntity.ok().body(new CommonResponse());
    }
}

모든 Handler에서 JWT 검증을 수행하며 JwtException을 Throw 하는 것 로직이 계속해서 늘어나게된다.
모든 Handler에서 같은 로직을 수행하는 코드가 존재해 메모리, 서버 부하 증가

또한 detailAdmin와 같이 특정 Handler에서 검증 로직이 누락되어 개발될 가능성이 존재한다.
검증 로직 누락으로 인해 위험성 증가

위와 같은 상황에서 Interceptor를 활용하면 JWT 검증 로직을 Interceptor에서 한번만 작성하고,

/admin/*에 대한 요청에 Interceptor를 적용하여 작성 코드량을 줄여 메모리 낭비를 줄일 수 있고,

Interceptor 적용의 기준 url을 명시해 줌으로써 일괄적으로 /admin/*에 해당하는 Handler들에 적용해주어 코드 누락의 위험성을 줄일 수 있다.


Interceptor 구현하기

Spring에서 Interceptor의 구현HandlerInterceptor(Interface)HandlerInterceptorAdapter(Abstract Class)로 구현할 수 있다.

HandlerInterceptorAdapter Abstract Class는 HandlerInterceptor Interface를 상속받아 구현

위 두가지 중 하나를 구현하는 Class를 생성한 후,

DispatcherServlet의 Context(servlet-context.xml)에 작성 Interceptor Class를 Bean 등록하고,

적용 Url을 명시해주면 된다.

구현 메소드

Interceptor 구현을 위해 아래와 같은 네 가지 Method가 있다.

PreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

Controller(RequestMapping 선언 Handler) 실행 직전에 동작하는 method.

return 값이 true일 경우 정상적으로 진행이 되고,

false일 경우 실행 종료
Controller 진입 X

Parameter 중 Object handlerHandlerMapping이 찾은 Controller Class 객체이다.


PostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

Controller 진입 후 ViewRendering 되기 전 수행된다.

Parameter 중 ModelAndView modelAndView를 통해 화면 단에 들어가는 Data 등의 조작이 가능하다.


afterComplete(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)

Controller 진입 후 View가 정상적으로 Rendering 된 후 수행된다.


afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)

Servlet 3.0부터 비동기 요청이 가능해지며 생겨난 method로,

비동기 요청PostHandleafterCompletion method를 수행하지 않고 이 메서드를 수행한다.


구현 예제

  1. Maven을 기준으로 pom.xmlspring-web Dependency가 존재하는지 확인한다.
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
  1. HandlerInterceptorHandlerInterceptorAdapter를 구현하는 자신만의 Interceptor Class를 구현한다.
// HandlerInterceptor 상속
public class CustomInterceptor implements HandlerInterceptor
{
 
}
 
// HandlerInterceptorAdaptor 상속
public class CustomInterceptor extends HandlerInterceptorAdapter
{
 
}
public class JwtInterceptor extends HandlerInterceptorAdapter {
	private static final Logger LOG = Logger.getLogger(JwtInterceptor.class);
	@Autowired
	private AuthService authService;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws IOException, JwtException {
		LOG.info("jwt interceptor cateched!!!");
		String jwt = request.getHeader("Authorization");
		if (jwt == null) {
			LOG.error("JWT is null");
			throw new AuthenticationException("JWT is null");
		}
		try {
			authService.vertifyJwt(jwt);
		} catch (JwtException e) {
			LOG.error(jwt);
			LOG.error("JWT is not valid");
			throw new AuthenticationException("JWT is not valid");
		}
		return true;
	}
}
  1. DistpatcherServlet의 Context 파일(servlet-context.xml)에 Interceptor Class와 적용 URL을 명시한다.
	<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/**"/>
			<bean id="jwtInterceptor" class="project.config.interceptor.JwtInterceptor"/>
		</mvc:interceptor>
	</mvc:interceptors>

만약 Interceptor에서 제외하고 싶은 경로가 있을경우 <mvc:exclude-mapping path=""/>를 활용한다.

	<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/**"/>
                        <mvc:exclude-mapping path="/swagger"/>
			<bean id="jwtInterceptor" class="project.config.interceptor.JwtInterceptor"/>
		</mvc:interceptor>
	</mvc:interceptors>

위 단순 세 과정 만으로 Spring에서 Interceptor를 활용할 수 있다.

profile
🚀 기록보단 길록을 ⭐

0개의 댓글