보안 · 2026년 8월 28일

남의 서버에 토큰을 업로드하지 않고 JWT를 디코딩하는 방법

대부분의 온라인 JWT 디코더는 토큰을 제3자에게 전송합니다. 이것이 중요한 이유, 그리고 로컬에서 동일한 편리함으로 JWT를 검사하는 방법.

JWT는 어디에나 있다. 웹 앱에 로그인할 때마다 API가 JSON Web Token을 발급하고, 이 토큰은 페이로드에 신원, 권한, 그리고 종종 몇 가지 개인정보를 담고 있다. 형식 자체는 10년간 안정적이었다가, 2024-2025년에 활발한 활동이 있었다 — RFC 8725 (JWT 모범 현재 관행)가 새 사양에 대한 필수 요건이 되었고, “JWT는 나쁘다” 토론이 세션 크기 문제로 다시 촉발되었으며, 여러 대형 제공자가 브라우저 사용을 위해 불투명 토큰으로 전환했다. 결과: 그 어느 때보다 많은 개발자들이 토큰을 수동으로 검사하고 있다. 무언가 잘못될 때 — 세션이 잘못된 시간에 만료되거나, 유효하다고 생각했던 토큰을 API가 거부하거나, 팀원이 “JWT에 뭐가 들어있지?“라고 물을 때 — 대부분의 개발자가 하는 첫 번째 일은 토큰을 온라인 디코더에 붙여넣는 것이다.

문제: 대부분의 디코더는 토큰을 서버로 보낸다. 토큰 자체는 비밀이 아니다(단순히 base64로 인코딩된 JSON이지만), 하지만 페이로드의 내용물은 종종 비밀이다: 사용자 ID, 이메일, 때로는 세션 ID, 때로는 권한 목록. 서비스가 다른 내부 서비스와 통신하기 위해 JWT를 사용하는 경우, 페이로드에 내부 서비스 이름이나 기능 플래그가 포함될 수도 있다.

서드파티 디코더에 붙여넣은 토큰은 작은 정보 유출이다. 대부분의 경우 중요하지 않다. 하지만 원칙은 중요하다: 당신은 다른 사람의 인프라를 신뢰하지 않고도 토큰을 검사할 수 있어야 한다.

JWT의 실제 모습

JWT는 점으로 구분된 세 개의 base64url 인코딩 문자열이다:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBbGljZSIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

세 부분은 헤더, 페이로드, 서명이다. 처음 두 개를 디코딩하면 JSON이 나온다:

// 헤더
{
  "alg": "HS256",
  "typ": "JWT"
}
// 페이로드
{
  "sub": "12345",
  "name": "Alice",
  "iat": 1516239022
}

서명은 비밀을 아는 사람이 발급했음을 증명하는 암호화 해시이다. 누구나 페이로드를 디코딩할 수 있다 — 그것이 보안 경계가 아니다. 보안 경계는 서명을 검증할 수 있는지 여부이다.

검증과 디코딩의 차이

여기서 많은 개발자가 잘못된다. 디코딩은 검증이 아니다. 디코더는 JSON을 base64 디코딩하여 읽을 수 있게 할 뿐이다. 누구나 그렇게 할 수 있고, 그것이 토큰이 진정한지 증명하지 않는다. 검증은 알려진 비밀에 대해 서명을 확인하여 토큰이 변조되지 않았음을 확인하는 행위이다.

  • 토큰 내용을 디버깅하려면 (어떤 클레임이 있는지, 언제 만료되는지), 디코딩이면 충분하다.
  • 사용자를 인증하려면, 검증이 필요하고, 신뢰할 수 있는 서버에서 이루어져야 한다.

대부분의 온라인 디코더 도구는 디코딩만 한다. 헤더와 페이로드를 표시하지만 비밀을 요청하지 않는다 — 검증하는 것이 아니라 토큰에 무엇이 있는지 보여줄 뿐이기 때문이다.

로컬에서 디코딩하는 방법

이것을 위해 서드파티 도구가 필요 없다. JWT는 세 개의 base64url 문자열일 뿐이다. 터미널을 연다:

echo 'eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBbGljZSIsImlhdCI6MTUxNjIzOTAyMn0' | tr '_-' '/+' | base64 -d

이것이 페이로드이다. 헤더는 첫 번째 세그먼트이고, 같은 트릭이다.

tr '_-' '/+' 동작을 암기하고 싶지 않다면, DevSpeedTools의 JWT 디코더가 브라우저에서 같은 작업을 한다. 네트워크 요청 없음, 업로드 없음, 로그 없음. DevTools → Network를 열고, 토큰을 붙여넣고, 아무것도 기계를 떠나지 않는지 스스로 확인한다.

실제로 검증이 필요한 경우

인증 문제를 디버깅하고 토큰이 유효한지 (포함된 내용뿐만 아니라) 알아야 하는 경우, 서명을 검증해야 한다. 검증은 인증 서버가 사용하는 것과 동일한 비밀 또는 공개 키로 이루어져야 한다. 이것은 당신이 통제하는 인프라에서 이루어져야 한다는 것을 의미한다.

비밀을 웹사이트에 붙여넣는 것과 관련 없는 검증 옵션 몇 가지:

  • Node.js에서: jsonwebtoken.verify(token, process.env.JWT_SECRET).
  • Python에서: jwt.decode(token, key, algorithms=['HS256']).
  • Go에서: token, err := jwt.Parse(tokenString, keyFunc).
  • CLI: jwt-cli (Rust) 또는 한 줄 명령어로 pyjwt.

어떤 이유로든 온라인 검증기가 필요한 경우, 브라우저에서 실행된다고 명시적으로 말하는 것을 찾는다(소스가 보통 보인다). 정직한 도구는 그렇게 말한다. 나머지는 토큰을 서버로 업로드한다고 가정해야 한다.

“잘못된 서명” 오류

개발자가 디코더에 도달하는 일반적인 이유는 invalid signature 오류이다. 이 메시지는 두 가지를 하고 있다:

  1. 토큰이 검증에 사용하는 것과 다른 비밀로 서명되었음을 알려준다.
  2. 토큰이 변조되었을 수 있음을 알려준다.

또는 — 그리고 이것이 대부분의 사람들이 놓치는 것이다 — 토큰이 만료되었을 수 있다. 페이로드의 exp 클레임을 확인한다. 현재 Unix 타임스탬프가 exp를 지나면 토큰은 유효하지 않다. 일부 라이브러리는 만료된 토큰에 invalid signature를 발생시키는데, 이는 오해의 소지가 있다.

JWT 디코더는 exp 값을 나머지 페이로드와 함께 표시하여 만료된 토큰을 한눈에 확인할 수 있게 한다. nbf (not before), iat (issued at), aud (audience)도 마찬가지이다 — 모두 디버깅에 유용하다.

요약

JWT 디코더는 유용하다. 또한 개발자들이 프로덕션 토큰을 붙여넣는 데 느긋해지는 곳이기도 하다. 더 안전한 기본값:

  • 빠른 검사를 위해 로컬 또는 브라우저 내 디코더를 사용한다. 업로드 없음, 유출 없음.
  • 검증을 위해 CLI 또는 자체 코드를 사용한다. 절대 비밀을 웹사이트에 붙여넣지 마라.
  • 먼저 표준 클레임을 확인한다 — exp 만료가 대부분의 “토큰이 갑자기 작동하지 않아” 문제의 원인이다.

토큰은 비밀이 아니다. 토큰의 내용물은 종종 비밀이다. 그에 맞게 대우하라.