0%

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

커스텀 어노테이션의 활용

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


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

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

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

1
2
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. 인터셉터를 하나 만든다.

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

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

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

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

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

1
2
3
4
5
6
7
8
9
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는 아래와 같이 사용하게 될 수 있습니다.

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

정리

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

감사합니다.