디코딩은 검증이 아닙니다
JSON 웹 토큰은 암호화된 것처럼 보이지만 아닙니다. 점으로 이어진 세 개의 base64url 조각, 헤더, 페이로드, 서명, 이며, 앞의 둘은 토큰을 가진 누구나 그대로 읽을 수 있습니다. 서명은 내용을 감추지 않습니다. 서명 이후에 변조되지 않았음을 증명할 뿐입니다.
이 디코더는 토큰을 분해해 헤더와 페이로드를 정렬된 JSON으로 보여 주므로, 토큰이 실제로 무엇을 주장하는지 확인할 수 있습니다.
토큰 디코딩하는 방법
점 두 개를 포함해 토큰 전체를 붙여넣으세요. 입력하는 즉시 디코딩됩니다.
- 토큰을 붙여넣으세요. 세 조각을 나누는 점이 정확히 두 개여야 합니다.
- 헤더를 확인하세요. `alg`에 서명 알고리즘이, 흔히 `kid`에 키 식별자가 들어 있습니다.
- 페이로드를 확인하세요. 클레임이 있는 곳으로 `sub`, `iss`, `aud`, `exp`, `iat`와 발급 애플리케이션이 추가한 항목들이 들어 있습니다.
- `exp`를 현재 시각과 대조하세요. 밀리초가 아니라 초 단위 유닉스 타임스탬프이며, 1000배 착오의 흔한 원인입니다.
- 서명 조각은 표시되지만 검사되지 않는다는 점을 유념하세요. 검증에는 비밀키나 공개키가 필요하며 서버에서 할 일입니다.
- 아래에 설명한 이유로, 실제 토큰보다 테스트용 토큰을 쓰세요.
각 조각에 들어 있는 것
헤더는 알고리즘을 지정하는 작은 JSON 객체입니다. 공유 비밀키에는 `HS256`, 공개/개인 키 쌍에는 `RS256`이 흔하며, `typ`과 검증자가 어느 키를 쓸지 알려 주는 `kid`가 함께 오기도 합니다. 페이로드에는 `iss`, `sub`, `aud`, `exp`, `nbf`, `iat` 같은 등록 클레임과 애플리케이션이 정의한 커스텀 클레임이 들어갑니다.
서명은 인코딩된 헤더와 페이로드를 함께 대상으로 계산됩니다. 둘 중 어느 한 글자만 바뀌어도 서명이 맞지 않게 되고, 이것이 토큰을 변조 감지 가능하게 만드는 원리입니다. 표현에 주의하세요. 변조 감지이지 기밀 유지가 아닙니다. 토큰을 가로챈 누구나 모든 클레임을 읽을 수 있으므로, 토큰에는 비밀번호나 카드 번호처럼 노출되면 곤란한 것을 절대 담아서는 안 됩니다.
표준 등록 클레임
JWT 명세가 정의한 클레임과 각각의 용도입니다.
| 클레임 | 이름 | 의미 |
|---|---|---|
| iss | Issuer | 토큰을 발급한 주체 |
| sub | Subject | 토큰이 가리키는 대상, 보통 사용자 식별자 |
| aud | Audience | 토큰이 향하는 수신자 |
| exp | Expiration | 이후로는 거부해야 하는 유닉스 초 |
| nbf | Not before | 이전에는 아직 유효하지 않은 유닉스 초 |
| iat | Issued at | 생성된 시각, 유닉스 초 |
| jti | JWT ID | 고유 식별자, 재전송 방지에 사용 |
세 개의 시각 클레임 모두 밀리초가 아니라 유닉스 초입니다. 만료가 1970년이나 서기 56000년으로 보인다면 거의 언제나 토큰이 망가진 것이 아니라 단위가 어긋난 것입니다.
여기서 서명을 검사하지 않는 이유
서명 검증에는 키가 필요합니다. HMAC 계열이면 공유 비밀키, RSA나 ECDSA면 발급자의 공개키입니다. 운영 비밀키를 웹 페이지에 붙여넣는 것은 그것으로 해결하려던 문제보다 나쁜 선택이므로, 이 도구는 의도적으로 디코딩만 합니다. 검증되지 않은 토큰을 신뢰하는 것이 가장 흔한 JWT 취약점이며, 검증은 `exp`, `aud`, 알고리즘까지 함께 확인하는 라이브러리와 함께 백엔드에서 이루어져야 합니다.
마지막 항목은 들리는 것보다 중요합니다. 고전적인 JWT 공격은 알고리즘 혼동입니다. 공격자가 `alg`를 `none`으로 바꾸거나 RS256을 HS256으로 바꿔치기해, 공개키가 HMAC 비밀키로 쓰이게 만드는 것입니다. 검증하는 쪽은 토큰이 선언한 값을 믿지 말고 기대하는 알고리즘을 고정해야 합니다. 디코딩은 전적으로 브라우저에서 이루어지고 아무것도 전송되지 않지만, 브라우저 탭에 열린 유효한 토큰은 여전히 살아 있는 자격 증명이므로 가능하면 만료된 토큰이나 테스트 토큰을 쓰세요.