12월 14일 연구일지 (Spring boot에서 커스텀 어노테이션을 활용한 Jwt 검증)

주의

이 문건은 과거 Hexo 블로그 (2017-12-14) 에서 이동된 문서입니다.

시간이 지남에 따라 최신 기술과 다를 수 있으니 주의 바랍니다.


커스텀 어노테이션의 활용

제가 진행하는 프로젝트에서는 안드로이드와 Spring Boot 서버의 서로 통신을 진행하면서 Jwt를 이용하여 사용자 인증을 진행하였습니다.
이번 포스팅에서는 Jwt인증에 대한 부분 보다는,
커스텀 어노테이션을 왜 쓰게 되었는지와 더불어 어떤식으로 커스텀 어노테이션을 활용하였는지에 대해 작성하도록 하겠습니다.


왜 커스텀 어노테이션을 쓰게 되었는가?

서버와 모바일에서 사용자 인증은 jwt를 활용하고 있습니다.
컨트롤러에 진입할 때 Header에 있는 jwt를 받아서 해당 jwt를 검증하는 방식을 사용하고 있습니다.
처음에는 로직을 너무 복잡하게 짰습니다.

초창기에 개발된 일부 코드를 보도록 하겠습니다.

return isProblemJsonResultCode(securityService.veryfiyHeaderInJwt(request))
                .orElseGet(() -> {...})

위의 작은 코드에서만 봐도 jwt를 검증하는 로직 자체도 불분명하고, 람다식을 잘못 쓴 것도 보이네요.
컨트롤러에 진입할 때마다 저런식으로 작업을 하게 된다면 코드가 정말 지저분해질 것으로도 예상되네요. (실제로도 그랬습니다…)

그래서 이 부분을 어떻게 개선을 할까 하다가 인터셉터를 활용하기로 하였습니다.
특정 url에 접근하게 된다면 인터셉터에서 처리하는 방식을 사용하는 것을 생각하였습니다.
그런데 이 부분에서는 아래와 같은 고민거리에 대해 생각하게 되었습니다.

  1. 특정 Rest api 호출 시에는 jwt 검증이 필요하지 않다.
  2. 서버가 Rest api only가 아니라서 몇 가지 다른 url 호출도 같이 쓰게 됨

1번의 경우 검증을 안하는 api가 적은 숫자는 아니라서 excludePathPatterns 을 일일히 적용하기엔 인터셉터가 길어질 것 같았습니다.
2번의 경우 기존에 개발이 된 서버에 Rest api를 붙이는 방식이어서(사실 설계측도 잘못되어 있지만..) url 앞 부분이 겹치는 문제도 있었습니다.

그래서 이 부분을 해결하게 된 것이 커스텀 어노테이션이었습니다.
컨트롤러에서 jwt 검증을 진행해야 하는 부분에는 @CheckJwt 와 같은 어노테이션을 붙여서 구분을 하게 하는 방식을 생각하였습니다.


어떤 식으로 활용을 하였는가?

일단 어노테이션과 커스텀 어노테이션에 대한 이해가 있다는 가정하에 진행하겠습니다.

만약 두 가지에 대해 모르신다면 아래의 포스팅을 참고해주세요.
Java에서 어노테이션(Annotation)이란? > 커스텀 어노테이션 만들고 사용하기

  1. 인터셉터를 하나 만든다.

먼저 인터셉터를 하나 만들어줍니다.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckJwt {
    boolean needCheck() default true; //체크가 필요한 경우의 여부 (기본 값 : 검사)
}

이런식으로 만들어줍니다.


  1. 선언된 인터셉에서 어노테이션 처리를 진행

저같은 경우 컨트롤러 진입하기 전에 jwt 검증을 처리하기 위해서 preHandle에서 처리를 진행하였습니다.

HandlerMethod method = (HandlerMethod)handler;
CheckJwt checkJwt = method.getMethodAnnotation(CheckJwt.class);

if (checkJwt == null || checkJwt.needCheck()==false) { return true; }//검사를 할 필요가 없는 부분
else { //세션 체크 검사
	return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
                    .map(token -> jwtService.veryfiyJwt_new(token))
                    .orElseThrow(JwtNotFoundException::new); //토큰이 존재하지 않는 경우
}

preHandle의 전달인자 중 handler 파라메터는 인터셉터에서 핸들러 매핑이 찾아 준 컨트롤러의 객체를 반환합니다.
그래서 첫번 째 줄에서 method에는 호출된 컨트롤러의 객체가 담겨져 있고, 두번째 줄에서 @CheckJwt 어노테이션을 찾습니다.
만약 @checkJwt가 선언이 안되어 있거나 needCheck가 false일 경우 검사를 진행하지 않고, 그 외에는 검사를 진행하게 됩니다.


  1. 선언 및 사용

이제 @CheckJwt는 아래와 같이 사용하게 될 수 있습니다.

@CheckJwt
@ResponseBody
@GetMapping("list/{hour}/{level}")
public JsonResultVo getMachineWarning(HttpServletRequest request, @PathVariable("hour") String hour, @PathVariable("level") String level) { ... }

정리

자바의 커스텀 어노테이션을 활용한다면 정말 효과적인 코드 리펙토링을 진행할 수 있을 뿐더러, 코드의 이해도가 향상되는 것 같습니다.
다음에는 jwt에 대한 것에 대하여 알아보는 포스팅을 작성해보도록 하겠습니다.

감사합니다.


Written by@MHLab
로또는 흑우집합소 🎲
와인관리, 시음노트, 셀러관리는 마와셀 🥂

🫥 My Service|  📜 Contact|  💻 GitHub