Java + Spring 에서 NestJS Style Auth Guard 만들기
Spring Security, JWT 만을 사용하기에는 너무 크고 복잡하다. 간단한 Annotation으로 컨트롤러의 인증 인가를 설정하는 코드를 작성해보자.
    PassportJava
- 새로운 프로젝트에서 인증, 인가 과정에 Spring Security 를 사용하는 것이 적합하다는 의사결정을 내리기는 했으나, NestJS 를 통해 백엔드를 구현했던 입장에서는 Spring Security의 사용에 다소 불편함이 있었다. 때문에, Java + Spring 에서 NestJS Style (AuthGuard + Passport.js) 으로 인증 / 인가 프레임워크를 제작해보고 Spring Security 와의 장단점을 비교해보고자 했다.
 - 내가 구현하고자 하는 시스템(?)의 목표는 다음과 같다.
- NestJS 와 같이 @AuthGuard(Guard) 와 같은 형태로 Controller 의 method 에 대해서 가볍게 Auth 과정을 표현할 수 있을 것.
 - AuthGuard 의 경우 Strategy 의 선택을 통해서 Guard 의 작동 방식을 선택할 수 있어 유연성을 가질 수 있을 것. (Strategy pattern 적용)
 - Guard 에서 처리한 정보를 이후 Method 에서 사용할 수 있도록 Parameter를 통해서 주입할 수 있도록 할 것.
 - 사용자 입장에서는 Guard, Strategy의 정의만으로 쉽게 Auth 과정을 정의할 수 있을 것.
 
 - Passport.js 의 구조와 NestJS 의 구조를 따라했다는 점에서 PassportJava 라는 이름을 붙여보았다.
 
구조
- Controller의 method를 대상으로 Request 처리 이전에 intercept하여 실행할 로직을 결정하는 시스템이다. 해당 시스템은 
@UseGuards,@Guard를 통해서Guard를 선택하여 method에 적용할 수 있으며, 로직의 정의는Strategy를 통해 수행한다. 

@Guard
- Controller 에서 사용자가 request 이전에 인증 / 인가 과정에 사용할 
Guard를 선택할 수 있도록 하는annotation이다. - 사용법
value = {...<T extends AuthGuard> T.class}args = {...String}- 주입할 인자가 없을 경우 입력하지 않고 
@Guard(RoleGuard.class)와 같이 사용해도 된다. 
- 주입할 인자가 없을 경우 입력하지 않고 
 
 
@Guard(value = RoleGuard.class, args = {"ADMIN"})  
public ResponseEntity<UserResDto> getById(@PathVariable("id") Long id)  
	throws NotFoundException {  
	return ResponseEntity.ok(userService.getById(id));  
}
@UseGuards
- Controller 에서 사용자가 request 이전에 인증 / 인가 과정에 사용할 
Guard를 여러 개 선택할 수 있도록 하는annotation이다.- Guard를 여러개 사용하는 것과 다르지는 않다.
 
 - 사용법
value = {...Guard.class}args = {...String.class}
 
@UseGuards({  
	@Guard(JwtGuard.class),  
	@Guard(value = RoleGuard.class, args = {"ADMIN"})  
})
public ResponseEntity<UserResDto> getById(@PathVariable("id") Long id)  
	throws NotFoundException {  
	return ResponseEntity.ok(userService.getById(id));  
}
AuthGuard
AuthGuard는UseGuard에서 사용할Guard의 기틀이 되는abstract class이다.- 해당 클래스는 
AuthStrategy를private member로 가지고 있으며, 이를 상속하는 모든Guard객체는super(strategy)를 통해 해당Guard가 사용하는Strategy를 결정한다.AuthStrategy를 통해서 직접 사용할 수 있으나 다음과 같이 구현한 이유는 아래와 같다.Guard의 경우 어떤 형태의Auth과정의Guard를 적용 함을 의미한다.- Guard의 적용은 결국 Controller 의 method 실행 이전 어떤 동작이 실행됨을 적용하는 것이다.
 
