JWT 디코더
JWT의 헤더와 페이로드를 열어 클레임을 확인하고 만료 시각을 사람이 읽을 수 있는 형태로 보여줍니다.
서명은 검증하지 않습니다. 검증에는 비밀키나 공개키가 필요한데, 그 키를 웹 도구에 붙여넣도록 유도하는 것 자체가 위험한 관행이라 의도적으로 제외했습니다. 디코딩이 된다고 해서 유효한 토큰이라는 뜻은 아닙니다. 디코딩은 전적으로 브라우저 안에서 이루어집니다. 그렇더라도 운영 환경의 유효한 토큰은 어떤 온라인 도구에도 붙여넣지 않는 것이 안전합니다.
JWT는 어떻게 생겼나
JWT(JSON Web Token)는 점(.)으로 구분된 세 부분으로 이루어집니다.
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMjM0In0 . 4pcPyMD09olPSyXnrXCjTw
헤더 페이로드 서명
앞의 두 부분은 URL-safe Base64로 인코딩된 JSON입니다. 암호화가 아니라 인코딩이므로 누구나 내용을 볼 수 있습니다.
헤더에는 서명 알고리즘(alg)과 토큰 타입(typ)이 들어갑니다.
페이로드에는 클레임(claim)이 들어갑니다. 사용자 ID, 권한, 만료 시각 같은 정보입니다.
서명은 앞의 두 부분을 비밀키로 서명한 값입니다. 내용이 위조되지 않았음을 증명합니다.
표준 클레임
JWT 표준(RFC 7519)이 정의한 클레임입니다.
| 클레임 | 의미 |
|---|---|
| iss | 발급자 (issuer) |
| sub | 주체 — 보통 사용자 ID |
| aud | 이 토큰을 받을 대상 (audience) |
| exp | 만료 시각 |
| nbf | 이 시각 이전에는 사용 불가 (not before) |
| iat | 발급 시각 |
| jti | 토큰 고유 ID |
이 밖에 서비스가 필요로 하는 값(이름, 역할, 테넌트 ID 등)을 자유롭게 넣을 수 있습니다.
exp는 초 단위입니다 (밀리초 아님)
가장 자주 발생하는 버그입니다. exp, iat, nbf는 모두 1970년 1월 1일 UTC부터 흐른 초(second) 단위입니다.
그런데 자바스크립트의 Date.now()는 밀리초를 반환합니다. 이 값을 그대로 넣으면 1,000배 큰 값이 되어 서기 5만 년 같은 날짜가 나옵니다.
Math.floor(Date.now() / 1000) ← 올바른 방법
Date.now() ← 1000배 어긋남
이 디코더는 값이 지나치게 크면 밀리초를 넣은 것은 아닌지 알려줍니다.
왜 서명 검증은 안 해주나요
서명을 검증하려면 **비밀키(HS256)나 공개키(RS256)**가 필요합니다.
그런데 사용자에게 서명 키를 웹 도구에 붙여넣도록 유도하는 것 자체가 위험한 관행입니다. 그 키 하나면 누구든 유효한 토큰을 위조할 수 있기 때문입니다. 그래서 이 도구는 디코딩만 제공합니다.
기억하실 점은 디코딩이 된다고 해서 유효한 토큰이 아니라는 것입니다. 서명이 완전히 엉터리여도 헤더와 페이로드는 읽힙니다. 실제 검증은 서버에서 해야 합니다.
JWT에 민감한 정보를 넣지 마세요
페이로드는 암호화되지 않습니다. Base64를 되돌리면 누구나 내용을 봅니다.
따라서 다음은 절대 넣으면 안 됩니다.
- 비밀번호
- 주민등록번호, 카드번호
- 내부 시스템의 상세 정보
서명은 위조를 막을 뿐 내용을 숨기지 않습니다. 내용까지 감춰야 한다면 JWE(암호화된 JWT)를 써야 합니다.
alg: none 공격
과거 여러 JWT 라이브러리에 있던 심각한 취약점입니다. 헤더의 alg를 none으로 바꾸고 서명 부분을 비우면, 검증을 건너뛰고 토큰을 받아들이는 구현이 있었습니다.
공격자가 페이로드의 사용자 ID를 관리자로 바꿔 넣으면 그대로 통과됩니다.
방어 방법은 서버가 기대하는 알고리즘을 고정하는 것입니다. 토큰이 알려주는 alg를 그대로 믿으면 안 됩니다.
토큰이 만료되면
exp가 지난 토큰은 서버가 거부합니다. 이때는 보통 리프레시 토큰으로 새 액세스 토큰을 발급받습니다.
액세스 토큰은 수명을 짧게(15분~1시간) 두고, 리프레시 토큰은 길게(2주~한 달) 두는 것이 일반적입니다. 액세스 토큰이 유출되어도 피해 기간을 줄이기 위해서입니다.
한 가지 알아둘 점은 JWT는 서버에서 즉시 무효화하기 어렵다는 것입니다. 서버가 상태를 저장하지 않기 때문입니다. 강제 로그아웃이 필요하다면 별도의 블랙리스트를 두거나 토큰 수명을 짧게 가져가야 합니다.
함께 확인하면 좋은 것
Base64 자체를 다루려면 Base64 인코더·디코더를, 페이로드 JSON을 정리하려면 JSON 포맷터를 이용하세요.