🪪 JWT 토큰 구조 이해하고 디코딩하는 방법

Header·Payload·Signature 3부분 구조 설명

로그인 API가 응답으로 내려주는 eyJhbGci... 형태의 긴 문자열, 다들 한 번쯤 본 적 있을 겁니다. 이 글에서는 JWT(JSON Web Token)가 어떤 구조로 이루어져 있는지, 왜 "암호화된 것이 아닌지", 그리고 서명 검증이 실제로 무엇을 보장하는지 짚어봅니다.

JWT는 점(.)으로 구분된 3부분

JWT는 header.payload.signature 형태로, 마침표 두 개를 기준으로 세 부분이 이어붙은 문자열입니다. 각 부분은 JSON을 Base64URL로 인코딩한 값입니다. Base64URL은 표준 Base64와 거의 같지만, URL에 그대로 써도 안전하도록 +/를 각각 -_로 바꾸고 끝의 = 패딩을 생략한 변형입니다. 예를 들어 다음과 같은 토큰이 있다면:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6ImRldmVsb3BrIiwiZXhwIjoxNzUyMDAwMDAwfQ.4f3c2a1e...

첫 번째 부분을 디코딩하면 {"alg":"HS256","typ":"JWT"}, 두 번째 부분을 디코딩하면 {"sub":"1234","name":"developk","exp":1752000000}가 나옵니다. 세 번째 부분은 사람이 읽을 수 있는 JSON이 아니라, 앞의 두 부분을 특정 키로 서명한 바이트값입니다.

Header와 Payload에 담기는 정보

Header에는 보통 두 필드만 들어갑니다. alg는 서명에 사용한 알고리즘(예: HS256, RS256), typ는 토큰 타입(대개 JWT)을 나타냅니다. Payload에는 "클레임(claim)"이라 불리는 데이터가 들어가는데, 표준으로 정해진 것도 있고 서비스가 임의로 추가하는 것도 있습니다. 자주 보이는 표준 클레임은 다음과 같습니다.

여기에 role, email처럼 서비스마다 필요한 커스텀 클레임을 자유롭게 추가할 수 있습니다.

왜 암호화가 아닌가 — Base64는 누구나 풀 수 있다

가장 많이 오해하는 부분입니다. Base64URL 인코딩은 데이터를 안전한 텍스트 형태로 "표현"하는 방식일 뿐, 숨기는 방식이 아닙니다. 키도 필요 없이 누구나 즉시 원래 JSON으로 되돌릴 수 있습니다. 따라서 Payload에 비밀번호, 카드번호, 세션 시크릿 같은 민감한 정보를 넣으면 안 됩니다. 토큰을 가진 사람(또는 네트워크를 가로챈 사람)이라면 누구나 그 내용을 읽을 수 있기 때문입니다.

그렇다면 JWT는 무엇을 보장할까요? 바로 무결성(integrity)입니다. Signature 부분이 Header와 Payload가 발급 이후 변조되지 않았음을 증명합니다. 즉 "이 내용을 볼 수 없게 감춘다"가 아니라 "이 내용을 서버 몰래 바꿀 수 없게 막는다"가 JWT 서명의 역할입니다.

HS256 서명 검증은 어떻게 동작하나

HS256(HMAC-SHA256)은 서버와 클라이언트(또는 서버끼리)가 같은 비밀키(secret)를 공유하는 대칭키 방식입니다. 토큰을 발급할 때 서버는 base64url(header) + "." + base64url(payload) 문자열을 비밀키로 HMAC-SHA256 서명해 세 번째 부분으로 붙입니다. 검증할 때는 받은 토큰의 Header·Payload를 같은 방식으로 다시 서명해보고, 그 결과가 토큰에 들어있는 Signature와 정확히 일치하는지 비교합니다. 비밀키를 모르는 사람은 Payload 내용을 바꾼 뒤 유효한 서명을 새로 만들어낼 수 없기 때문에, 서명이 일치한다는 것은 곧 "이 토큰이 비밀키를 가진 발급자로부터 나왔고, 발급 이후 변경되지 않았다"는 뜻이 됩니다. RS256처럼 공개키/개인키를 쓰는 비대칭 알고리즘도 있지만, 원리는 같습니다 — 서명 생성과 검증에 서로 다른 키를 쓴다는 점만 다릅니다.

흔한 실수

실무에서 자주 보이는 실수 몇 가지를 정리하면 다음과 같습니다.

토큰을 직접 붙여넣어 Header와 Payload 내용을 확인하고, HS256 시크릿이 있다면 서명까지 즉시 검증해보고 싶다면 아래 도구로 바로 확인해보세요.

표준 등록 클레임 (RFC 7519)