- 예를 들어, 유저의 로그인 가능 여부를 묻는 인증 과정이라고 하자.  이를 
LoginedGuard라고 하면, 해당 인증 과정에는JWT를 사용한 전략이 사용될 수도 있고,Session을 통한 전략이 사용될 수도 있다. 혹은 또 다른 전략이 선택될 수도 있다. - 전략이 변경된 경우에도, 해당 
Guard의 역할은 로그인 검증일 뿐이므로, 바뀌지 않는다. 따라서 단순히 해당 유저를 검증하는 전략만 변경 함으로써 코드의 단 한부분만 변경하여 해당 전략의 변경이 가능하다. - 만약 그렇지 않다면, 
@UseGuards({JwtStrategy.class})라고 작성된 모든 method를 변경해야할 것이다. 
 - 해당 클래스를 구현하는 객체는 
@Component를 통해Spring bean으로 등록되어야한다.GuardResolver에서 해당 객체들을 스캔하여 가지고 있어야한다. - 구현
 
public abstract class AuthGuard {  
	private AuthStrategy authStrategy;  
	/**  
	* 상속받은 클래스는 반드시 AuthStrategy를 지정해야합니다.  
	*  
	* @param authStrategy  
	*/  
	public AuthGuard(AuthStrategy authStrategy) {  
		this.authStrategy = authStrategy;  
	}  
	  
	public final void check(HttpServletRequest request, String... args) {  
		try {  
			authStrategy.check(request, args);  
		} catch (CustomException customException) {  
			throw customException;  
		} catch (RuntimeException e) {  
			throw new CustomException(ExceptionStatus.UNAUTHORIZED_USER);  
		}  
	}  
}
- 사용법
 
@Component  
public class RoleGuard extends AuthGuard {  
	  
	public RoleGuard(@Autowired RoleStrategy strategy) {  
		super(strategy);  
	}  
}
AuthStrategy
AuthStrategy는 사용자가 정의할Strategy의 기틀이 되는interface이다.- 보다 유연하게 전략을 선택하여 
Guard를 정의하도록 하였다. 
- 보다 유연하게 전략을 선택하여 
 - 해당 
interface는AuthGuard에 주입될 객체를 정의하며,implements하는class는public void check(HttpServletRequest request, String... args)를 정의해야한다. - 이후 해당 전략을 사용하기 위해서는 
AuthGuard를 상속한 객체의 constructor에서 사용할 Spring bean strategy를 주입할 수 있도록JwtGuard(@Autowired JwtStrategy strategy)와 같이 정의해야한다. - 구현
 
public interface AuthStrategy {  
	/**  
	* Check method는 해당 Auth 작업에 대한 전략을 수행합니다. 만약, 검증 과정에서 실패할 경우 Error를 throw 합니다. 해당 Error는  
	* Exception Handler 에서 처리할 수 있습니다.  
	*  
	* @param request  
	*/  
	public void check(HttpServletRequest request, String... args);  
}
- 사용법
 
@Component  
public class RoleStrategy implements AuthStrategy {  
	@Override  
	public void check(HttpServletRequest request, String[] args) {  
	// role check  
	}  
}
// JwtGuard 의 정의
public class RoleGuard extends AuthGuard {
  public RoleGuard(@Autowired RoleStrategy strategy) { super(strategy); }
}
GuardAspect
@UseGuards를 핸들링하기 위한ApectJ를 사용하는class이다.handleUseGuards()를 통해 해당annotation을 선언한 메소드가 실행되기 이전에Guard의Strategy를 실행한다.GuardAspect의 경우GuardResolver를 통해Guard객체를 찾아낸다.- 구현
 
@Aspect  
@Component  
public class GuardAspect {  
	  
	@Autowired  
	private GuardResolver guardResolver;  
	  
	@Before("@annotation(useGuards)")  
	public void handleUseGuards(UseGuards useGuards) {  
		final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
		.currentRequestAttributes())
		.getRequest();  
		final List<Guard> guards = Arrays.stream(useGuards.value()).toList();  
		for (Guard guard : guards) {  
			AuthGuard authGuard = guardResolver.getGuard(guard.value().getSimpleName());  
			authGuard.check(request, guard.args());  
		}  
	}  
	  
	@Before("@annotation(guard)")  
	public void handleGuard(Guard guard) {  
		final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder  
		.currentRequestAttributes()).getRequest();  
		final AuthGuard authGuard =  
		guardResolver.getGuard(guard.value().getSimpleName());  
		authGuard.check(request, guard.args());  
	}  
}
GuardResolver
Spring Bean으로 등록된 객체 중AuthGuard타입의 객체를 찾아 저장하고, 이를 이후에GuardAspect에 전달하는 역할을 수행한다.- 구현
 
  
@Component  
public class GuardResolver {  
	  
