JWT 디코딩과 서명 검증의 차이 — exp·iat 읽기
2026.10.03 업데이트 · 4분 읽기
JWT 디코딩은 Base64URL을 풀어 읽는 것일 뿐 서명 검증이 아닙니다. 헤더·페이로드 구조, exp·iat·nbf 시각을 읽는 법, alg: none 토큰을 받으면 안 되는 이유를 RFC 7519 기준으로 정리했습니다.
JWT 디코더 바로 사용하기디코딩은 서명 검증이 아닙니다. JWT 내용은 누구나 읽을 수 있으니 비밀 정보를 넣지 마세요. JWT 디코더에 토큰을 붙여 넣으면 헤더와 페이로드, 만료 시각을 볼 수 있지만, 그것은 읽기일 뿐입니다. 그 토큰을 믿어도 되는지는 받는 서버가 키로 서명을 검증하고 exp·aud 같은 클레임을 확인해야 정해집니다.
JWT는 세 부분이다
서명된 JWT는 헤더.페이로드.서명 꼴이고, 세 부분은 각각 패딩 없는 Base64URL로 적혀 있습니다(RFC 7515). 예시 토큰을 보겠습니다. HS256 알고리즘과 예시용 비밀 키 zekilo-secret으로 서명한 것입니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlpFS0lMTyIsImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI5MjAwfQ.218DR4r7DRNkKO3XJcQYXE0ztwbqxYMeQpkIp9S18oE
| 부분 | 디코딩한 내용 |
|---|---|
| 헤더 | {"alg":"HS256","typ":"JWT"} |
| 페이로드 | {"sub":"1234567890","name":"ZEKILO","iat":1767225600,"exp":1767229200} |
| 서명 | 32바이트 HMAC-SHA256 값(Base64URL 43자) |
헤더와 페이로드는 암호화된 것이 아니라 Base64URL로 적힌 JSON입니다. 키 없이도 읽을 수 있습니다. 내용까지 암호화한 JWT는 JWE라는 별도 형식이며 점으로 나눈 부분이 다섯 개입니다.
디코딩과 검증은 무엇이 다른가
| 항목 | 디코딩 | 서명 검증 |
|---|---|---|
| 하는 일 | Base64URL을 풀어 JSON 읽기 | 키로 서명을 확인 |
| 필요한 것 | 없음 | 비밀 키(HS256) 또는 공개 키(RS256·ES256) |
| 알 수 있는 것 | 토큰에 적힌 내용 | 그 내용이 키를 가진 쪽이 서명한 그대로인지 |
| 할 수 있는 쪽 | 누구나 | 키를 가진 쪽 |
서명 대상은 BASE64URL(헤더), 점, BASE64URL(페이로드)를 이은 문자열입니다(RFC 7515). 위 토큰의 앞 두 부분에 대해 zekilo-secret으로 HMAC-SHA256을 계산하면 세 번째 부분과 같은 값이 나옵니다. 페이로드가 한 글자라도 달라지면 계산 결과가 달라지므로, 검증하는 쪽은 내용이 바뀌었다는 것을 알 수 있습니다.
검증은 서명 확인으로 끝나지 않습니다. 받는 쪽은 만료 시각(exp)과 발급자(iss), 대상(aud)도 확인해야 합니다. RFC 7519는 aud 클레임이 있는데 그 값에 자신이 없으면 토큰을 거부해야 한다고 정합니다.
exp·iat·nbf 읽기
세 클레임의 값은 NumericDate, 곧 1970-01-01T00:00:00Z부터 센 초입니다(RFC 7519 2절). 밀리초가 아닙니다. JavaScript의 Date.now()는 밀리초를 돌려주므로 1000으로 나눈 뒤 비교합니다.
| 클레임 | 뜻 | 예시 토큰의 값 | 날짜 |
|---|---|---|---|
iat |
발급한 시각 | 1767225600 | 2026-01-01 00:00:00 UTC (KST 09:00) |
exp |
이 시각부터는 받으면 안 됨 | 1767229200 | 2026-01-01 01:00:00 UTC (KST 10:00) |
nbf |
이 시각 전에는 받으면 안 됨 | 없음 | — |
exp에서 iat를 빼면 3,600초이므로 이 토큰은 발급 후 1시간 동안 유효합니다. RFC 7519는 서버 사이의 시계 차이를 감안해 보통 몇 분을 넘지 않는 여유를 둘 수 있다고 합니다. 등록 클레임은 모두 선택 사항이라 토큰에 없을 수도 있습니다.
숫자를 날짜로 바꿀 때는 Unix 타임스탬프 변환에 1767225600을 넣으면 2026-01-01T00:00:00Z, 한국 시간 2026-01-01 09:00:00으로 보여 줍니다. JWT 디코더도 exp·nbf·iat 옆에 날짜를 표시하고, exp가 지났으면 만료 경고를 보여 줍니다. 이 표시는 브라우저 시계와 비교한 참고 정보이며 검증을 대신하지 않습니다.
alg: none과 알고리즘 확인
RFC 7519 6절은 서명이 없는 JWT(Unsecured JWT)를 정의합니다. 헤더의 alg가 none이고 서명 부분이 비어 있습니다. 다른 수단으로 보호되는 경우를 위한 형식이지, 일반 인증 토큰으로 받을 수 있는 것이 아닙니다.
- RFC 7518 3.6절: 구현은 서명 없는 토큰을 기본으로 받아들여서는 안 됩니다.
- RFC 8725 3.1절: 라이브러리는 호출하는 쪽이 허용할 알고리즘 목록을 지정할 수 있게 해야 하고, 그 밖의 알고리즘을 써서는 안 됩니다.
따라서 서버는 허용할 알고리즘을 코드에 정해 두고, 토큰 헤더에 적힌 alg를 그대로 따르지 않아야 합니다. JWT 디코더는 alg가 none인 토큰에 서명이 없다는 경고를 보여 줍니다.
실무에서 지킬 것
- 페이로드에 비밀번호, 주민등록번호, 카드 번호 같은 정보를 넣지 않습니다.
- 토큰은 가진 사람이 쓸 수 있는 출입증입니다. 운영 환경의 토큰을 채팅, 이슈, 로그에 붙이지 않습니다.
- 검증은 서버에서 검증된 라이브러리로 합니다. 알고리즘을 정해 두고, 서명과
exp·nbf·iss·aud를 확인합니다. - 클라이언트에서 디코딩한 값은 화면 표시용으로만 씁니다. 권한 판단은 서버가 검증한 결과로 합니다.