JWT Payload에 쓰이는 표준 클레임입니다. 전부 선택 사항이지만, 라이브러리 대부분이 exp·nbf를 자동 검증합니다.

클레임이름의미
issIssuer토큰 발급자
subSubject토큰 주체 (보통 사용자 ID)
audAudience토큰 수신 대상
expExpiration Time만료 시각 (유닉스 타임스탬프, 초)
nbfNot Before이 시각 이전에는 무효
iatIssued At발급 시각
jtiJWT ID토큰 고유 식별자 (재사용 방지)

주요 서명 알고리즘 (alg 헤더)

alg방식키 유형
HS256HMAC + SHA-256대칭키 (공유 비밀)
HS384 / HS512HMAC + SHA-384/512대칭키
RS256RSA + SHA-256비대칭키 (개인/공개키)
ES256ECDSA + SHA-256비대칭키 (타원곡선)
none서명 없음— (절대 허용 금지)

⚠️ alg: none은 서명을 생략하는 값으로, 서버가 이를 허용하면 누구나 위조 토큰을 만들 수 있습니다. 검증 시 허용 알고리즘을 반드시 화이트리스트로 고정하세요.

🪪 이 작업, 도구로 바로 해보세요

JWT 토큰 구조 이해하고 디코딩하는 방법 — Header·Payload·Signature 3부분 구조 설명

다른 가이드