	private final Map<String, AuthGuard> guards;  
	  
	public GuardResolver(List<AuthGuard> guards) {  
		this.guards = guards.stream()  
			.collect(  
				Collectors.toMap(  
				g -> g.getClass().getSimpleName(),  
				Function.identity()  
			));  
	}  
	  
	public AuthGuard getGuard(@NonNull String guardName) {  
		return this.guards.get(guardName);  
	}  
}
UserData
Guard를 통해서Session혹은JWT정보를 가져올 수 있다. 해당 경우를 위한 지원으로HttpServletRequest객체를 통해서 유저의 데이터를parameter를 통해 주입하는 역할을 수행하는annotation이 필요하다.
@UserData
Guard에서 주입된 정보를 사용하며, 이 정보는HttpServletRequest의USER_DATAattribute 를 기본으로 한다. 만약 다른 형태의 attribute 를 사용하고자 하는 경우,@UserData의value값을 지정해야한다.- 해당 값은 외부 
Configure을 통한 주입을 추천하며,HttpServletRequest의attribute와 겹치지 않도록해야한다.SNAKE_CASE(UPPER CASE)를 추천한다. 
- 해당 값은 외부 
 
예시
- Guard
 
@Component  
public class JwtStrategy implements AuthStrategy {  
	@Value("jwt.data.name")  
	private String dataName = "JWT_DATA";  
	  
	@Override  
	public void check(HttpServletRequest request, String... args) {
		String token = getTokenFromRequest(request);
		request.setAttribute(dataName, token);  
	}  
	  
	private String getTokenFromRequest(@NonNull HttpServletRequest request) {  
		// Get Bearer token from authorization  
		return request.getHeader(HttpHeaders.AUTHORIZATION);  
	}  
}
- UserData 적용
 
// controller code
@Value("jwt.data.name")
private String jwtDataName;
//...
@GetMapping("/{id}")
public boolean someMethod(
	@PathVariable("id") Long id,  
	@UserData(jwtDataName) String token, // token is injected
	@UserData JwtData data // value 없이도 가능. default "USER_DATA" 사용.
	) throws NotFoundException {
	return ResponseEntity.ok(userService.getById(id, token));  
}
UserDataAspect
@UserData를 핸들링하기 위한ApectJ를 사용하는class이다.handleUserData()를 통해 해당annotation을 선언한parameter값에 해당Object를 주입한다.- 해당 annotation을 사용할 때 반드시 Strategy에서 사용하는 타입과 일치해야한다.
 - 구현
 
@Aspect
@Component
public class UserDataAspect {
  /**
   * TokenData annotation 을 적용한 Parameter 에 대해서 Request의 TokenData Attribute 를 가져옵니다. Pointcut :
   * method(params.., @TokenData(Type data), params..)
   * <p>
   * TokenData 의 경우 v
   *
   * @param joinPoint
   * @return
   * @throws Throwable
   */
  @Around("execution(* *(.., @com.ticketwar.ticketwar.auth.pass.UserData (*), ..))")
  public Object handleUserData(ProceedingJoinPoint joinPoint)
      throws Throwable {
    final Signature signature = joinPoint.getSignature();
    final MethodSignature methodSignature = (MethodSignature) signature;
    final Method method = methodSignature.getMethod();
    final Parameter[] parameters = method.getParameters();
    final Object args[] = joinPoint.getArgs();
    final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
        .currentRequestAttributes()).getRequest();
    for (int idx = 0; idx < parameters.length; ++idx) {
      UserData userData = parameters[idx].getAnnotation(UserData.class);
      if (userData != null) {
        args[idx] = request.getAttribute(userData.value());
        break;
      }
    }
    return joinPoint.proceed(args);
  }
}JWT 적용하기
JWT 의 경우 auth0:java-jwt 라이브러리를 활용하였다.
(단순한 PassportJava 적용 예시)
JwtConfigure
- Spring Bean 으로, 외부 변수를 주입하기 위함이다.
jwt.data.name@UserData에 저장할request의attribute이름을 지정함.
jwt.secret- JWT secret 지정.
 
 
@Component
public class JwtConfigure {
  @Value("${jwt.data.name:USER_DATA}")
  private String dataName;
  @Value("${jwt.secret}")
  private String secret;
  public String getDataName() {
    return dataName;
  }
  public String getSecret() {
    return secret;
  }
}
JwtUtil
- SpringBean (Configure 사용 및 타 Bean 에서 autowire 사용을 위함.) 으로, JWT 토큰 획득 과정과 관련된 method를 담는다.
 verifyTokenFromRequestAuthorizationHeaderHttpServletRequest로 부터Bearer Token을 획득하고, 이를JwtData형태로 변환한다.
