정식명칭이 핸들러 인터셉터(Handler Intercepter)인 스프링 인터셉터란 서버통신 중 요청을 가로채서(Intercept) 어떤 추가적인 로직을 처리하는 것을 말합니다. 주로 인터셉터는 로그인 인증/인가, 사용자 권한 등을 체크하는데 사용합니다.
위 사진처럼 요청이 들어와
세 가지가 있습니다. 이 포스트에서는 컨트롤러에 도달하기 전 api를 낚아채 로직은 처리하는 preHandler를 사용 예시로 들어보겠습니다.
인터셉터를 구현하는 방법은 HandlerInterceptor 인터페이스를 구현하는 방법과 HandlerInterceptorAdapter 클래스를 상속 받는 방법이 있습니다.
어떤 방법이던 메서드를 오버라이드 해서 내가 원하는 타이밍에 원하는 로직을 수행하도록 하면 됩니다.
먼저 위에서 설명한 인터셉터 3가지를 사용하려면 WebMvcConfigurer 인터페이스에서 addInterceptors() 메서드를 오버라이드 해 구현 해 주면 됩니다. WebMvcConfigurer는 스프링 mvc의 갖가지 편리한 메서드들을 모아놓은 인터페이스이고, 우리는 이 인터페이스를 받아 스프링MVC 설정 클래스를 만들겁니다.
앞서 로그인이나 유저의 권한등을 체크할 때 사용한다고 했는데 사용 예시를 들어보겠습니다.
회사에서 진행했던 대학원 평가 시스템의 추가 개발건에서 두 가지를 인터셉터에서 체크하고자 했습니다.
두 가지를 체크하기 위해 각각의 인터셉터를 먼저 생성 해 보겠습니다.
유저의 권한체크
class ProfessorInterceptor : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
SecurityUtil.Professor()
return true
}
}
인터셉터 클래스는 API모듈의 인터셉터 디렉토리에 생성했고, HandlerInterceptor 인터페이스의 preHandle을 오버라이드 했습니다.
내장 클래스의 메서드가 default로 되어있다면, 그 인터페이스 내의 모든 메서드를 오버라이드 할 필요는 없습니다. 필요한 것만 꺼내서 사용하도록 합시다.
preHandle이기 때문에 컨트롤러를 거쳐 비즈니스 로직을 수행하기 전 SecurityUtil(스프링 시큐리티)를 통해 유저의 권한을 체크합니다.
fun checkProfessor() {
val authUser = authUser()
if (authUser.isFreePass()) return
if (currentEvalRole != EvalRole.PROFESSOR.name) {
throw AccessDeniedException("접근할 수 없는 정보입니다.")
}
}
스프링 시큐리티 클래스 중 일부입니다. 먼저 인증된 유저인지 확인하고 유저의 권한을 체크해 인가처리 합니다. enum인 EvalRole이 Professor가 아니면 AccessDeniedException을 리턴하도록 했습니다.
인증된 유저의 평가 가능 여부 체크
class EvaluateValidationInterceptor(
private val evalDeptEvaluatorQueryService: EvalDeptEvaluatorQueryService,
private val evalPeriodQueryService: EvalPeriodQueryService
) : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
val nowEvalPhase = evalPeriodQueryService.getNowEvalType()
if (request.method != "GET")
{
val evalDeptEvaluatorOid = SecurityUtil.currentEvalDeptEvaluatorOid()
val currentEvalType = evalDeptEvaluatorQueryService.getEvalTypeByEvalDeptEvaluatorOid(evalDeptEvaluatorOid!!)
if (nowEvalPhase == EvalType.INTERVIEW && currentEvalType == EvalType.DOCUMENT) throw AccessDeniedException("심사가 마감되어 평가를 진행할 수 없습니다.")
}
return true
}
}
똑같이 preHandle을 오버라이드 해서 사용합니다. 현재의 평가기간(getNowEvalType)을 가져온 다음, API를 호출 한 평가자(Evlauator)의 권한을 한번 더 체크합니다.
현재의 평가기간과 평가자가 서비스를 사용할 수 있는 평가기간이 일치하지 않으면 AccessDeniedException을 리턴 해 주도록 했습니다.
request.method != "GET" 에서 HTTP Method중 GET메서드는 제외하고 인터셉트 하도록 했는데, 평가정보를 수정 할 수는 없지만 이전의 평가기록을 조회할 수는 있도록 구현했습니다.
@Configuration
class WebMvcConfig(
private val evalDeptEvaluatorQueryService: EvalDeptEvaluatorQueryService,
private val evalPeriodQueryService: EvalPeriodQueryService
) : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(EvaluateValidationInterceptor(evalDeptEvaluatorQueryService, evalPeriodQueryService))
.addPathPatterns("/api/professor/**")
registry.addInterceptor(ProfessorInterceptor())
.addPathPatterns("/api/professor/**")
}
}
원하는 인터셉터를 다 만들었다면 스프링 MVC config 클래스에 인터셉터를 추가 해 줘야 사용할 수 있습니다. WebMvcConfigurer 인터페이스에서 addInterceptors() 메서드를 오버라이드 하면 인터셉터를 추가할 수 있습니다.
addInterceptor() 메서드에 인터셉터를 넣어주고, addPathPatterns()에 API Uri 를 적어줍니다. 패턴에 일치하는 API만 인터셉터가 작동되고, 이외의 패턴에는 작동되지 않습니다.
위 예시는 [/api/professor/**] 하위의 모든 api를 검열하고, @PathVariable을 사용하는 API라면 /**를 추가로 적어줘야 합니다.