Handler Interceptor는 말 그대로 handler(Controller)로 가는 요청을 가로채는 녀석으로 Servlet의 Filter와 유사한 녀석이다.
주요 역할은 여러 컨트롤러에서 공통적으로 사용되는 기능을 정의하는데 예를 들어 여러 컨트롤러에서 사용되는 공통적인 model attribute를 설정하거나 request에 대한 검사, response header 설정 등 무궁무진하다.
HandlerInterceptor interface
Handler Interceptor를 만들기 위해서는 org.springframework.web.servlet.HandlerInterceptor를 구현한다. 이 인터페이스에는 3개의 주요 메서드가 있는데 preHandle, postHandle, afterCompletion이 그것이다. 메서드 이름에 나와있듯이 메서스들은 handler 동작 전, 후 그리고 view의 response까지 완전히 끝난 시점에 동작한다.
고맙게도 HandlerInterceptor의 메서드들은 파라미터로 HttpServletRequest, HttpServletResponse, Handler, Exception을 파라미터로 받기 때문에 웹과 관련된 모든 일을 처리할 수 있다.
성능 측정을 위한 interceptor 만들기
다음은 Controller의 성능을 로깅하는 PerformanceInterceptor의 작성 예이다.
addInterceptor를 이용해서 인터셉터를 추가한 후 별도의 경로 설정이 없다면 모든 요청에 대해서 적용하겠다는 의도이다. 적용할 경로를 추가할 때는 addPathPatterns, 제외할 경로를 추가할 때는 excludePathPatterns를 적용하는데 경로는 Ant 표현식을 따른다.
Ant 표현식으로 경로를 작성할 때는 3개의 특수문자를 사용한다.
?: 1개의 글자
/test/member??.info: /test 아래 member로 시작하고 두 개의 임의의 문자가 나온 후. info로 끝나는 경로
*: 0개 이상의 글자
/test/?*.info: /test 아래 최소 한 글자가 있고 .info로 끝나는 모든 경로
**: 0개 이상의 디렉터리
/test/**/info: /test의 하위 디렉터리에 depth에 상관없이/info로 끝나는 모든 경로로 /test/math/info, /test/math/abs/info … 등
따라서 위의 예에서 pi는 .css, .js가 아닌 모든 요청을 대상으로 동작한다.
HandlerInterceptor를 이용한 session 관리
특정 웹 페이지에 접근할 때 이미 로그인한 사용자인지 확인하고 로그인하지 않은 경우는 로그인을 유도해야 하는 경우가 많다. 이때 페이지마다 로그인 여부를 체크하는 것은 매우 번거로운 일이다. 이때 HandlerInterceptor를 써먹어보자.
클라이언트가 session에 로그인 정보(loginUser) 없이 /secret에 접근한다. 이때 Session Handler Interceptor가 요청을 가로채서 session에서 loginUser 정보를 확인한다.
Handler Interceptor가 session에서 정보를 확인하지 못한 경우 /login으로 redirect 시킴으로 로그인을 유도한다.
브라우저가 get 방식으로/login을 요청하고 login.html이 서비스된다.
login.html에서 로그인 정보를 입력하고 post로 /login을 호출하면 service를 이용해 로그인 가능 여부를 파악한다.
login이 성공했다면 session에 loginUser 속성을 설정 후 redirect:/secret를 반환해서 다시 /secret를 호출하도록 한다.
이제 session에 loginUser 정보가 있으므로 /secret에 접근이 허용된다.
로그인이 반드시 필요한 페이지
다음 페이지는 반드시 로그인해야 접근할 수 있는 페이지인 secret.html이다. 이 페이지가 열리면 세션에 loginUser 정보가 있을 것이고 그 정보를 화면에 출력하자.
<!--secret.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>여기는 중요한 페이지입니다.</h1>
<!-- session 속성 중에서 loginUser를 출력한다. -->
<h2 th:text="${session.loginUser}">로그인 사용자 정보</h2>
</body>
</html>
HandlerInterceptor에서 세션 확인 및 처리
이제 세션을 모니터링하는 HandlerInterceptor를 만들자. 만약 세션에 loginUser라는 속성이 있다면 그대로 진행하고 없다면 login 페이지로 유도한다.
package com.eshome.mvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@ComponentpublicclassSessionInterceptorimplementsHandlerInterceptor{
@OverridepublicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
HttpSessionsession= request.getSession();
// 세션에 loginUser 정보가 있다면 그대로 진행한다.if (session.getAttribute("loginUser") != null) {
returntrue;
// 세션에 정보가 없다면 login을 유도한다.
} else {
// login 성공 후 다시 원래의 목적지로 갈 수 있게 prev 정보를 저장한다.
response.sendRedirect(request.getContextPath() + "login?prev=" + request.getServletPath());
returnfalse;
}
}
}
SessionInterceptor 설정
MVCConfig에 SessionInterceptor를 설정해 주자. /secret 요청에 대해서 SessionInterceptor를 적용한다.
@Autowired
SessionInterceptor si;
@Autowired
PerformanceInterceptor pi;
@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){
// 성능 측정을 위한 HandlerInterceptor 설정
registry.addInterceptor(pi)
.addPathPatterns("/**")
.excludePathPatterns("/**/*.css", "/**/*.js");
// 세션에서 loginUser를 체크하기 위한 HandlerInterceptor 설정
registry.addInterceptor(si).addPathPatterns("/secret");
}
login.html
다음으로 login 정보를 입력하기 위한 login.html을 작성해 보자. 로그인 성공 후 특정 페이지로 이동을 희망하기 때문에 해당 정보를 hidden 타입의 input에 저장해 두었다.
<!DOCTYPE html><htmlxmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><title>Insert title here</title></head><body><formth:action="@{/login}"method="post"><fieldset><legend>로그인</legend><inputtype="hidden"name="prev"th:value="${param.prev}"><inputtype="text"name="id"placeholder="아이디입력"><inputtype="text"name="password"placeholder="비번입력"><inputtype="submit"></fieldset></form></body></html>
login 처리를 위한 서비스
HelloService와 HelloServiceImpl에 로그인할 수 있는 login 메서드를 구현해 주자. 단지 문자열 id가 hong과 같고 password가 1234인지 만 판단하도록 간단히 구성한다.