- 이후 다른 형태로 JWT 가 필요할 경우, 해당 형태로 가져와서 사용할 수 있도록 method를 추가할 것이다.
 
@Component
public class JwtUtil {
  private final String BEARER = "BEARER ";
  @Autowired
  private JwtConfigure jwtConfigure;
  /**
   * Verify Authorization header's bearer token.
   * <p>
   * if fails, it throws RuntimeException.
   *
   * @param request
   * @return
   */
  public JwtData verifyTokenFromRequestAuthorizationHeader(HttpServletRequest request) {
    final String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION).substring(BEARER.length());
    final Algorithm algorithm = Algorithm.HMAC256(jwtConfigure.getSecret());
    final JWTVerifier jwtVerifier = JWT.require(algorithm).build();
    final DecodedJWT decodedJWT = jwtVerifier.verify(bearerToken);
    return getJwtDataFromDecodedJwt(decodedJWT);
  }
  private JwtData getJwtDataFromDecodedJwt(DecodedJWT decodedJWT) {
    final Claim id = decodedJWT.getClaim("id");
    final Claim email = decodedJWT.getClaim("email");
    final Claim nickname = decodedJWT.getClaim("nickname");
    final Claim role = decodedJWT.getClaim("role");
    return JwtData.builder()
        .id(id.asLong())
        .email(email.asString())
        .nickname(nickname.asString())
        .role(role.asString())
        .build();
  }
}
JwtStrategy
@Component
public class JwtStrategy implements AuthStrategy {
  @Autowired
  private JwtConfigure jwtConfigure;
  @Autowired
  private JwtUtil jwtUtil;
  @Override
  public void check(HttpServletRequest request, String... args) {
    final JwtData jwtData = jwtUtil.verifyTokenFromRequestAuthorizationHeader(request);
    request.setAttribute(jwtConfigure.getDataName(), jwtData);
  }
}
JwtGuard
@Component
public class JwtGuard extends AuthGuard {
  public JwtGuard(@Autowired JwtStrategy strategy) {
    super(strategy);
  }
}
개발 과정 중 문제
Spring bean 획득과 new ()를 통한 Object
- AuthGuard 의 설계 초기에는 
super(new JwtStrategy())와 같은 형태를 통해서Strategy를 주입하였다. Spring 과 상관 없이Strategy를 제작을 하고자 함이었다. 
@Component  
public class JwtGuard extends AuthGuard {  
	  
	public JwtGuard() {  
		super(new JwtStrategy());  
	}  
}
- 그러나 
JwtStrategy를 만들면서, 실패했다.JwtStrategy의 경우 필연적으로secret을 외부로부터 주입받아야하는데, 보통 그 수단으로 Spring의@Value를 통해서 값을 주입받는다. 그러나, 이는Spring bean에 의해서만 가능하다. - 따라서 어쩔 수 없이, 
@Component를 선언하고, Spring bean 에 등록을 하였으나,jwt.secret의 값을 제대로 가져오지 못했다. 위 코드를 보면 알 수 있듯,JwtStrategy를new를 통해서 새로 인스턴스를 만들고 주입하기에,Bean과 관련없는 객체가 주입이되었다. AuthGuard의constructor에서 직접 bean 을 찾아 넣는 방식도 시도하였으나, 적절하지도 않으며, 해당 시점에는ApplicationContext가 완성되지 않아 활용할 수도 없다. (되도록이면 접근하지 않는 것이 맞다고 생각한다.) 이렇게 할 경우, Spring 자체에도 의존성이 생겨버린 상태기 때문에 의미가 없었다.- 애초에 해당 시스템 자체가 Spring에 의존을 하고 있기 때문에, Strategy를 Component로 등록하지 않을 이유는 없었다. 따라서, 항상 
AuthStrategy구현 객체는 Spring Bean 에 등록하고,@Autowired를 통해서 이를 등록하도록 했다. - 결과적으로는 NestJS + Passport 의 방식에 비하면 조금 복잡하기는 하나, 
AuthGuard의 유연성을 지키고, 몇 줄 안되는 코드로AuthStrategy를 결정할 수 있는 방식이 되었다. 나름 깔끔하게 사용자가 선언할 수 있어 마음에 든다. 
@Component  
public class JwtGuard extends AuthGuard {  
	  
