<aside> 🔑

요약

JWT 재발급 후에도 클라이언트에서 토큰이 만료된 것으로 인식되는 문제가 발생했었다. 디버깅 결과 exp 값이 서버 설정 기준(KST)보다 하루 정도 빠르게 설정되는 것을 확인하였다.

이는 JVM의 기본 타임존이 UTC로 되어 있고, JWT의 exp 클레임이 UTC 기준으로 저장되기 때문이었다. 즉, 서버에서는 KST 기준으로 토큰 만료 시간을 계산했지만, 내부적으로는 UTC로 처리되어 오차가 발생한 것이었다.

이를 해결하기 위해 **@PostConstruct**를 이용해 JVM 기본 타임존을 Asia/Seoul로 고정하였다. 추가로, 필요 시 clockSkewSeconds를 추가해 서버 간 시계 오차도 허용할 수 있도록 설정하였다.

이번 경험을 통해 시간대 설정은 인증 시스템 전반에 영향을 미치는 중요한 요소임을 깨달을 수 있었다.

</aside>

문제배경

예전에 구축해두었던 JWT 기반 인증(acccess + refresh) 시스템에서 access token을 재발급했음에도 불구하고 클라이언트에서 만료된 토큰으로 인식되는 문제가 발생하였다.

토큰의 exp(만료 시간)를 확인해보면 서버에서 분명히 현재 시간 기준으로 1시간 뒤로 설정했음에도, 실제 JWT 내부에 저장된 만료 시간이 예상보다 약 하루 전으로 찍히는 현상이 발생하였다.

이로 인해 isExpired() 메서드는 false를 반환했지만, JWT 파싱 도중 내부적으로는 ExpiredJwtException 예외가 발생했다.

개선

문제 원인 분석

여러 차례 디버깅 로그를 따라가며 분석해보니, 해당 문제의 원인을 타임존 불일치로 인한 것으로 보았다.

즉, 현재 민초 프로젝트의 백엔드의 시스템 자체 타임존은 KST(아시아/서울) 로 지정하여 동일하지만, 현재 사용하고 있는 jjwt 라이브러리 내부에서 동작하는 타임존과는 불일치하여 서울 기준(20일)이 아니라 미국 기준(19 혹은 18일)로 재발급 토큰의 유효기간이 설정되는 것이다.

결국, 해당 현상의 주요 원인은 JVM이 사용하는 기본 타임존과 JWT 스펙 간의 시간 기준 차이에서 비롯되었다.

구체적으로 다음 두 가지가 핵심이었다.

  1. JVM 기본 타임존 미설정

    Spring Boot에서 System.currentTimeMillis()new Date()를 사용할 경우 JVM의 기본 타임존 설정에 의존한다. 기본적으로 JVM은 UTC를 사용하기 때문에, KST 기준으로 토큰 만료 시간을 설정하더라도 JWT 내부에는 UTC 기준 시간이 저장되어 오차가 발생할 수 있다.

  2. JWT 스펙은 UTC 기준

    JWT의 exp 클레임은 UTC 기준의 UNIX timestamp로 저장된다. 따라서 JVM 타임존과의 불일치가 존재하면, KST 기준으로 설정된 시간이 UTC 기준으로 잘못 해석되어 하루 정도 오차가 생기는 문제가 발생할 수 있다.

그 예시로 아래 로그들을 살펴보면, JWT 생성 시에 설정한 시간이 생성된 이후 측정한 로그에서 보여지는 시간과 다른 것을 확인할 수 있었다.

[참고] 현재 설정된 타임존 로그

image.png