JWT 만료 및 발급 시각 클레임: 유닉스 타임스탬프가 토큰 인증을 작동시키는 방법

JWT 페이로드 다이어그램에서 Unix 타임스탬프로 표시된 만료 클레임 exp와 iat

사용자가 SaaS 애플리케이션에 로그인할 때마다 인증 토큰 이 생성되어 브라우저나 모바일 앱에 전달돼요. 이 토큰 안에는 보이지 않는 시계가 내장되어 있어요. JWT 만료 필드는 일반 Unix 타임스탬프 형태로 저장되며, 토큰을 읽는 모든 서버에 정확히 언제부터 해당 자격 증명을 신뢰하지 말아야 하는지 알려줘요. 이 타임스탬프가 단 몇 초라도 잘못 설정되면 사용자가 너무 일찍 로그아웃되거나, 반대로 보안 정책이 허용하는 것보다 훨씬 오래 유효한 세션을 유지하게 돼요. JSON web token 의 클레임, 특히 exp iat 가 어떻게 동작하는지 이해하는 것은 SaaS 팀이 사용자 경험을 해치지 않으면서 인증 보안을 강화할 수 있는 가장 실용적인 방법 중 하나예요.

핵심 요약:

  • JWT JSON web token exp iat 클레임은 항상 밀리초가 아닌 초 단위의 Unix 타임스탬프예요.
  • 서버 간 클록 스큐(clock skew)는 토큰을 조용히 무효화하거나 세션을 정책 이상으로 연장할 수 있어요. 업계 표준 해결책은 60초 허용 범위를 설정하는 거예요.
  • 토큰 만료 시간을 너무 길게 설정하면 보안 위험이 커지고, 너무 짧게 설정하면 사용자 경험이 나빠져요. 적절한 값을 선택하는 구체적인 공식이 있어요.
  • 라이브러리 없이도 Unix epoch 변환 도구를 사용해 JSON web token 의 타임스탬프를 초 단위로 디코딩하고 확인할 수 있어요.

JWT란 무엇이고 타임스탬프는 왜 토큰 안에 있을까요

JSON web token 은 점(.)으로 구분된 세 개의 Base64 인코딩 파트로 구성된 간결하고 URL-safe한 자격 증명이에요. 세 파트는 헤더, 페이로드, 서명이에요. 시간에 민감한 데이터는 페이로드에 저장돼요. JWT는 스테이트리스(stateless) 하기 때문에 서버가 토큰을 검증할 때 데이터베이스에서 세션을 조회하지 않아요. 대신 페이로드에 내장된 클레임을 읽고 암호화 서명을 신뢰하는 방식으로 동작해요. 이 설계는 빠르고 확장성이 뛰어나지만 한 가지 중요한 제약이 있어요. 토큰이 한번 발급되면 서버가 그것을 "삭제"할 수 없다는 거예요. 토큰의 유효 기간을 제한하는 유일하고 신뢰할 수 있는 방법은 토큰 자체에 만료 타임스탬프를 직접 포함시키는 것이에요.

바로 이 이유로 Unix epoch 시간 이 JWT 보안의 핵심이에요. JWT를 정의하는 RFC 7519 명세 는 시간 관련 클레임을 "NumericDate" 값, 즉 1970년 1월 1일 UTC 이후 경과한 초 수로 표현하도록 규정하고 있어요. 타임존도 없고, 로케일 포맷도 없어요. 전 세계 어떤 서버든 자신의 시계와 비교할 수 있는 단순한 정수값이에요.

JWT structure showing header, payload, and signature with exp and iat claims

exp iat 클레임 자세히 알아보기

JWT 명세는 여러 등록 클레임을 정의하고 있어요. 토큰 라이프사이클 관리에서 가장 중요한 두 가지는 다음과 같아요.

  • exp (만료 시간, Expiration Time): 이 타임스탬프 이후에는 토큰을 수락하면 안 돼요. 현재 시간이 이 값과 같거나 크면 서버는 해당 토큰을 반드시 거부해야 해요. 이것이 JWT 만료 의 핵심 메커니즘이에요.
  • iat (발급 시간, Issued At): 토큰이 생성된 시점의 타임스탬프예요. iat 클레임은 기본적으로 강제 적용되지 않지만 매우 유용해요. 토큰의 나이를 계산하고, exp 와 독립적으로 최대 유효 기간 정책을 적용하며, 과거에 비정상적으로 발급된 토큰을 감지하는 데 활용할 수 있어요.

세 번째 클레임인 nbf (Not Before)는 토큰이 유효해지는 가장 이른 시점을 정의해요. 자주 사용되지는 않지만 동일한 Unix 타임스탬프 형식을 따라요.

세 값 모두 정수예요. JavaScript의 Date.now() 를 사용할 때처럼 시스템이 밀리초 단위로 값을 생성하는 경우(흔한 실수), 결과 숫자는 예상보다 약 1,000배 커져요. 서버가 exp 를 현재 epoch 초와 비교할 때 해당 토큰이 33,658년에 만료되는 것으로 보여 절대 거부하지 않게 돼요. 이는 여러 SaaS 팀에서 실제로 발생한 프로덕션 버그예요.

