1 분 소요

배경

이전에는 Spring Security를 사용하여 인증/인가 처리를 해왔는데 이번 프로젝트(talk-talk)에서는 Spring에 대한 이해도 높일겸 interceptor에서 인증/인가를 구현해봤다.

인터셉터

img1

출처: https://mangkyu.tistory.com/173 [MangKyu’s Diary:티스토리]

인터셉터는 J2EE 표준 스펙인 필터와 달리 Spring에서 제공하는 기술로써, DispatcherServlet이 컨트롤러를 호출하기 전 ,후에 요청과 응답을 참조하거나 조작할 수 있다. DispatcherServlet에서는 핸들러 매핑을 통해서 적절한 컨트롤러를 찾는데 이 응답값으로 HandlerExcurtionChain을 반환한다. 만약 1개 이상의 인터셉터가 등록되어 있다면 순차적으로 인터셉터를 실행하고 컨트롤러를 호출하게 된다.

DispatcherServlet이 컨트롤러 호출 전후 요청, 응답을 조작할 수 있기 떄문에 인터셉터에서 인증/인가 처리를 하면 깔끔하다. 물론 Spring Security를 사용하여 Filter에서 인증/인가를 구현할 수도 있고 Aop를 사용하여 처리할 수도 있다.

이제 인터셉터를 등록해보자.

인터셉터 등록

img1img1

인터셉터를 등록하기 위해서는 WebMvcConfigurer를 구현하여 addInterceptors()를 오버라이딩하고 우리가 만든 인터셉터를 추가해주면 된다.

위의 코드에서는 ArgumentResolver도 추가돼있는 것을 볼 수 있는데 이는 컨트롤러에서 인증된 사용자 정보를 받아서 쓰기 위해서이다. Spring Security의 @AuthenticationPrinciple과 같은 맥락이라고 생각하면 된다.

참고) Spring Security에서는 SecurityContextHolder에 저장한 인증 객체를 컨트롤러에서 @AuthenticationPrinciple 어노테이션을 통해서 가져올 수 있다.

이제 등록을 완료했으니 인터셉터를 구현해보자.

AuthenticationInterceptor

img1img1

우선 커스텀 인터셉터는 HandlerInterceptor를 구현해야 한다. HandlerInterceptor에는 총 3개의 메서드가 있는데 우리는 컨트롤러 이전에 호출되는 preHandle()만 구현하여 인증/인가를 처리할 것이다.

먼저 isRequiredAuth() 메서드는 로그인이 필요한 API인지 확인하는 메서드이다. 로그인, 회원가입등 로그인이 요구되지 않는 API가 있을 것이다. 이런 API는 인증을 수행할 필요가 없다.

그리고 요청으로부터 payload를 꺼내서 실제 유저가 존재하는 지 검증하고 setAttribute() 를 통해서 방금 꺼낸 payload 값인 memberId를 request에 넣어준다. 이렇게 해주는 이유는 컨트롤러에 인증 객체를 파라미터로 넘겨주기 위해서다.

각각의 메서드를 살펴보자.


isRequiredAuth

img1

컨트롤러 메서드에 달린 어노테이션중에 @PreAuthorize가 붙은 API에 대해서만 true를 반환한다. 따라서 위에서 로그인이 필요 없으면 그냥 넘어가게 된다.


extractMemberIdFromRequest

img1

단순히 request 로부터 토큰을 파싱하여 payload를 꺼내는 부분이다.


AuthResolver

img1

AuthResolver에서는 인증된 유저가 요구되는 컨트롤러의 파라미터를 세팅해주기 위해서 필요하다. supportsParameter에서 AuthMember 파라미터를 가진 컨트롤러들에 대해서 해당 리졸버가 동작하게 된다.

resolveArgument에서는 인터셉터에서 세팅한 유저의 정보(id)를 바탕으로 유저를 찾고 인증 객체를 생성하여 반환한다.

이렇게 되면 컨트롤러에서 인증 객체(AuthMember)를 파라미터로 꺼내서 사용할 수 있게 된다.


AuthMember를 사용하는 컨트롤러

img1

실제 컨트롤러 부분이다. 필자가 커스텀한 어노테이션(@PreAuthorize)가 붙어 있는 것을 확인할 수 있다. @PreAuthorize 어노테이션이 붙어 있으면 위에 설명한 AuthInterceptor에서 isRequiredAuth()가 true를 반환하게 된다.

그리고 파라미터 부분에 AuthMember가 있는 것을 볼 수 있다. 이는 마찬가지로 바로 위에 설명한 AuthResolver에서 채워지게 된다.

이로써 Interceptor를 활용한 인증/인가 처리 부분에 대한 설명을 마치겠다.

전체 코드는 talk-talk 에서 확인할 수 있다.

참고 자료