개발자
JWT 디코더
JWT를 붙여넣으면 header·payload를 풀어 클레임을 표로 보여주고, exp·iat·nbf를 한국 시각으로 환산해 만료 여부까지 확인합니다. 서명은 검증하지 않습니다.
JWT 구조 — header.payload.signature
JWT는 점(.)으로 나뉜 세 부분으로 이뤄집니다. 각 부분은 Base64URL로 인코딩되어 있어, 일반 Base64와 달리 +·/ 대신-·_를 쓰고 끝의 패딩(=)을 생략합니다.
① Header
서명 알고리즘(alg)과 타입(typ). 어떤 방식으로 서명했는지 알려줍니다.
② Payload
클레임의 집합. 사용자 ID(sub)·발급자(iss)·만료(exp) 등이 담깁니다.
③ Signature
header·payload와 비밀키로 만든 서명. 위변조를 잡아내는 검증용입니다.
주요 클레임의 의미
RFC 7519는 일곱 개의 표준 클레임을 정의합니다. 모두 선택 사항이지만 인증에서 자주 쓰입니다. 나머지 키는 서비스가 자유롭게 정한 커스텀 클레임입니다.
| 클레임 | 이름 | 의미 |
|---|---|---|
| iss | 발급자 | 토큰을 발급한 주체 (예: 인증 서버 주소) |
| sub | 주체 | 토큰이 가리키는 대상 — 보통 사용자 식별자 |
| aud | 대상자 | 이 토큰을 받기로 한 수신자 |
| exp | 만료 시각 | 이 시각(epoch 초) 이후에는 무효 |
| nbf | 활성 시각 | 이 시각 이전에는 사용 불가 (Not Before) |
| iat | 발급 시각 | 토큰이 만들어진 시각 |
| jti | 토큰 ID | 재사용·중복 방지를 위한 고유 식별자 |
왜 서명 검증은 서버에서만 가능한가
서명은 header와 payload를 비밀키로 해싱한 결과입니다. 토큰이 진짜인지 확인하려면 같은 비밀키로 서명을 다시 계산해 일치하는지 비교해야 합니다.
- HS256(대칭키): 서명과 검증에 같은 비밀키를 씁니다. 이 키를 브라우저에 두면 누구나 위조 토큰을 만들 수 있으므로 절대 노출하지 않습니다.
- RS256(비대칭키): 개인키로 서명하고 공개키로 검증합니다. 공개키는 공개돼도 되지만, 검증 로직 자체는 보통 서버나 게이트웨이가 담당합니다.
- 그래서 디코딩(읽기)과 검증(진위 확인)은 별개입니다. 이 도구는 읽기까지만 합니다.
exp·iat 시간 읽는 법
시간 클레임(exp·iat·nbf)은 Unix epoch 초로 저장됩니다 — 1970년 1월 1일 0시(UTC)부터 흐른 초입니다. 예를 들어 1767225600은 2026년 1월 1일 오전 9시(KST)를 가리킵니다.
- 이 도구는 초를 KST(Asia/Seoul)로 환산해 보여줍니다.
- 값이 13자리면 밀리초로 저장됐을 가능성이 있어 배지로 표시합니다. JWT 표준은 초 단위이므로 13자리는 비표준 신호입니다.
exp − iat가 토큰의 유효 기간입니다 (예: 3600이면 1시간짜리 액세스 토큰).
alg 혼동·none 공격 — header의 alg를 믿으면 안 되는 이유
서명 검증에서 가장 흔한 실수는 header에 적힌 alg 값을 그대로 믿는 것입니다. 공격자는 header만 고쳐도 되므로, 검증 서버가 알고리즘을 고정하지 않으면 두 가지 우회가 생깁니다.
| 공격 | 조작 | 통하는 조건 |
|---|---|---|
| alg:none | alg을 none으로 바꾸고 서명을 비움 | 라이브러리가 none을 서명 없는 토큰으로 통과시킬 때 |
| RS256→HS256 | alg을 HS256으로 바꿔 공개키를 HMAC 비밀키로 사용 | verify가 header의 alg를 따라 공개키로 HMAC 검증할 때 |
RS256→HS256 혼동은 공개키가 JWKS로 공개돼 있다는 점을 노립니다. 공개키를 그대로 HMAC 비밀키로 써서 토큰을 다시 서명하면, header의 alg만 보고 공개키로 HMAC 검증하는 서버는 이를 유효한 서명으로 받아들입니다. 실제로 node-jsonwebtoken 4.2.2 이전 버전은 비대칭키를 기대하는 검증에 대칭 알고리즘 토큰을 넣어 우회할 수 있었고 (CVE-2015-9235, CVSS 9.8), pyjwt·php-jwt 등 여러 라이브러리가 같은 결함을 공유했습니다.
- 알고리즘 화이트리스트: 검증 호출에서
algorithms: ['RS256']처럼 허용 알고리즘을 명시하고, 그 외에는 거부합니다. RFC 8725 §3.1은 라이브러리가 허용 집합을 지정하게 하고 header의 alg가 실제 연산과 같은지 확인하도록 요구합니다. - none 금지: RFC 8725 §3.2는 명시적 요청이 없는 한 none을 소비하지 말라고 권고합니다. 발급·검증 어느 쪽에서도 none을 기본 허용하지 않습니다.
- 알고리즘 고정: 발급 때 쓴 알고리즘을 서버가 이미 알고 있으므로, 검증은 header가 아니라 서버가 정한 알고리즘으로만 수행합니다.
액세스 vs 리프레시 토큰 — 수명 설계
토큰의 exp − iat로 종류를 역추정할 수 있습니다. 수명이 짧으면 API 호출용 액세스 토큰, 며칠~몇 주로 길면 재발급용 리프레시 토큰일 가능성이 큽니다.
| 구분 | exp − iat 예시 | 역할 |
|---|---|---|
| 액세스 토큰 | 900~3600초 (15분~1시간) | 리소스 접근에 직접 제시. 짧게 유지해 탈취 시 피해 창을 줄임 |
| 리프레시 토큰 | 수 일~수 주 | 만료된 액세스 토큰을 재발급받는 용도. 노출 위험이 커 회전이 필요 |
짧은 수명 + 리프레시 토큰 회전(rotation)이 권장되는 이유는, 토큰이 새면 피해를 줄이기 위해서입니다. 회전은 재발급 때마다 리프레시 토큰을 새로 내주고 이전 것을 즉시 폐기해, 한 번 쓰인 토큰이 다시 쓰이면 재사용을 탐지해 토큰 계열 전체를 무효화합니다. RFC 9700은 공개 클라이언트의 리프레시 토큰을 회전하거나 발신자 제약(mTLS·DPoP)으로 묶으라고 요구합니다.
왜 로그아웃 즉시 무효화가 어려운가: JWT는 서명만 맞으면 서버 조회 없이 유효하다고 판단하는 무상태(stateless) 토큰입니다. 그래서 발급 뒤에는 exp가 지나기 전까지 서버가 회수할 수단이 기본적으로 없습니다. 로그아웃해도 이미 나간 액세스 토큰은 만료까지 살아 있어, 즉시 차단하려면 블랙리스트·짧은 exp·서명키 교체 같은 상태 저장 장치를 더해야 합니다. 이는 무상태의 장점을 일부 포기하는 트레이드오프입니다. 그래서 액세스 토큰을 짧게 두고 실질적 회수는 리프레시 단계에서 처리하는 설계가 자리 잡았습니다.
자주 묻는 질문 (FAQ)
Q1. JWT를 이 도구로 디코딩해도 안전한가요?
입력한 토큰은 브라우저 안에서만 디코드되고 서버로 전송되지 않습니다. 다만 JWT의 payload는 누구나 Base64URL만 풀면 읽을 수 있는 암호화되지 않은 정보입니다. 그래서 운영 중인 실제 토큰을 공유 화면이나 캡처에 노출하지 않는 편이 안전합니다. 만료된 토큰이나 테스트 토큰으로 확인하는 것을 권장합니다.
Q2. JWT 만료 시간은 어떻게 확인하나요?
payload의 exp 클레임이 만료 시각이며, 1970년 1월 1일부터의 초(epoch seconds)로 저장됩니다. 이 도구는 exp를 KST(한국 시각)로 환산해 보여주고, 현재 시각과 비교해 유효 / 만료 임박(5분 이내) / 만료됨 배지와 남은 시간을 함께 표시합니다. exp가 없는 토큰은 만료 개념이 없는 토큰입니다.
Q3. 서명 검증도 되나요?
되지 않습니다. 서명 검증에는 토큰을 발급한 서버의 비밀키(HS256) 또는 공개키(RS256)가 필요한데, 정적 웹페이지에는 그 키가 없습니다. 이 도구는 header·payload를 디코드하고 시간 클레임을 환산하는 데까지만 합니다. 토큰이 위조되지 않았는지는 반드시 서버에서 확인해야 합니다.
Q4. payload에 비밀번호나 민감 정보를 넣어도 되나요?
안 됩니다. JWT payload는 암호화가 아니라 인코딩일 뿐이라 누구나 풀어 읽을 수 있습니다. 비밀번호·주민번호·카드번호 같은 민감 정보를 넣으면 토큰을 가진 사람에게 그대로 노출됩니다. payload에는 사용자 ID·권한·만료 시각처럼 노출돼도 되는 식별 정보만 담는 것이 원칙입니다.
Q5. Bearer 토큰이 곧 JWT인가요?
항상 그렇지는 않습니다. Authorization: Bearer <토큰>은 토큰을 전달하는 방식을 가리킬 뿐이고, 그 토큰이 JWT일 수도 있고 임의의 불투명 토큰(opaque token)일 수도 있습니다. 점(.)으로 3분할되고 각 부분이 Base64URL로 풀리면 JWT입니다. 이 도구는 Bearer 접두사를 자동으로 떼고 디코드합니다.
Q6. header.payload.signature 중 signature는 왜 안 풀리나요?
signature는 JSON이 아니라 해시·서명 알고리즘의 바이트 결과를 Base64URL로 인코딩한 값입니다. 사람이 읽을 수 있는 형태가 아니므로 원문 그대로 표시만 합니다. 이 값은 header와 payload, 그리고 비밀키로 다시 계산해 일치하는지 비교하는 검증용이며, 그 계산은 서버에서 이뤄집니다.