실제 예시: 토큰 타임스탬프 디코딩하기

사용자가 2025년 7월 1일 오전 10:00 UTC에 로그인한 후 인증 서비스가 다음과 같은 JWT 페이로드를 발급한다고 가정해 봐요.

{
  "sub": "user_8821",
  "iat": 1751364000,
  "exp": 1751367600,
  "role": "admin"
}

각 숫자의 의미를 분석해 볼게요.

클레임 Unix 타임스탬프 사람이 읽을 수 있는 형식 (UTC) 의미
iat 1751364000 2025-07-01 10:00:00 토큰이 이 시점에 발급됨
exp 1751367600 2025-07-01 11:00:00 정확히 1시간 후 토큰 만료

차이는 정확히 3,600초, 즉 1시간이에요. 이 토큰을 11:00:00 UTC 이후에 수신하는 모든 서버는 해당 토큰을 거부해야 해요. 직접 확인하려면 iat 에서 exp 를 빼면 돼요. 1751367600 - 1751364000 = 3600초예요. Unix 타임스탬프 변환 도구를 사용하면 이 값들의 사람이 읽을 수 있는 형식을 즉시 확인할 수 있어요. 앞서 언급한 밀리초 대 초 버그를 사전에 방지하는 빠른 검증 방법이기도 해요.

데이터베이스에서 이 값들을 어떻게 저장해야 하는지에 대한 자세한 내용은 데이터베이스의 Unix 타임스탬프 가이드 를 참고하세요. 토큰의 exp 를 저장하든 감사 로그 테이블에 저장하든 동일한 정수 형식이 적용돼요.

클록 스큐: 토큰을 조용히 죽이는 문제

많은 팀이 놓치는 실제 문제가 있어요. 분산 시스템에서 인증 서버와 API 서버는 서로 다른 머신이에요. 시스템 클록은 NTP로 동기화되지만 완벽하게 일치하지는 않아요. 클라우드 환경에서는 30초에서 90초 정도의 차이가 흔하게 발생해요.

이런 상황을 생각해 봐요. exp = 1751367600 인 토큰을 API 서버가 검증하는데, 해당 서버의 시계가 1751367610을 가리키고 있어요. 만료 시점보다 겨우 10초 지났을 뿐이에요. 토큰은 거부되고 사용자는 401 오류를 받아요. 사용자 입장에서는 방금 로그인했는데 앱이 갑자기 작동하지 않는 거예요.

표준 해결책은 검증 로직에 클록 스큐 허용 범위 를 추가하는 거예요. 대부분의 JWT 라이브러리는 leeway 파라미터를 지원해요. 60초 값이 널리 인정되며 OpenID Connect Core 명세 에서도 참조하고 있어요. 이 값을 설정하고 문서화한 뒤, 스택의 모든 서비스에서 동일한 값을 사용하도록 해야 해요.

실용적인 규칙: exp 검증에는 60초 leeway를 추가하세요. 하지만 iat 검사에는 절대 leeway를 추가하지 마세요. 그렇게 하면 미래에 발급된 토큰을 수락하게 되는데, 이는 리플레이 공격의 위험 신호예요.

SaaS 제품에 맞는 토큰 만료 시간 선택하기

적절한 토큰 만료 값은 애플리케이션의 민감도와 예상 세션 동작에 따라 달라져요. 다음은 실용적인 기준이에요.

  • 민감도가 높은 앱 (금융, 의료, 관리자 대시보드): 액세스 토큰은 15분에서 30분. 리프레시 토큰을 사용해 세션을 자동으로 갱신하세요.
  • 일반 SaaS 앱 (프로젝트 관리, CRM, 분석): 1시간에서 8시간. 일반적인 업무 세션 길이에 맞추세요.
  • 위험도가 낮거나 읽기 전용 API: 최대 24시간까지 가능하지만, 민감한 작업에는 토큰 로테이션을 적용하세요.
  • 머신 간 서비스 토큰: 더 길게 설정(일 또는 주 단위)할 수 있지만, 범위를 좁게 제한하고 일정에 따라 로테이션해야 해요.

발급 시점에 exp 값을 계산하는 유용한 공식이에요.

// Node.js 예시
const iat = Math.floor(Date.now() / 1000); // 현재 시간을 초 단위로 변환
const ttl = 3600; // time-to-live: 1시간(초 단위)
const exp = iat + ttl;

const payload = {
  sub: userId,
  iat: iat,
  exp: exp
};

밀리초를 초로 변환하기 위해 명시적으로 1000으로 나누는 부분에 주목하세요. 이 한 줄이 JavaScript 애플리케이션에서 가장 흔한 JWT 타임스탬프 버그를 방지해줘요.

안전한 JWT 만료 구현을 위한 실천 단계

