로그인 후 서버가 건네주는 서명된 토큰, 그것이 JWT입니다. 작동 원리와 안전하게 쓰기 위한 핵심을 살펴봅니다(공격 절차는 다루지 않습니다).
작동 원리(세 부분)
JWT는 header.payload.signature를 .로 이은 것입니다. header와 payload는 그저 base64url로 인코딩된 것이고(암호화 아님), 서명만 비밀(또는 공유) 키로 만들어집니다.
1. header
서명 알고리즘 같은 메타. 누구나 읽을 수 있음
2. payload
사용자 id, 만료 같은 클레임. 누구나 읽음 = 시크릿 금지
3. signature
키로 만들어짐. 변조 없음을 보장
핵심은 내용이 "그냥 읽힌다"는 점입니다. 그래서 payload는 비밀번호나 개인 데이터를 둘 곳이 아닙니다. JWT의 가치는, 서명을 검증함으로써 서버가 "내가 발급한 토큰이고 변조되지 않았다"를 확인할 수 있다는 데 있습니다.
안전하게 쓰기 위한 방어
항상 서명을 검증하고 alg를 고정(가장 중요)
매번 서버 측에서 서명을 검증하세요. 받아들이는 서명 알고리즘(alg)을 기대하는 값으로 고정하고, alg:none(서명 없음)을 거부하세요. 토큰 header에 적힌 alg를 무분별하게 신뢰하지 마세요.
JWT 모범 사례 RFC(RFC 8725)는 라이브러리가 호출하는 쪽이 지원 알고리즘 집합을 지정할 수 있게 해야 하고 그 밖의 알고리즘을 써서는 안 된다고 정합니다. 또한 JWT 안의 모든 암호 연산을 검증하고, 하나라도 실패하면 JWT 전체를 거부해야 한다고 말합니다.
alg:none뿐 아니라 알고리즘 바꿔치기도 막기
alg의 악용은 서명을 없애는 것만이 아닙니다. RFC 8725는 RS256을 HS256으로 바꾸면 일부 라이브러리가 RSA 공개키를 공유 비밀로 삼아 HMAC-SHA256으로 서명을 검증하려 한다고 설명합니다(알고리즘 혼동). 공개키는 이름 그대로 공개되어 있으므로, 이것이 통하면 공격자가 유효한 서명을 직접 만들 수 있습니다.
방어는 같습니다. header의 alg를 믿지 말고 서버가 기대하는 알고리즘을 고정하세요. 건네받은 키 정보로 검증 방식을 고르는 것이 아니라, 먼저 알고리즘을 정한 뒤 검증합니다.
누구를 위한 토큰인지, 언제까지인지 검증
서명이 유효하다는 것은 토큰이 진짜라는 뜻일 뿐입니다. 나를 위한 토큰이고 아직 유효한지는 별도의 확인입니다.
RFC 7519는 aud가 있을 때 처리하는 주체가 그 값에 자신을 포함하지 않으면 JWT를 거부해야 한다고, exp는 그 시각 이후에는 JWT를 받아들여서는 안 되는 만료 시각이라고 정합니다(nbf는 반대쪽 끝에서 같은 역할). RFC 8725는 사용된 키가 발급자의 것인지도 애플리케이션이 검증해야 한다고 덧붙입니다. 다른 서비스용으로 정당하게 발급된 토큰이 내 서비스에서 받아들여지는 것이 이 확인을 건너뛴 결과입니다.
payload에 시크릿을 넣지 않기
누구나 내용을 디코딩할 수 있다고 가정하세요. 시크릿 — 비밀번호, 개인 데이터, API 키 — 는 거기에 두지 마세요. 식별자나 권한처럼 보여도 치명적이지 않은 클레임만 포함하세요.
만료를 짧게 두고 폐기 방법을 마련
액세스 토큰을 짧은 수명으로 만드세요. 만료가 지나치게 길면 하나가 탈취됐을 때 피해 기간이 늘어납니다. 폐기가 필요하면, 짧은 수명의 액세스 토큰을 서버가 관리하는 리프레시/세션과 짝지으세요.
강한 키를 쓰고 탈취당하지 않는 곳에 보관
서명 키를 강하게 만들고, 재사용하지 말며, 서버 측 안전한 곳에 보관하세요. 토큰 자체도 보호하세요 — XSS로 탈취되지 않게 하고, 안전한 속성을 가진 쿠키에 담으세요(→ XSS를 통한 탈취에 주의).
본 사이트의 견해: JWT는 모든 용도에 쓰는 세션 방식이 아니다
JWT는 흔히 "로그인 상태를 유지"하는 데 쓰이지만, 약점이 있습니다: 폐기가 어렵습니다. 서명이 유효하고 만료되지 않았다면 서버는 기본적으로 그것을 신뢰하므로, 로그아웃이나 무효화를 즉시 반영하기가 까다롭습니다.
본 사이트의 입장은: JWT를 장수하는 세션 대용으로 쓰지 말 것. 짧은 수명의 액세스 토큰을 서버 측의, 폐기 가능한 리프레시/세션과 결합하면 JWT의 장점(저렴한 검증)과 손쉬운 폐기를 모두 얻습니다.
주의: "디코딩됐다"는 "유효하다"가 아니다
내용이 읽히기 때문에, 누구나 JWT를 JWT 디코더에 붙여 넣어 header와 payload를 들여다볼 수 있습니다. 하지만 디코딩(내용 읽기)과 검증(진위 확인)은 전혀 다릅니다. 디코딩할 수 있다는 것은 유효성에 대해 아무것도 보장하지 않습니다. 토큰이 진짜인지를 결정하는 것은 언제나 서버 측 서명 검증입니다. 디버깅을 위해 내용을 읽는 것은 편리하지만 — "읽을 수 있었다"를 "유효하다"로 착각하지 마세요.
JWT vs 서버 측 세션(쿠키)
JWT는 로그인 상태를 유지하는 한 가지 방법일 뿐이며, 서버 측 세션보다 잘 맞는 경우도 있고 덜 맞는 경우도 있습니다.
| 항목 | 서버 측 세션(쿠키) | JWT |
|---|---|---|
| 상태를 두는 곳 | 서버(DB나 메모리). 쿠키에는 ID만 | 토큰 안. 서버는 서명만 검증 |
| 로그아웃 / 정지 | 서버에서 지우면 바로 반영 | 폐기 방식을 따로 만들지 않으면 만료까지 받아들여짐 |
| 내용의 노출 | ID뿐이라 읽을 것이 없음 | 누구나 디코딩해 읽을 수 있음 |
| 잘 맞는 경우 | 하나의 도메인에서 동작하는 평범한 웹 앱 | 여러 서비스나 API가 같은 토큰을 검증 |
하나의 도메인 안에서 끝나는 웹 앱이라면 서버 측 세션으로 충분한 경우가 많습니다. JWT를 쓰기로 했다면, 로그인 시 새 토큰을 발급하는 것은 세션 고정 방어와 같은 원칙입니다.
실제 사례: 알고리즘을 확인하지 않은 라이브러리
알고리즘 바꿔치기는 이론만의 이야기가 아닙니다. 널리 쓰이는 Node.js용 JWT 라이브러리 jsonwebtoken은 4.2.1 이하에서 토큰 header에 적힌 알고리즘을 제대로 확인하지 않아 검증을 우회할 수 있었습니다(CVE-2015-9235, 4.2.2에서 수정). 공개키로 검증해야 할 토큰을 공유키(HS 계열) 알고리즘으로 서명해도 통과하는, 위에서 설명한 알고리즘 혼동 그대로였습니다.
교훈은 두 가지입니다.
- 라이브러리를 최신으로 유지한다. lockfile을 의존성 취약점 스캐너에 붙여 넣으면 의존성의 알려진 취약점을 확인할 수 있다
- 라이브러리의 기본값에 기대지 말고, 받아들이는 알고리즘을 자신의 코드에 명시한다
내 토큰을 확인하는 방법
자신의 서비스가 발급한 토큰을 JWT 디코더에 붙여 넣고 다음을 확인하세요. 디코딩은 브라우저 안에서 이루어지며 토큰은 서버로 전송되지 않습니다. 그래도 운영 중인 실제 토큰이라면 확인 후 로그아웃해 무효화해 두는 것이 좋습니다.
- header의
alg가 기대한 값인가(none이 아닌가) exp가 있고,iat(발급 시각)부터의 유효 기간이 용도에 맞는가aud와iss가 있고, 내 서비스를 가리키는가- payload에 이메일 주소, 전화번호, API 키처럼 보이면 곤란한 정보가 들어 있지 않은가
서버 측 검증은 개발 환경의 테스트로 확인합니다. 서명 한 글자를 바꾼 토큰, alg가 다른 토큰, 만료된 토큰, 다른 서비스용 토큰(aud가 다름)이 모두 거부되는지를 자동 테스트로 남겨 두세요.
출처(1차 자료)
- IETF RFC 8725, "JSON Web Token Best Current Practices" — datatracker.ietf.org(알고리즘 고정, 모든 암호 연산의 검증, 알고리즘 혼동, 키가 발급자의 것인지 확인)
- IETF RFC 7519, "JSON Web Token (JWT)" — datatracker.ietf.org(
exp,nbf,aud의 규범적 정의) - GitHub Advisory Database, "Verification Bypass in jsonwebtoken"(CVE-2015-9235) — github.com/advisories(4.2.1 이하 해당, 4.2.2에서 수정)
- OWASP HTML5 Security Cheat Sheet — cheatsheetseries.owasp.org(세션 식별자를 localStorage에 저장하지 않기)
다음으로 읽기
- 도구: JWT 디코더 / 인스펙터(내용 확인, alg:none이나 만료 점검)
- 설계: 비밀번호 재설정 설계의 결함(영문)(토큰을 발급하는 쪽의 흔한 실수)
- 용어집: XSS란(고전적 토큰 탈취 경로) / CSRF란 / 세션 고정이란 / 공개키 암호란
- 학습: MFA를 제대로 고르는 법