🔁
도메인 IP 주소 확인하는 방법
도메인이 가리키는 실제 서버 IP를 찾는 법
🛰️
DNS 전파 확인하는 방법 (TTL이란?)
네임서버 변경 후 전파 여부와 TTL 개념 정리
📡
내 IP 주소 확인하는 가장 쉬운 방법
공인 IP·사설 IP 차이와 확인 방법
📐
CIDR 표기법과 서브넷 마스크 계산법
/24 같은 CIDR 표기를 서브넷 마스크로 바꾸는 법
🩺
SSL 인증서 만료일 확인하는 방법
브라우저 없이 인증서 만료일을 확인하는 법
🗝️
Apache Basic 인증(.htpasswd) 파일 만드는 법
APR1-MD5 해시로 .htpasswd 생성하는 법
🔑
안전한 비밀번호 만드는 기준과 생성 방법
길이·문자 조합·엔트로피로 보는 비밀번호 강도
🔐
Base64 인코딩이란? 언제 어떻게 쓰나
이메일 첨부·데이터 URI에 쓰이는 이유
📘
JSON Formatter로 API 응답 가독성 높이기
압축된 JSON을 보기 좋게 정리하는 법
🗓️
유닉스 타임스탬프 변환하는 방법
1970년 기준 초 단위 시간을 날짜로 바꾸는 법
🗃️
지저분한 SQL 쿼리 포맷팅하는 방법
한 줄 SQL을 절 단위로 정렬하는 법
🎨
HEX RGB HSL 색상 코드 변환하는 방법
세 가지 색상 표기법의 차이와 변환법
🔠
카멜케이스 스네이크케이스 등 표기법 정리
camelCase·snake_case·PascalCase 언제 쓰나
🆚
두 텍스트 파일 차이 비교하는 방법
코드·문서 버전 간 차이를 빠르게 찾는 법
🔤
Punycode란? 한글 도메인이 xn--으로 보이는 이유
국제화 도메인(IDN)을 ASCII로 안전하게 표현하는 인코딩 방식.
🔀
IPv4를 IPv6로 매핑하는 법
시스템 로그의 ::ffff:192.168.1.1 같은 표기, 정체와 계산법.
🕸️
텍스트에서 도메인만 뽑아내는 법
로그·이메일처럼 뒤섞인 텍스트에서 도메인만 정확히 골라내기.
🚪
URL 여러 개를 한 번에 여는 법
매일 확인하는 사이트 목록, 하나씩 클릭하지 말고 한 번에.
📮
이메일 주소 유효성 확인하는 법
형식 검사와 MX 레코드로 알 수 있는 것, 알 수 없는 것.
📋
HTTP 응답 헤더 확인하는 법
캐시 정책, 서버 정보, 압축 여부까지 헤더 하나로.
🛡️
보안 헤더 점검하는 법
HSTS·CSP·X-Frame-Options — 없으면 생기는 위험까지.
📇
Whois/RDAP로 도메인 등록 정보 조회하는 법
누가, 언제 등록했고 언제 만료되는지 확인하는 법.
📍
IP 주소로 대략적인 위치 확인하는 법
동작 원리와 정확도 한계, VPN 때문에 틀리는 이유까지.
🖼️
OGP 메타태그 확인하는 법
카톡·SNS 공유 카드가 이상하게 뜰 때 확인할 것.
📑
제목 태그(h1~h6) 구조 확인하는 법
SEO·접근성의 기본, 제목 계층 구조 점검하기.
🏷️
TITLE·META 태그 확인하는 법
검색결과에 어떻게 노출되는지 결정하는 핵심 태그들.
#️⃣
해시 함수란? MD5·SHA-256 차이
어떤 해시를 언제 써야 하는지, 되돌릴 수 없다는 게 무슨 뜻인지.
📱
QR코드 동작 원리
일부가 가려져도 인식되는 이유와 QR 피싱 주의사항.
🔍
SSL 인증서 항목 읽는 법
CN·SAN·발급기관·유효기간, 각각 뭘 의미하나.
📜
CSR이란?
SSL 인증서를 발급받으려면 왜 CSR부터 만들어야 하나.
♻️
PEM vs DER 인증서 포맷
왜 같은 인증서를 형식만 바꿔서 다시 써야 할 때가 있나.
🤝
인증서·개인키가 안 맞을 때
짝이 맞는지 확인하는 원리와 흔한 불일치 원인.
🪄
자체서명 SSL 인증서 쓰는 법
테스트·개발 환경에서만 쓰고 프로덕션엔 절대 쓰면 안 되는 이유.
🔑
HTTP Basic vs Bearer 인증
Authorization 헤더 값이 어떻게 만들어지는지, 언제 뭘 써야 하는지.
🎫
안전한 랜덤 토큰 만드는 기준
API 키·세션 시크릿, 얼마나 길어야 하고 어떤 형식이 맞나.
🔒
Jasypt로 설정값 암호화하기
application.properties의 ENC(...) 값, 어떻게 동작하나.
🧩
JSON 문자열 이스케이프란?
따옴표·줄바꿈이 든 텍스트를 JSON에 안전하게 넣는 법.
JS·CSS 포맷팅과 압축의 차이
코드 정리할 땐 포맷팅, 배포할 땐 압축 — 왜 반대인가.
✂️
코드 압축의 안전 범위
공백·주석 제거는 안전, 변수명 변경은 왜 위험할 수 있나.
🧽
HTML 들여쓰기가 중요한 이유
구조가 보여야 실수도 보인다.
🧹
XML 들여쓰기 정리가 필요한 상황
한 줄 XML에서 엘리먼트 중첩 구조 파악하기.
🔣
HTML 엔티티란?
<, >, & 문자를 화면에 그대로 보여주고 싶을 때.
📊
엑셀 표를 HTML TABLE로 바꾸는 법
복사-붙여넣기만으로 표 데이터를 HTML로.
⛓️
HTML에서 링크 한 번에 뽑는 법
href·src 속성값을 전부 골라내는 법.
🐘
PHP·MySQL 구문 검사기의 한계
괄호·따옴표 짝 검사로 알 수 있는 것, 알 수 없는 것.
🗄️
MySQL 쿼리 짝 검사가 유용할 때
긴 서브쿼리, 동적으로 조립한 쿼리에서 실수 찾기.
🔍
HTML5 검증에서 자주 걸리는 실수
태그 짝·중복 id·alt 누락, 각각 왜 문제인가.
✍️
비주얼 HTML 에디터, 언제 쓰나
위지윅 편집이 유리한 경우, 코드 직접 작성이 유리한 경우.
📝
Markdown 미리보기가 필요한 이유
코드로만 보면 놓치는 렌더링 결과.
🔗
URL 퍼센트 인코딩이란?
한글·공백이 %EC%95%88 같은 형태로 바뀌는 이유.
✏️
정규식으로 찾아 바꾸기
단순 검색으로 안 되는 패턴 치환, 정규식으로.
↕️
목록 정렬, 알아야 할 정렬 기준
가나다순과 숫자순은 왜 다르게 동작하나.
📧
텍스트에서 이메일만 뽑는 법
정규식 패턴과 흔히 발생하는 오탐 실수.
🔢
글자 수와 바이트 수, 왜 다른가
한글 한 글자가 3바이트인 이유.
🔤
전각·반각 문자란?
숫자처럼 보이는데 검색이 안 되는 이유.
🈚
유니코드 코드포인트란?
모든 문자가 가진 고유 번호, U+XXXX 표기 읽는 법.
🔟
텍스트를 숫자로 바꾸는 원리
10진수·16진수·2진수, 어떻게 같은 값을 다르게 표현하나.
🧾
UTF-8 바이트 인코딩 기초
왜 영어는 1바이트, 한글은 3바이트인가.
0️⃣
텍스트가 0과 1로 저장되는 원리
비트란 무엇이고 텍스트는 어떻게 비트가 되나.
🔡
아스키 아트는 어떻게 만들어지나
문자만으로 큰 글자 배너를 그리는 원리.