모든 아티클

JWT: 토큰 구조와 JWT를 "디코딩"한다는 것의 의미

JWT(JSON Web Token)는 서명된 데이터를 전송하기 위한 간결한 형식으로, 대개 인증된 사용자에 관한 정보를 담습니다. 토큰은 점으로 구분된 세 부분으로 구성됩니다: header.payload.signature. 한국 기업 환경에서는 Spring Boot 기반 백엔드에서 JWT 인증을 구현하는 경우가 특히 많고, 네이버·카카오 등 소셜 로그인과 결합해 쓰이는 경우도 흔합니다.

한글 claim은 완성형 음절 단위로 인코딩된다

{"name": "김민준", "city": "서울"} 같은 payload도 다른 JSON과 동일하게 Base64URL로 인코딩됩니다 — 이 과정은 UTF-8 바이트 단위로 동작할 뿐, 한글인지 여부와는 무관합니다. 실무적으로 중요한 점은 한글 완성형 음절 하나가 UTF-8에서 3바이트를 차지한다는 것으로, 한글 이름과 주소가 들어간 payload는 같은 의미의 영문 payload보다 인코딩 후 눈에 띄게 길어집니다.

토큰의 세 부분

  • Header — 토큰 유형과 서명 알고리즘(예: HS256 또는 RS256)을 담은 JSON으로, Base64URL로 인코딩됩니다.
  • Payload — 사용자 데이터, 발급 시각, 만료 시각 등 "claim"을 담은 JSON으로, 이 역시 Base64URL로 인코딩됩니다.
  • Signature — 비밀 키 또는 개인 키를 사용해 header와 payload로 계산한 서명으로, 토큰이 변조되지 않았음을 확인합니다.

일반 Base64가 아닌 Base64URL

JWT는 URL에 안전한 알파벳을 사용하는 Base64 변형을 씁니다: +/ 문자는 -_로 대체되고, 패딩 =는 보통 생략됩니다. 이 덕분에 추가 인코딩 없이 토큰을 URL이나 헤더에 바로 넣을 수 있습니다.

중요한 차이: 디코딩 ≠ 검증

header와 payload는 단지 Base64URL일 뿐입니다 — 누구나 아무 키 없이 이를 디코딩하고 내용을 읽을 수 있습니다. 토큰의 내용을 신뢰할 수 있는 것은 해당 키로 서명을 검증한 이후뿐이며, 이 검증은 토큰 내부를 그저 들여다보는 클라이언트가 아니라 서버가 수행합니다.

위험한 공격: 알고리즘을 "none"으로 바꿔치기

JWT 명세는 none 알고리즘 — 즉 서명 없는 토큰 — 을 허용합니다. 백엔드가 고정된, 미리 알고 있는 알고리즘으로 서명을 검증하는 대신 토큰 헤더의 alg 필드를 순진하게 신뢰한다면, 공격자는 algnone으로 바꿔 서명을 제거할 수 있습니다. 신뢰할 수 있는 JWT 라이브러리는 검증 시 기대하는 알고리즘을 명시적으로 지정하도록 요구합니다.

주민등록번호를 claim에 넣지 말아야 하는 이유

header와 payload는 키 없이도 누구나 디코딩할 수 있으므로, 주민등록번호처럼 민감한 정보를 claim에 그대로 담으면 서명이 아무리 정상적이어도 토큰을 가로챈 사람이 그 값을 평문으로 읽을 수 있게 됩니다. 국내 서비스에서는 개인정보보호법 준수를 위해 claim에는 내부 사용자 ID만 담고, 실제 개인정보는 서버 쪽 조회로 분리하는 방식이 권장됩니다.

도구 사용해보기