JWT 디코더
JWT 헤더·페이로드를 즉시 디코딩, 만료·알고리즘 확인
🔒 입력한 토큰은 서버로 전송되지 않고 브라우저 안에서만 처리됩니다.
헤더 (Header)
페이로드 (Payload)
서명 (Signature, 검증 안 함)
토큰을 입력하면 여기에 디코딩 결과가 표시됩니다.
처리 중…
0.0
멈춘 것이 아닙니다. 계산이 끝나면 결과가 표시됩니다.
JWT 디코더 소개
JWT(JSON Web Token, RFC 7519)는 점 두 개로 나뉜 세 조각입니다. 앞의 두 조각은 헤더와 페이로드 JSON을 base64url로 인코딩한 것이고, 마지막 조각이 서명입니다. 이 도구는 앞의 두 조각을 base64url 디코딩한 뒤 JSON.parse로 읽어 보여 주고, exp·iat·nbf를 사람이 읽는 날짜로 바꿉니다.
중요한 점은 이 도구가 서명을 검증하지 않는다는 것입니다. 서명을 확인하려면 HS256이면 서버의 비밀키, RS256·ES256이면 발급자의 공개키가 필요한데, 디코더는 그 어느 것도 받지 않습니다. 화면에 페이로드가 멀쩡히 보인다는 것은 「base64url과 JSON 형식이 맞다」는 뜻일 뿐, 「이 토큰을 믿어도 된다」는 뜻이 아닙니다.
디코딩은 누구나 할 수 있다: 페이로드는 암호화가 아니다
base64url은 암호화가 아니라 표기 변환입니다. 로그에 남은 토큰, 브라우저 localStorage에 있는 토큰을 복사한 사람은 누구나 이 도구처럼 내용을 읽을 수 있습니다. 그래서 서명된 JWT(JWS)의 페이로드에 주민번호, 비밀번호, 내부 권한 정책 같은 값을 넣으면 안 됩니다. 내용을 숨겨야 한다면 JWE(암호화된 JWT)를 써야 하는데, JWE는 점으로 나뉜 조각이 다섯 개라 이 도구에서는 「세그먼트 수 오류」로 나옵니다.
반대로 서명이 하는 일은 「누가 이 내용을 바꾸지 않았다」를 보장하는 것입니다. 페이로드의 role을 user에서 admin으로 고쳐 다시 인코딩하면 디코더에는 admin으로 잘 보이지만, 서명이 맞지 않으므로 제대로 된 서버는 거부합니다.
alg 헤더를 믿으면 생기는 두 가지 공격
헤더의 alg는 공격자가 마음대로 바꿀 수 있는 값입니다. RFC 8725(JWT 모범 사례)는 이 값을 믿어서 생긴 대표적인 공격 두 가지를 적어 둡니다.
- alg: none — RFC 7519 6장은 서명이 빈 문자열인 「보안 없는 JWT」를 정의합니다. 일부 라이브러리는 헤더가 none이면 서명 검사 없이 통과시켰습니다. 아래 예시처럼 마지막 점 뒤가 비어 있는 토큰이 이 형태입니다.
- RS256 → HS256 혼동 — 서버가 RS256 공개키로 검증하도록 되어 있는데, 공격자가 alg를 HS256으로 바꾸고 그 공개키(누구나 구할 수 있음)를 HMAC 비밀키 삼아 서명을 만들면, alg를 그대로 따르는 구현은 공개키로 HMAC을 계산해 통과시킵니다.
- 대응은 같습니다. 검증하는 쪽이 허용할 알고리즘을 코드에 고정하고(RFC 8725는 호출자가 허용 목록을 지정할 수 있어야 하고 그 밖의 알고리즘은 쓰면 안 된다고 씁니다), 키 하나는 알고리즘 하나에만 쓰도록 합니다.
- HS256 비밀키가 짧거나 사람이 외울 수 있는 문자열이면, 토큰 하나만 손에 넣어도 오프라인에서 사전 대입으로 키를 찾을 수 있습니다(RFC 8725 2.2절).
exp, nbf, iat와 시계 차이
세 값 모두 NumericDate, 즉 1970-01-01 UTC부터의 「초」입니다. JavaScript의 Date.now()는 밀리초라서 직접 토큰을 만들 때 1000을 곱하거나 나누는 것을 잊으면 exp가 5만 년 뒤가 되거나 1970년이 됩니다. 도구가 보여 주는 날짜가 이상하게 멀다면 이 실수를 의심하세요.
RFC 7519에서 exp는 「그 시각 이후에는 받아들이면 안 되는」 시각이고, nbf는 「그 시각 전에는 받아들이면 안 되는」 시각입니다. 두 항목 모두 시계 차이를 위해 「보통 몇 분을 넘지 않는 작은 여유(leeway)」를 둘 수 있다고 적혀 있습니다. 발급 서버와 검증 서버의 시계가 30초 어긋나 있으면, 방금 받은 토큰이 nbf 때문에 거부되는 일이 실제로 생깁니다.
이 도구의 「만료됨」과 「아직 유효하지 않음」(nbf 이전) 표시는 지금 여러분 컴퓨터의 시계와 exp·nbf를 비교한 결과입니다. PC 시계가 틀려 있으면 판정도 틀립니다. 「시계 오차 허용(초)」 칸에 값을 넣으면 그만큼 여유를 두고 판정하며, 기본값은 0초입니다. iat가 현재보다 미래면 시계가 어긋났다는 경고를 띄웁니다. iat는 발급 시각일 뿐 그 자체로 만료를 뜻하지 않으며, exp가 없는 토큰은 표준상 만료가 없습니다.
예시
jwt.io 기본 예제 토큰
- 입력
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c- 결과
헤더 {"alg":"HS256","typ":"JWT"} 페이로드 {"sub":"1234567890","name":"John Doe","iat":1516239022} 발급 시각 2018-01-18 01:30:22 UTC, 만료 시각 없음
exp가 없어서 만료 판정이 나오지 않습니다. 서명 조각 SflKx...는 그대로 표시될 뿐 맞는지 확인되지 않습니다.
alg: none 토큰
- 입력
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTc5MDAwMDAwMH0.- 결과
헤더 {"alg":"none","typ":"JWT"} 페이로드 {"sub":"admin","exp":1790000000} 서명: 빈 문자열, 만료 시각 2026-09-21 14:13:20 UTC
끝이 점으로 끝나고 서명이 비어 있습니다. 디코더는 정상적으로 읽어 내지만, 운영 서버가 이런 토큰을 받아들인다면 누구나 sub를 admin으로 적어 로그인할 수 있다는 뜻입니다.
사용 방법
- Authorization: Bearer 뒤의 토큰 문자열만 붙여 넣습니다.
- 헤더의 alg와 typ, 페이로드의 클레임을 확인합니다.
- exp·iat·nbf가 사람이 읽는 시각으로 표시되니, 만료·nbf 상태를 PC 시계 기준으로 봅니다. 서버와 시계가 어긋난다면 시계 오차 허용(초)을 넣습니다.
자주 묻는 질문
여기에 운영 토큰을 붙여 넣어도 되나요?
디코딩은 브라우저 안에서 끝나고 토큰을 어디로 보내지 않습니다. 다만 유효한 액세스 토큰은 그 자체로 로그인 수단이므로, 메신저·이슈 트래커에 공유하는 것은 피하세요.
서명이 맞는지 확인할 수 있나요?
이 도구로는 안 됩니다. 비밀키나 공개키로 검증하는 것은 서버 측 라이브러리가 해야 할 일이고, 그때 허용할 알고리즘을 명시해야 합니다.
토큰이 만료로 나오는데 서버는 받아 줍니다.
서버가 leeway를 두었거나, 여러분 PC 시계가 앞서 있을 수 있습니다. 서버와 같은 값을 시계 오차 허용(초)에 넣으면 서버와 같은 기준으로 판정합니다. 반대로 서버가 거부하는데 여기서는 유효하다면 서버 시계를 의심해 보세요.
세그먼트 수 오류가 납니다.
JWT는 점이 정확히 두 개입니다. Bearer 접두어나 따옴표가 같이 붙었는지, 암호화된 JWE(점 네 개)인지 확인하세요.
출처
기준일: