jwt 토큰의 id를 UUID로 주기

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();
    }

댓글

개발자  김철준

백엔드 개발자 김철준의 블로그입니다.

주요 프로젝트