	public JwtGuard(@Autowired JwtStrategy strategy) {  
		super(strategy);  
	}  
}
@Guard, @UseGuards에서 arguments
@UseGuards({JwtGuard.class, RoleGuard.class})
- 초기에는 
@Guard없이@UseGuards만으로 구성했다. 또한, 인자를 받지 않았다. JwtGuard의 경우, JWT verification 만 하고 끝내면 되니, 특별한 인자가 필요없지만,RoleGuard의 경우 해당 method 의 접근 권한을 설정해야하기 때문에 문제가 발생한다.- 또한 다른 custom guard 를 만들 경우에서도 인자를 받을 수 없어 유연성이 부족하다. 권한마다 매번 Guard를 만들 수는 없다.
 
@Target({ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface UseGuards {  
  
	Class[] value();  
	String[] args();
}
@UseGuards(value = {JwtGuard.class, RoleGuard.class}, args = {null, "ADMIN"})
- 그렇다고, 위와 같이 args를 순서대로 매핑하는 느낌으로 사용하는 것도 바람직하지 않다. 또한 여러 인자를 한 Guard에 전달할 수도 없다.
 - 따라서, 각 
AuthGuard는 해당 클래스와 인자를 전달하는@Guard를 통해서 사용하도록 했다. 
@Target({ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface Guard {  
	Class value();  
	  
	String[] args() default {};  
}
- 이를 통해서 
Guard는 각각의 인자를 받아올 수 있게 되었다. - 또한, 여러개의 
Guard를 순차적으로 실행하는 것도 필요하기에@UseGuards는 다음과 같이 고쳤다. 
/**  
* UseGuards  
*/  
@Target({ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface UseGuards {  
  
	Guard[] value();  
}
@GetMapping("/{id}")  
@UseGuards({  
	@Guard(JwtGuard.class),  
	@Guard(value = RoleGuard.class, args = {"ADMIN"})  
})  
public ResponseEntity<UserResDto> getById(){...}
- 다소 복잡하기는 하나, 필요한 요소들을 모두 표현할 수 있다. 다만, String을 통해서만 인자를 받아올 수 있기 때문에 아쉬운 면이 있다.
UserRole.ADMIN과 같이 설정한다면 더욱 편리할 것이다. 하지만, annotation에서 Enum의 경우 특정해야만 사용 가능하여, 다양한 형태를 받아야하는 현재로서는 어쩔 수 없었다.
 
Spring Security 와의 비교
직접 구현 이전에는, Spring security 의 예시만 알고 있기 때문에 직접 개발한 형태의 방식은 어떤 장점과 단점을 가질 지에 대해서 명확하게 정의를 내리기 어려웠다. 직접 구현 이후 Spring security 의 적용에 비해서 가질 수 있는 장단점을 간략하게 생각해보았다.
장점
- 상대적으로 쉽다.
- 이는 자체적으로 구조가 단순하기 때문이다. Spring security 의 경우 다양한 보안 관련 기능을 지원해야하고, 이로 인해서 어쩔 수 없이 복잡한 구조를 가져야한다. (실제로 사용자가 사용하지 않는 기능들 까지 담아야하므로...) 반면, 해당 PassportJava 의 경우 Controller method의 실행 이전 intercept 하여 실행할 내용을 정의하는 것을 지원하는 것 뿐이다. 구조적으로 단순하니, 쉬울 수 밖에 없다.
 - 또한, 개발자가 직접 해당 기능에 대한 작동을 정의하기 때문에 상대적으로 그 코드를 작성한 개발자에게 있어서는 이해가 쉬울 것이다. 그러나, 팀 단위로 개발하는 환경을 고려하면 정형화되어 있는 형태로 작성해야하는 생태계 (Spring security)가 오히려 코드와 구조를 이해하는 측면에서는 나을 것 같다.
 
 - 직접 제어 가능하다.
- Spring security 와 같은 형태의 이미 주어진 프레임워크에서는 어쩔 수 없이, 제한된 환경에서 해당 구조에 알맞게 코드를 작성해야한다. 물론 어느정도 유연하게 작성할 수는 있겠으나, 프레임워크가 제공하는 시점 아래에서 코드가 작성되어야하며 디테일한 조정은 불가하다.
 - 반면, 이렇게 직접 작성하는 경우 개발자가 원하는 방식으로 모두 제어할 수 있다. "불만이 있으면 직접 뛰던가" 의 예시...? 결국에는 Spring Security 가 등장하기 이전에는 어쩔 수 없이 이렇게 코드를 작성해야한 했을 것이다.
 
 
단점
- 다양한 기능 지원의 부재
- "쉽다/단순하다" 에서 비롯되는 어쩔 수 없는 단점이다. 필요한 기능이 있을 경우 개발자가 직접 지원하도록 만들어야한다. 시간적인 측면에서 문제가 있다.
 
 - 안정성
- 거대하고 안정된 커뮤니티에서 만들어진 프레임워크를 사용하는 것에 비하면 안정성에서 확실하지 않다. 이는 이러한 형태의 시스템을 만드는 사람에 따라 다르겠으나, 아무래도 Spring Security 에 비하면 부족함은 사실이다.
 
 - (내 구조의 경우) 여전히 Spring에 의존한다.
- Spring bean 이 되어 해당하는 
AuthGuard를 찾아내고, 적합한Strategy를 주입한다. 이로 인해 Spring 으로부터 독립된 패키지가 아니다. 다른 Java 의 Framework 에 적용할 수 없다. 
 - Spring bean 이 되어 해당하는 
 - 결국에는 Spring Security 형태를 따라갈 수 밖에 없다.
- 이번에 PassportJava를 만들어내면서, Spring security 의 등장에 대해서 생각해보았다.
 
 - Spring Security 이전에는 Spring scope 내에서 처리하기 위해서는 PassportJava 와 같은 형태로 제작을 해야만 했을 것이다.
 - 혹은, Spring Security 와 마찬가지로 Servlet 에 직접 접근하여 Filter 를 사용하는 형식을 취할 것이다. 이런 형태의 최종 추상화가 바로 Spring Security 형태이다.
 - 개발자가 원하는 형태의 사용자 인증 Process 는 결국 비슷하다. 다양한 형태의 인증 프로세스를 범용적으로 지원하기 위한 고민을 Spring security 에서 나름의 정답을 내놓은 셈이다. 더 나은 프로세스가 존재할 수도 있지만, 결국에는 유사한 형태의 결과가 만들어지곤한다.
 - 특별히 더 나은 형태의 구조의 발상이 있지 않다면, 누구나 접근할 수 있으며, 사용법과 구조에 대해 이해가 있는 Spring Security 를 적용하는 것이 생산성 측면에서 더 유리하리라 생각한다.
 
테스트에서의 문제
- 해당 라이브러리는 AspectJ 를 활용해서 Method가 실행되기 이전 intercept 하여 실행한다.
 - 이는 Controller 영역의 테스트에서 문제가 발생한다. Test 를 위한 지원이 포함되어있는 Spring Security 와 달리, 해당 라이브러리는 Test 를 할 때 Intercept 하고, argument 를 주입하는 영역에 대해 개별적으로 mocking을 하거나 하는 방식으로 처리를 해야한다.
 
@Test
void something() {
  // 이런 방식으로 Strategy 의 동작과 Resolver 의 동작을 구성해야함...
  BDDMockito.doNothing().when(jwtStrategy).check(any(HttpServletRequest.class));
      BDDMockito.given(userIdArgumentResolver.supportsParameter(any())).willReturn(true);
      BDDMockito.given(userIdArgumentResolver.resolveArgument(any(), any(), any(), any()))
}