rotate jwt 전략을 선택하고 토큰 발행 후 used 처리된 토큰으로 refresh 시도 시, 계정을 블록하는 테스트를 시도했다.
@Test
@DisplayName("토큰 refresh 실패 - 사용된 토큰")
@Transactional
fun refreshToken_fail_used_token() {
// given
val userSignUpCommand = UserSignUpCommand("tester", "1234", "testerName")
val user = userService.signUp(userSignUpCommand)
val firstIssuedRefreshToken = authService.create(LoginCommand("tester", "1234")).refresh
//when & then
authService.refresh(firstIssuedRefreshToken)
assertThatThrownBy { authService.refresh(firstIssuedRefreshToken) }
.isInstanceOf(BadCredentialsException::class.java)
.hasMessage("재사용된 Refresh Token이 감지되어 계정이 잠겼습니다.")
assertThat(user.statusCode).isEqualTo(BLOCKED)
}
그런데, validate 함수에서 두 개의 토큰 row가 발견되어 예상치 못한 예외가 발생했다. 동시성 이슈가 발생한 걸까? 동시성 이슈라고 판단하고 반나절 이상을 헤맸지만, 이유는 다른 곳에 있었다. 바로 JwtProvider 코드였다.
public String createRefreshToken(String userId) {
// Refresh Token 유효기간 (14일)
return Jwts.builder()
.claim("sub", userId)
.issuedAt(new Date())
.expiration(Date.from(Instant.now().plus(14, ChronoUnit.DAYS))) // 14일 뒤
.signWith(secretKey)
.compact();
}
기존 발급 코드는 이렇게 되어 있어서, 위와 같은 테스트 환경의 경우 완전히 같은 값, (Date 마저도) 으로 처리되어 같은 token이 발행될 뿐이었던 것이다. 따라서 id를 UUID로 추가해주어서 해결했다.
public String createRefreshToken(String userId) {
// Refresh Token 유효기간 (14일)
return Jwts.builder()
.claim("sub", userId)
.id(UUID.randomUUID().toString())
.issuedAt(new Date())
.expiration(Date.from(Instant.now().plus(14, ChronoUnit.DAYS))) // 14일 뒤
.signWith(secretKey)
.compact();
}