JWT를 디코딩하는 방법
- 토큰에 토큰을 붙여넣으세요.
Authorization: Bearer eyJ…줄 전체도 되고, 앞뒤 따옴표나 여러 줄로 나뉜 토큰도 됩니다. - 결과를 읽으세요. 세 부분이 서로 다른 색으로 표시되고, 헤더와 페이로드는 보기 좋게 정렬되며, 클레임에 모든 클레임과 그 의미가 나열됩니다.
exp와iat같은 시각은 현지 시각, UTC, “1시간 후 만료” 같은 상대 시간으로 나타납니다. - 서명을 확인하려면 서명 검증 아래에 공유 시크릿이나 공개 키를 붙여넣으세요. 필드 라벨은 토큰의
alg를 따릅니다. HS256/384/512는 시크릿, 나머지는 공개 키입니다. - 경고를 읽으세요. 도구는
alg: none, 만료된 토큰, 미래의nbf, 없는exp, 밀리초로 쓴 시각, 원격 키를 가리키는 헤더(jku,x5u)나 자기 키를 담은 헤더(jwk)를 표시합니다.
/jwt-decoder/#token=eyJ… 같은 링크로 토큰을 바로 열 수도 있습니다. # 뒤의 부분은 URL 프래그먼트이며, 브라우저는 프래그먼트를 서버로 보내지 않습니다.
토큰 안에 든 것
서명된 JWT(compact 형식의 JWS)는 점으로 이은 Base64URL 문자열 세 개입니다: header.payload.signature. 다음은 2026년 1월 1일에 발급되어 한 시간 동안 유효한 실제 HS256 토큰입니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjkyMDB9.U3Gb12hN5Fnpi1rJ7nrkUzIEidkg-hPnbgiIpTah3R4
첫 번째 부분은 헤더 {"alg":"HS256","typ":"JWT"}로 디코딩됩니다. 두 번째는 페이로드입니다.
{"iss":"https://auth.example.com","sub":"user-42","aud":"api.example.com","iat":1767225600,"exp":1767229200}
세 번째 부분은 앞의 두 부분으로 계산한 32바이트 HMAC-SHA256이므로, 헤더나 페이로드의 문자 하나만 바꿔도 깨집니다. 거의 모든 JWT는 {"의 Base64인 eyJ로 시작합니다. 각 부분은 = 패딩 없이 URL-safe 알파벳(-와 _)을 씁니다. 직접 디코딩하는 과정은 JWT가 Base64URL을 쓰는 방식에서 차근차근 설명합니다.
JWS와 JWE: 서명된 토큰과 암호화된 토큰
대부분의 JWT는 JWS입니다. 서명되어 있고 누구나 읽을 수 있습니다. JWE는 암호화되어 있으며 세 부분이 아니라 다섯 부분으로 됩니다: header.encrypted_key.iv.ciphertext.tag. 헤더는 읽을 수 있고, 키 관리 알고리즘(alg, 예: RSA-OAEP-256이나 dir)과 콘텐츠 암호(enc, 예: A256GCM)를 명시합니다.
JWE를 여기에 붙여넣으면 도구가 그 헤더를 디코딩해 alg, enc, kid를 보여 주고 페이로드를 암호화됨으로 표시합니다. 복호화하려면 수신자의 개인 키나 공유 키가 필요하며, 이 도구는 JWE를 복호화하지 않습니다. 사용자가 페이로드를 읽을 수 없는 토큰이 필요하다면 JWE가 맞는 형식입니다. JWS 페이로드에 비밀 값을 넣는 것은 일반 텍스트로 넣는 것과 같습니다. Base64는 암호화가 아닙니다에서 설명합니다.
JWT 클레임 참고표
처음 일곱 행은 RFC 7519의 등록 클레임이고, 나머지는 OpenID Connect와 OAuth 토큰에서 흔히 쓰입니다. 모든 시각 값은 NumericDate, 즉 1970-01-01 UTC 이후의 정수 초이며 밀리초가 아닙니다.
| 클레임 | 이름 | 확인할 점 |
|---|---|---|
iss | 발급자 | 토큰을 만들고 서명한 주체. 신뢰하는 발급자와 일치해야 합니다. |
sub | 주체 | 토큰이 가리키는 사용자나 엔터티. 대개 사용자 ID입니다. |
aud | 대상 | 토큰을 받을 대상. 문자열이나 배열이며, API는 다른 대상에게 발급된 토큰을 거부해야 합니다. |
exp | 만료 시각 | 이 시각 이후에는 토큰을 거부해야 합니다. |
nbf | 유효 시작 | 이 시각 전에는 토큰이 아직 유효하지 않습니다. |
iat | 발급 시각 | 발급된 시각. 사용 기한 제한에 유용합니다. |
jti | JWT ID | 고유 ID. 재전송 감지나 개별 토큰 폐기에 쓰입니다. |
azp | 권한 있는 당사자 | 토큰을 발급받은 OAuth 클라이언트(OpenID Connect). |
scope, scp | 스코프 | 부여된 OAuth 스코프. scp는 Microsoft와 Okta 방식의 표기입니다. |
nonce | Nonce | OpenID Connect ID 토큰을 그것을 요청한 로그인 요청과 연결합니다. |
auth_time | 인증 시각 | 사용자가 실제로 로그인한 시각. iat보다 훨씬 이전일 수 있습니다. |
roles, groups | 역할, 그룹 | ID 공급자가 추가한 권한 데이터. |
tid, oid | 테넌트, 개체 ID | Microsoft Entra ID 테넌트와 사용자 개체 식별자. |
헤더에는 별도의 파라미터가 있습니다. alg(서명 알고리즘), typ(대개 JWT, OAuth 액세스 토큰은 at+jwt), kid(서명한 키), cty(토큰이 다른 토큰 안에 중첩될 때 JWT로 설정)입니다.
서명 검증: 어떤 키를 붙여넣어야 하나
토큰의 출처를 보여 주는 것은 서명 확인뿐이며, 필요한 키는 헤더의 알고리즘 계열에 따라 다릅니다.
alg | 붙여넣을 키 | 서명 길이 |
|---|---|---|
| HS256, HS384, HS512 | 공유 시크릿. 저장된 방식에 맞게 시크릿 형식을 텍스트, Base64/Base64URL, Hex 중 하나로 설정하세요. oct JWK도 됩니다. | 32, 48, 64바이트 |
| RS256–RS512, PS256–PS512 | RSA 공개 키: PEM BEGIN PUBLIC KEY, PEM BEGIN RSA PUBLIC KEY, X.509 인증서, JWK, JWKS. | 키 크기. RSA-2048은 256바이트 |
| ES256, ES384, ES512 | P-256, P-384, P-521 곡선의 EC 공개 키. 위의 어떤 형태든 됩니다. | 64, 96, 132바이트 (원시 r‖s) |
| EdDSA | PEM이나 OKP JWK 형식의 Ed25519 공개 키. | 64바이트 |
ID 공급자가 발급한 토큰이라면 공개 키는 https://<issuer>/.well-known/openid-configuration에 나온 jwks_uri에 있습니다. 이 페이지는 다른 서버에 연결할 수 없어 그 URL을 가져올 수 없으므로, 새 탭에서 열어 JSON을 붙여넣으세요. JWKS를 쓰면 kid로 키를 고르고, 토큰에 kid가 없으면 호환되는 모든 키를 시도합니다.
DER 형식의 ECDSA 서명(ES256이면 64바이트 대신 대개 70–72바이트)은 감지해 변환하고 표시합니다. JWS에는 원시 r‖s가 필요하고 많은 라이브러리가 DER을 거부하기 때문입니다. 개인 키를 붙여넣어도 됩니다(공개 키 부분을 도출합니다). 다만 검증에 개인 키가 필요한 경우는 없습니다. ES256K와 Ed448은 WebCrypto에 없어서 지원하지 않습니다.
확실한 시크릿인데도 HS256 토큰 검증이 실패한다면, 설정 어딘가에 시크릿이 Base64로 인코딩되어 있고 서버가 사용 전에 디코딩하는 경우가 많습니다. 시크릿 형식을 Base64로 바꿔 다시 시도하세요.
여기서 토큰을 디코딩해도 안전한 이유
Authorization 헤더의 JWT는 만료될 때까지 실제로 작동하는 자격 증명입니다. 운영 환경의 유효한 토큰을 서버로 보내는 웹 페이지에 붙여넣으면, 그 서버를 운영하는 누군가가 토큰을 API에 재사용할 수 있고, 페이지가 그런 일을 하는지 밖에서는 대개 알 수 없습니다.
이 페이지에서는 디코딩과 WebCrypto 서명 확인이 모두 브라우저에서 실행됩니다. 페이지에는 Content Security Policy도 있는데, 이 정책의 connect-src는 Cloudflare Web Analytics가 페이지 조회수를 집계하는 /cdn-cgi/rum만 허용합니다. 그래서 페이지가 시도할 수 있는 그 밖의 모든 fetch, XHR, WebSocket, beacon 요청을 브라우저가 차단합니다. 직접 확인할 수도 있습니다. 개발자 도구의 네트워크 탭을 연 채 토큰을 붙여넣거나, 페이지를 불러온 뒤 Wi-Fi를 끄고 계속 디코딩해 보세요.
그래도 테스트 토큰이나 곧 만료될 토큰을 쓰는 편이 좋으며, 운영 토큰을 직접 관리하지 않는 곳에 붙여넣었다면 세션을 폐기하세요.
만료된 토큰, nbf, 시계 오차
exp와 nbf는 검증하는 쪽의 시계를 기준으로 확인하는데, 시계는 어긋납니다. 시계가 30초 빠른 서버가 발급한 토큰은 iat와 nbf가 미래인 채로 도착해 “아직 유효하지 않음"으로 거부될 수 있습니다. 그래서 JWT 라이브러리는 작은 허용 오차를 둡니다. PyJWT의 leeway, jsonwebtoken과 jose의 clockTolerance가 그것입니다.
이 디코더는 시각을 기기의 시계와 비교해 결과를 알기 쉬운 말로 알려 줍니다. exp는 “2일 전 만료됨”, nbf는 남은 시간과 함께 “아직 유효하지 않습니다”, 그리고 iat가 미래라면 발급자의 시계를 의심하라는 경고를 보여 줍니다. 1767229200000 같은 값은 밀리초로 표시됩니다. 초로 읽으면 수만 년 뒤가 되는데, 코드에서 Math.floor(Date.now() / 1000) 대신 Date.now()를 쓸 때 흔히 생기는 버그입니다.
검증 없이 코드에서 클레임만 읽는다면 Python 표준 라이브러리로 충분합니다.
import base64, json
def b64url_decode(part):
return base64.urlsafe_b64decode(part + "=" * (-len(part) % 4))
header_b64, payload_b64, signature_b64 = token.split(".")
print(json.loads(b64url_decode(payload_b64)))
Node.js에서는 JSON.parse(Buffer.from(payload_b64, "base64url").toString("utf8"))가 같은 일을 합니다. 권한 결정에는 서명을 검증하고 허용 알고리즘을 고정하는 라이브러리를 쓰세요. 더 많은 코드는 Python과 Node.js 페이지에 있습니다.
테스트용으로 토큰을 만들거나 다시 서명하려면 JWT 인코더를 사용하세요. 한 부분만 따로 디코딩하려면 Base64URL 디코더를 쓰면 됩니다.
자주 묻는 질문
시크릿 키 없이 JWT를 디코딩할 수 있나요?
네. 헤더와 페이로드는 Base64URL로 인코딩한 JSON일 뿐이라 토큰을 가진 사람은 누구나 읽을 수 있습니다. 시크릿이나 공개 키는 서명이 진짜인지 확인할 때만 필요합니다.
디코더가 서명이 잘못되었다고 하는 이유는 무엇인가요?
대개 키가 틀렸거나 형식이 맞지 않습니다. HS256이라면 시크릿이 Base64나 hex로 저장되어 있는지 확인하고 시크릿 형식을 그에 맞게 바꾸세요. RS256이나 ES256이라면 공개 키가 토큰의 kid에 해당하는지 확인하세요. JWKS 전체를 붙여넣으면 도구가 kid로 키를 골라 줍니다.
JWT가 언제 만료되는지 어떻게 알 수 있나요?
exp 클레임을 보세요. 1970-01-01 UTC 이후의 초 단위 값이며, 클레임 표에 현지 시각, UTC, “2일 전 만료됨” 같은 상대 시간으로 표시됩니다. exp가 없는 토큰은 스스로 만료되지 않습니다.
이 도구로 JWE를 복호화할 수 있나요?
아닙니다. 다섯 부분으로 된 암호화 토큰이라면 보호된 헤더를 디코딩해 alg, enc, kid를 보여 줍니다. 페이로드를 복호화하려면 수신자의 개인 키나 공유 키가 필요합니다.
붙여넣은 토큰이 어딘가로 전송되나요?
아닙니다. 디코딩과 검증은 브라우저에서 JavaScript와 WebCrypto로 실행되며, 페이지의 Content Security Policy가 Cloudflare 조회수 집계를 제외한 모든 네트워크 요청을 막습니다.