저번 뉴스피드 프로젝트는 Filter로 인증인가를 진행했다.
그런데 이번에는 특강 때 Handler Interceptor에 대해 배워서 이를 써먹어보기로 했다.
Handler Interceptor 란?
: Spring Context에서 특정 URI 호출을 가로채어 Controller에 닿기 전 후에 제어를 할 수 있게끔 하는 interceptor
필터가 spring 컨텍스트 안에 들어가기 전에 한번 걸러주는 역할이라면
handler interceptor는 request가 필터를 지나 컨트롤러에 도달하기 전이나 후에 걸러주는 역할인 것이다.
Filter | Handler Interceptor | |
---|---|---|
호출 시점 | DispatcherServlet 처리 전에 호출 | DispatcherServlet 실행 후에 호출 |
실행 영역 | 동일한 웹 애플리케이션의 영역 내에서 동작하므로, 스프링의 Context에 접근하기 어려움 | 스프링에서 관리되기 때문에 스프링 컨텍스트 내의 모든 객체(빈)에 접근이 가능 |
용도 | 보안 관련 공통 작업, 모든 요청에 대한 로깅 또는 감사, 이미지/데이터 압축 및 문자열 인코딩 등 | 인증/인가 등과 같은 공통 작업, Controller로 넘겨주는 정보의 가공, API 호출에 대한 로깅 또는 감사 등 |
이번 프로젝트에서는 앞서 말했다시피 새롭게 배운 기능을 사용해 보고 싶기도 했고
예외처리를 해줄 수 있다는 것이 큰 이점 같아 handler Interceptor로 인증인가를 처리해보기로 했다.
Handler Interceptor를 적용시키기 위해서는 두 가지 절차가 필요하다.
WebConfig
에서 InterceptorRegistry
에 Handler Interceptor 등록하기Handler Interceptor는 인터페이스이며 언제 호출되냐에 따라서 세 가지 메서드를 사용할 수 있다.
preHandle()
: Controller 처리 이전에 수행.postHandle()
: Controller 처리 이후 수행.afterCompletion()
: request 처리가 완료되고 뷰 렌더링 이후 수행.
나는 이번 프로젝트에서 로그인 여부 확인해줄 Handler Interceptor와 접근 권한을 확인할 Handler Interceptor 들을 만들기로 했기 때문에
예를 들어 가게를 생성하는 기능은 우선 로그인이 되어있어야할 것이고, 사장님(OWNER)의 권한을 가지고 있어야할 것이다. 그럼 LoginInterceptor
와 OwnerRoleInterceptor
의 두 가지 handler interceptor를 거쳐 storeController
에 가면 될 것이다.
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ResponseStatusException {
HttpSession session = request.getSession(false);
if (session == null) {
throw new WrongAccessException(ExceptionType.NOT_LOGIN);
}
if (session.getAttribute(Const.LOGIN_USER) == null) {
throw new WrongAccessException(ExceptionType.NOT_LOGIN);
}
return true;
}
}
HandlerInterceptor
를 implents
한 뒤 preHandle
메서드를 @Override
해서 실행할 동작을 작성한다.
이 때, true
를 반환하면 체인에 의해 다음 순서의 Interceptor를 실행시킨다.
*참고로 이 코드 예시는 소셜로그인을 적용시키기 전 작성한 Interceptor이다.
@Component
public class OwnerRoleInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ResponseStatusException {
HttpSession session = request.getSession(false);
if (session == null) {
throw new WrongAccessException(ExceptionType.CUT_OF_SESSION);
}
User loginUser = (User) session.getAttribute(Const.LOGIN_USER);
if (loginUser.getRole() != Role.OWNER) {
throw new WrongAccessException(ExceptionType.NEED_AUTHORITY_OWNER);
}
return true;
}
}
마찬가지로 preHandle
을 @Override
하여 실행 동작을 적어준 뒤 true를 반환시켜 다음으로 넘긴다.
그렇다면 이제 WebConfig
에서 Interceptor를 등록해야한다.
방법은 Filter 때와 유사했는데 FilterRegistrationBean
에 저장하는 것이 아니라 InterceptorRegistry
에 저장한다.
WebMvcConfigurer
가 가진 addInterceptors
라는 메서드를 @Override 하여 만든 Handler Interceptor와 그를 적용시킬 URI 패턴들을 등록해준다.
.addInterceptors(인터셉터명)
: 레지스트리에 인터셉터 등록
.addPathPatterns(uri 패턴)
: 인터셉터를 등록할 uri 패턴 설정
.excludePathPatterns(uri 패턴)
: 인터셉터를 건너뛸 uri 패턴 설정
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private static final String[] LOGIN_REQUIRED_PATH_PATTERNS = {"/api/**"};
private static final String[] LOGIN_EXCLUDE_PATH_PATTERNS = {"/api/users/signup", "/api/users/login"};
private static final String[] OWNER_ROLE_REQUIRED_PATH_PATTERNS = {"/api/owners/**"};
private final LoginInterceptor loginInterceptor;
private final OwnerRoleInterceptor ownerRoleInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns(LOGIN_REQUIRED_PATH_PATTERNS)
.excludePathPatterns(LOGIN_EXCLUDE_PATH_PATTERNS)
.order(Ordered.HIGHEST_PRECEDENCE);
registry.addInterceptor(ownerRoleInterceptor)
.addPathPatterns(OWNER_ROLE_REQUIRED_PATH_PATTERNS)
.order(Ordered.HIGHEST_PRECEDENCE + 2);
}