어떤 SaaS 인증 시스템에도 바로 적용할 수 있는 구체적인 체크리스트예요.

  1. 항상 iat exp 를 모두 포함하세요. iat 클레임은 기술적으로 선택 사항이지만, 감사 추적을 제공하고 만료와 독립적으로 최대 유효 기간을 적용할 수 있게 해줘요.
  2. 배포 전에 타임스탬프 형식을 확인하세요. 토큰을 디코딩해서 exp 가 10자리 정수인지 확인하세요. 13자리라면 밀리초가 포함된 거예요.
  3. 검증 미들웨어에 60초 클록 스큐 leeway를 설정하세요. 인증 토큰을 사용하는 모든 서비스에 일관되게 적용하세요.
  4. 단기 액세스 토큰과 리프레시 토큰을 함께 사용하세요. 15분 액세스 토큰과 7일 리프레시 토큰 조합은 단일 7일 액세스 토큰보다 훨씬 안전해요. 리프레시 토큰은 서버 측에서 폐기할 수 있기 때문이에요.
  5. 인증된 모든 요청에서 iat 값을 로깅하세요. 이를 통해 동일한 토큰이 서로 다른 두 지역에서 동시에 사용되는 것과 같은 이상 징후를 감지할 수 있어요.
  6. 스테이징 환경에서 시간을 가속해 만료를 테스트하세요. 클록을 exp 값 이후로 이동시켜 앱이 401 오류를 충돌이나 루프 없이 올바르게 처리하는지 확인하세요.

토큰에서 가져온 원시 exp 값을 읽기 쉬운 날짜로 빠르게 변환하거나, 특정 기간에 맞는 타임스탬프 값을 확인하고 싶다면 홈페이지의 epoch 변환 도구 를 사용해 보세요. 코드 없이 즉시 양방향 변환이 가능해요.

마무리

JWT 만료는 복잡한 개념이 아니지만, 작은 실수가 큰 보안 취약점으로 이어질 수 있어요. exp iat 클레임은 단순한 정수, 즉 epoch 기준으로 초 단위로 계산된 Unix 타임스탬프예요. 이 단순함이 강점이에요. 모든 서버, 모든 언어, 모든 플랫폼이 두 숫자를 모호함 없이 비교할 수 있어요. 진짜 실력은 이를 올바르게 적용하는 데 있어요. 적절한 토큰 만료 기간을 선택하고, 허용 범위로 클록 스큐를 처리하며, 밀리초 대 초 실수를 배포 전에 잡아내는 것이 중요해요. 이런 습관을 인증 파이프라인에 녹여내면 JSON web token 구현이 안전하고 유지보수하기 쉬운 형태로 완성돼요.

Unix timestamp converter tool for JWT expiration verification

JWT 타임스탬프를 즉시 확인하세요 - 코드 불필요

JWT 페이로드의 Unix 타임스탬프를 붙여넣으면 클릭 한 번으로 읽기 쉬운 날짜로 변환돼요. 밀리초 대 초 오류를 프로덕션 배포 전에 잡아보세요.

무료 도구 사용해보기 →

exp (만료 시간)는 토큰이 무효화되어 거부되어야 하는 Unix 타임스탬프예요. iat (발급 시간)는 토큰이 생성된 시점의 타임스탬프예요. 두 값 모두 초 단위 정수예요. iat 클레임은 선택 사항이지만 감사 및 최대 토큰 유효 기간 정책 적용에 유용해요.

Unix 타임스탬프는 타임존에 독립적인 정수이기 때문에 모든 서버와 지역에서 모호함 없이 사용할 수 있어요. "2025-07-01T10:00:00"과 같은 포맷된 날짜 문자열은 로케일이나 타임존 설정에 따라 잘못 해석될 수 있어요. 또한 정수 비교는 처리량이 높은 인증 시스템에서 문자열 파싱보다 빠르고 오류 발생 가능성도 낮아요.

유효 기간이 긴 토큰은 사용자 계정이 침해되거나 권한이 변경되어도 계속 유효해요. JWT는 스테이트리스이기 때문에 서버가 유효 기간 중에 토큰을 폐기할 수 없어요. 공격자가 30일 만료 토큰을 탈취하면 30일 동안 접근 권한을 갖게 돼요. 단기 액세스 토큰과 리프레시 토큰을 함께 사용하면 이 위험 범위를 크게 줄일 수 있어요.

JavaScript에서 iat 또는 exp 값을 생성할 때는 항상 Math.floor(Date.now() / 1000) 을 사용하세요. 원시 Date.now() 는 밀리초를 반환해요. 1000으로 나누면 JWT 명세가 요구하는 초 단위 정수로 변환돼요. 타임스탬프가 13자리가 아닌 10자리인지 확인해서 검증하세요.

클록 스큐는 분산 시스템에서 두 서버의 시스템 클록 사이에 발생하는 미세한 시간 차이예요. 30초 차이만 있어도 검증 서버의 클록이 약간 앞서 있으면 유효한 토큰이 거부될 수 있어요. 표준 해결책은 JWT 검증 라이브러리에 60초 leeway를 설정해 일반적인 NTP 드리프트를 흡수하는 거예요.