JWT 서명 및 검증
JWT는 세 개의 base64url 세그먼트 — 헤더, 페이로드, 서명 — 으로 이루어져 있으며, 앞의 두 개는 토큰을 가진 누구나 읽을 수 있습니다. 토큰을 디코딩하면 무엇을 주장하는지 알 수 있고, 세 번째 세그먼트만이 그것을 믿어야 하는지를 알려줍니다. 이 글은 그 세 번째 세그먼트에 대한 것입니다. 서명이 어떻게 만들어지고, 누가 만들 수 있으며, 검증자가 어떻게 확인하고, "검증됨"이 "신뢰할 수 있음"을 의미하지 않게 되는 지점이 정확히 어디인지 다룹니다. 모든 단계를 JWT 서명자 및 검증기에서 직접 실행해 볼 수 있으며, 브라우저에서 로컬로 서명하고 검증합니다.
디코딩은 검증이 아닙니다
JWT에 대한 가장 흔한 오해는 가장 위험한 오해이기도 합니다. 토큰을 읽는 것이 토큰을 확인하는 것과 같다는 생각입니다. 그렇지 않습니다. 헤더와 페이로드는 base64url입니다. 암호화가 아니라 인코딩입니다. 그래서 토큰을 가로채거나, 로그에서 찾거나, 브라우저의 로컬 스토리지에서 복사한 사람은 키 없이도 몇 초 만에 디코딩할 수 있습니다. 그 안의 클레임(누구를 위한 것인지, 언제 만료되는지, 어떤 역할을 주장하는지)은 주장일 뿐이며, 주장은 값싸게 만들 수 있습니다.
서명이 존재하는 이유는 바로 클레임이 값싸기 때문입니다. 서명은 헤더와 페이로드, 그리고 발급자만 가진 키 사이의 암호학적 결합입니다. 페이로드의 문자 하나만 바꿔도 서명이 맞지 않게 됩니다. 검증은 그 수학을 다시 수행하는 행위입니다. 받은 헤더와 페이로드에 대해 발급자의 키로 서명을 다시 계산하고 비교하는 것입니다. 일치하면 토큰은 발급자가 서명한 그대로입니다. 일치하지 않으면 무언가가 바뀌었고, 클레임이 아무리 그럴듯해 보여도 토큰은 무가치합니다.
대칭 대 비대칭: 누가 토큰을 만들 수 있는가
HS256에서는 서명하는 비밀키와 검증하는 비밀키가 같습니다. 그래서 HMAC은 빠르고 단순하며, 하나의 서비스 안에서는 충분히 적절합니다. API가 자신의 토큰에 서명하고 스스로 확인하며, 비밀키는 건물을 떠나지 않습니다. 두 번째 당사자가 검증해야 하는 순간 모델이 깨집니다. 비밀키를 건네는 것은 토큰을 만들 힘을 건네는 것입니다. 검증과 서명이 같은 연산이기 때문입니다. 유출된 HMAC 비밀키는 검증 문제가 아니라 위조 문제입니다.
비대칭 알고리즘은 두 힘을 나눕니다. RS256, ES256, EdDSA는 개인 키로 서명하고 공개 키로 검증하므로, 발급자는 공개 키를 JWKS로 게시할 수 있고 전 세계의 모든 검증자는 위조할 힘을 전혀 갖지 않은 채 토큰을 확인할 수 있습니다. 이 분리가 JWT를 서비스 간에 동작하게 합니다. API는 ID 공급자와 비밀키를 공유해서가 아니라, 그 공급자가 게시한 키를 신뢰하기 때문에 그 공급자의 토큰을 신뢰합니다. 대가는 복잡성입니다. 키 형식, 교체, kid 매칭. 이 사이트의 서명자 및 검증기가 바로 그것을 구체적으로 보여주기 위해 존재합니다.
알고리즘 혼동 공격
고전적인 JWT 공격에는 깨진 암호학이 필요 없습니다. 토큰이 자신의 알고리즘을 지정하도록 믿는 검증자가 필요할 뿐입니다. 공격자는 합법적인 RS256 토큰을 가져옵니다. 그 공개 키는(공개 키이므로) 게시되어 있습니다. 그리고 같은 페이로드를 HS256으로 다시 서명하는데, 공개 키 자체를 HMAC 비밀키로 사용합니다. 헤더의 alg를 읽고 그에 따라 검증 방식을 고르는 검증자는 이제 공개 키를 비밀키로 삼아 HMAC으로 위조된 토큰을 "검증"하게 됩니다. 그런데 공개 키는 공개되어 있습니다.
방어는 권고가 아니라 구조입니다. 검증자는 받아들일 알고리즘을 고정해야 하며, 키 계열은 알고리즘 계열과 일치해야 합니다. RSA 키는 HMAC 토큰을 검증할 수 없고, 비밀키는 RSA 토큰을 검증할 수 없습니다. 계열을 넘나들며 키를 재해석하는 검증자는 암호학이 아무리 정확해도 설계부터 깨져 있습니다. 그래서 이 사이트의 검증기는 계열이 다른 키를 검사를 시도하는 대신 명시적 경고와 함께 거부합니다. 그 거부가 바로 기능입니다.
실무에서의 JWKS와 kid 교체
ID 공급자는 하나의 영원한 키를 나눠주지 않습니다. 키 집합 — 보통 /.well-known/jwks.json에 있는 JWKS — 을 게시하고, 새 키를 추가한 뒤 유예 기간이 지나면 오래된 키를 제거하는 방식으로 키를 교체합니다. 각 토큰의 헤더에는 서명한 키를 지정하는 kid가 들어 있어, 검증자는 집합을 가져와 일치하는 키를 고르고 검증합니다. 오래된 키로 서명된 토큰은 그 키가 제거될 때까지 계속 동작하므로, 교체가 일제 전환이 아니라 안전하게 이루어집니다.
운영상의 세부 사항에서 문제가 생깁니다. 검증자는 JWKS를 캐시해야 합니다(요청마다 가져오는 것은 스스로 만드는 장애입니다). kid를 알 수 없는 경우 — 보통 캐시가 아직 보지 못한 교체입니다. 실패하기 전에 다시 가져와야 합니다 — 를 처리해야 하며, 신뢰할 수 없는 URL에서 온 키 집합을 절대 받아들여서는 안 됩니다. 집합은 가져온 채널만큼만 신뢰할 수 있습니다. 그래서 JWKS 엔드포인트는 HTTPS 뒤에 있으며, 그래서 이곳의 JWKS 빌더는 각 kid를 키 자체의 RFC 7638 지문으로 도출해, ID를 키와 독립적으로 속일 수 없게 합니다.
exp, nbf, iat — 그리고 시계 문제
서명은 토큰이 서명되었다는 것을 증명합니다. 아직 유효한지는 아무것도 말해주지 않습니다. 그건 시간 클레임의 일입니다. exp(언제 유효하지 않게 되는지), nbf(언제부터 유효해지는지), iat(언제 발급되었는지). 서명만 확인하고 시계를 건너뛰는 검증자는 토큰을 영원히 받아들입니다. 일 년 전에 도난당한 토큰까지 포함해서 말입니다. 짧은 수명이 실용적인 답입니다. 액세스 토큰은 분 단위, 리프레시 토큰이 장기 신뢰를 지니고, 폐기는 단순히 재발급하지 않는 것으로 처리합니다.
시계는 서로 어긋나므로 검증자는 약간의 여유를 둡니다. 보통 exp와 nbf에 1~2분의 허용 오차입니다. 그러나 여유는 오차에 대한 관용이지 만료를 무시할 면허가 아닙니다. 시계가 몇 분 어긋난 기기는 자신의 서버가 거부하는 토큰을 만들어내고, iat이 미래인 토큰은 반올림 오차가 아니라 위조 신호입니다. 같은 시계 규율은 도구에도 적용됩니다. 검증기는 눈으로 확인할 수 있는 시계에 대해 만료를 확인하고, 토큰이 만료되었을 때 그저 "무효"라고만 하지 않고 솔직하게 밝힙니다.
"검증됨"이 증명하는 것과 증명하지 못하는 것
검증된 토큰은 세 가지만 증명하고 그 이상은 증명하지 않습니다. 헤더와 페이로드가 서명된 그대로라는 것, 서명자가 키를 가지고 있었다는 것, 토큰이 유효 기간 안에 있다는 것. 발급자가 생각하는 그 사람이라는 것은 증명하지 않습니다. 그건 키가 어디에서 왔는지에 달려 있으며, 공격자가 통제하는 URL에서 가져온 키는 공격자의 토큰을 완벽하게 검증합니다. 클레임이 사실이라는 것도 증명하지 않습니다. 서명은 진술을 인증할 뿐, 그 뒤의 사실을 인증하지 않습니다. 그리고 토큰이 도난당하지 않았다는 것도 증명하지 않습니다. 누구든 제시하는 유효한 토큰은 받아들여지므로, 토큰은 수명이 짧고 범위가 좁습니다.
그래서 신뢰 결정은 토큰 밖에 있습니다. API가 토큰을 신뢰하는 이유는 HTTPS를 통해 ID 공급자의 잘 알려진 URL에서 키를 가져왔고, iss가 구성한 공급자와 일치하며, aud가 서비스하는 서비스와 일치하기 때문입니다. 검증은 첫 번째 검사가 아니라 마지막 검사입니다. 키 출처, 발급자, 대상, 그다음 서명, 그다음 시간. 그 맥락 없이 "VALID"라고 보고하는 도구는 수학에 대해서는 진실을 말하고 신뢰에 대해서는 아무것도 말하지 않습니다. 그래서 이 사이트의 검증기는 판결 옆에 그 사실을 페이지에 밝힙니다.
자주 묻는 질문
키 없이 토큰을 검증할 수 있나요?
아니요. 검증에는 발급자의 공개 키가 필요합니다(HMAC의 경우 공유 비밀키). 디코딩에는 키가 필요 없습니다. 바로 그 혼동을 피해야 합니다. 디코딩된 토큰은 읽을 수 있을 뿐 검증된 것이 아닙니다. 발급자가 JWKS를 게시한다면 잘 알려진 URL에서 가져와 토큰의 kid와 대조하세요. 이 사이트의 검증기가 대신 해 줍니다.
제 RS256 토큰이 비밀키로 실패하는 이유는 무엇인가요?
비밀키와 RSA 키는 다른 계열이며, 제대로 된 검증자는 둘을 섞는 것을 거부합니다. RS256 토큰은 발급자의 RSA 공개 키 — PEM, JWK 또는 JWKS — 로 검증하며, 텍스트 비밀키로는 절대 검증하지 않습니다. 비밀키를 쓰는 것은 알고리즘 혼동 공격이 됩니다. 공개 키는 공개되어 있으므로 HMAC 검증이 이를 비밀키로 받아들인다면 누구나 토큰을 위조할 수 있습니다.
JWT는 얼마나 오래 살아야 하나요?
액세스 토큰은 며칠이 아니라 몇 분입니다. 5~15분이 일반적인 범위이며, 리프레시 토큰이 더 긴 세션을 이어갑니다. 짧은 수명은 도난당한 토큰의 피해를 만료 전 창으로 제한하고, 폐기는 차단 목록을 유지하는 대신 재발급하지 않는 문제가 됩니다. 몇 주씩 사는 토큰은 도구만 더 나쁜 세션 쿠키입니다.