Json Web Token을 활용해 사용자를 인증하는 방식은 서버에 사용자의 상태를 저장하지 않기 때문에 stateless 합니다.
또한 Jwt에는 사용자의 간단한 정보를 담을 수 있습니다.
이 정보를 활용하기 위해서는 요청헤더에서 토큰을 얻고 토큰에서 사용자의 정보를 다음과 같이 추출할 수 있습니다.
@Operation(summary = "회원이 작성한 게시물 목록")
@GetMapping("wrote/boards")
public RestResult<?> boardListByMember(@RequestParam(defaultValue = "0") int pageNumber,
@RequestParam(defaultValue = "10") int pageSize,
HttpServletRequest request) {
String token = request.getHeader("Authorization");
Long memberIdFromToken = jwtTokenizer.getMemberIdFromToken(token);
Pageable pageable = PageRequest.of(pageNumber, pageSize);
Page<BoardByMemberResDto> boards = boardService.boardsByMember(MemberContextHolder.getMemberId(), pageable);
return RestResult.success(boards);
}
하지만 HttpServletRequest는 Spring MVC의 FrontController 역할을 하는 DispatcherServletcontroller이 아닌
일반적인 컨트롤러 계층에서는 잘 사용하지 않고
매번 헤더에서 토큰을 추출하고 그 토큰에서 회원의 ID를 추출해야하는 반복적인 작업을 컨트롤러의 메서드 내부에 작성해야 한다는 단점이 있습니다.
그래서 Spring에서 제공하는 Interceptor를 활용하여 모든 요청에 대하여 일괄적으로 해당 작업을 실행하여 반복되는 코드를 줄여보았습니다.
Spring Interceptor
인터셉터를 활용하면 웹 어플리케이션 내에서 사용자에 의해 이루어지는 요청을 DispatcherServlet -> Controller로 넘어가는 때에
가로채어 추가적인 작업을 할 수 있도록 해줍니다.
즉, 컨트롤러의 메서드 실행 전, 후로 추가적인 작업을 할 수 있도록 해줍니다.
Spring Interceptor VS Filter
그렇다면 인터셉터와 필터 모두 컨트롤러의 메서드에서 요청을 처리하기전 추가적인 작업을 할 수 있도록 해준다면 무엇이 다른가 하는 궁금증이 생겼습니다.
차이점
1. Filter는 Java에서 제공하는 서블릿 기술이며 Interceptor는 Spring에서 제공하는 기술입니다.
2. Filter와 Interceptor는 각 기술이 개입하는 시기와 그 목적이 다릅니다.
Filter는 웹 어플리케이션 내에서, Interceptor는 스프링 내부에서 동작합니다.
따라서 Filter는 spring context에 접근할 수 없기 때문에 스프링에서 관리하는 객체들을 작업에 사용하지 못합니다.
반면에 Interceptor는 스프링에서 관리하는 객체들을 작업에 사용할 수 있습니다.
Filter
1. 보안 관련 공통 작업
2. 모든 요청에 대한 로깅, 검사
3. 이미지, 데이터 압축 및 문자열 인코딩
Interceptor
1. 인증/인가 등과 같은 공통 작업
2. controller로 넘어가는 정보의 가공
3. API 호출에 대한 로깅, 검사
구현
@Component
@RequiredArgsConstructor
public class JwtInterceptor implements HandlerInterceptor {
private final JwtTokenizer jwtTokenizer;
@Override
//Controller 동작 이전 작업
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token != null && !token.isEmpty()) {
Long memberId = jwtTokenizer.getMemberIdFromToken(token);
MemberContextHolder.setMemberId(memberId);
}
return true; //true 반환시 다음 인터셉터 또는 컨트롤러 실행
}
@Override
//DispatcherSerlvet의 화면 처리(뷰)가 완료된 상태에서 처리
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//Object handler - 현재 실행하려 메소드 자체를 의미
MemberContextHolder.clear();
}
}
회원의 ID는 ThreadLocal에 저장하여 각각의 요청에 대해 독립적으로 변수를 관리할 수 있도록하였습니다.
또한 컨트롤러에서 메서드 실행후에 추가적인 작업을 하고싶다면 postHandle 메서들를 오버라이딩하여 사용할 수 있습니다.