용어 사전

JWT(JSON Web Token)란? 서명된 토큰의 작동 원리와 안전하게 쓰는 법

JWT(JSON Web Token)는 변조를 감지할 수 있도록 서버가 서명한 토큰입니다. header/payload/signature의 작동 원리, 그리고 위험들: 서명 검증 건너뛰기, alg:none 허용, payload에 시크릿 넣기를 다룹니다.

게시 2026-06-12 업데이트 2026-10-06 7분 읽기
이 페이지에서

로그인 후 서버가 건네주는 서명된 토큰, 그것이 JWT입니다. 작동 원리와 안전하게 쓰기 위한 핵심을 살펴봅니다(공격 절차는 다루지 않습니다).

작동 원리(세 부분)

JWT는 header.payload.signature를 .로 이은 것입니다. header와 payload는 그저 base64url로 인코딩된 것이고(암호화 아님), 서명만 비밀(또는 공유) 키로 만들어집니다.

1. header

서명 알고리즘 같은 메타. 누구나 읽을 수 있음

2. payload

사용자 id, 만료 같은 클레임. 누구나 읽음 = 시크릿 금지

3. signature

키로 만들어짐. 변조 없음을 보장

JWT는 세 부분으로 이루어집니다. header와 payload는 '그냥 읽힘'이고, signature가 진위 보장을 제공합니다.

핵심은 내용이 "그냥 읽힌다"는 점입니다. 그래서 payload는 비밀번호나 개인 데이터를 둘 곳이 아닙니다. JWT의 가치는, 서명을 검증함으로써 서버가 "내가 발급한 토큰이고 변조되지 않았다"를 확인할 수 있다는 데 있습니다.

안전하게 쓰기 위한 방어

1

항상 서명을 검증하고 alg를 고정(가장 중요)

매번 서버 측에서 서명을 검증하세요. 받아들이는 서명 알고리즘(alg)을 기대하는 값으로 고정하고, alg:none(서명 없음)을 거부하세요. 토큰 header에 적힌 alg를 무분별하게 신뢰하지 마세요.

JWT 모범 사례 RFC(RFC 8725)는 라이브러리가 호출하는 쪽이 지원 알고리즘 집합을 지정할 수 있게 해야 하고 그 밖의 알고리즘을 써서는 안 된다고 정합니다. 또한 JWT 안의 모든 암호 연산을 검증하고, 하나라도 실패하면 JWT 전체를 거부해야 한다고 말합니다.

2

alg:none뿐 아니라 알고리즘 바꿔치기도 막기

alg의 악용은 서명을 없애는 것만이 아닙니다. RFC 8725는 RS256을 HS256으로 바꾸면 일부 라이브러리가 RSA 공개키를 공유 비밀로 삼아 HMAC-SHA256으로 서명을 검증하려 한다고 설명합니다(알고리즘 혼동). 공개키는 이름 그대로 공개되어 있으므로, 이것이 통하면 공격자가 유효한 서명을 직접 만들 수 있습니다.

방어는 같습니다. header의 alg를 믿지 말고 서버가 기대하는 알고리즘을 고정하세요. 건네받은 키 정보로 검증 방식을 고르는 것이 아니라, 먼저 알고리즘을 정한 뒤 검증합니다.

3

누구를 위한 토큰인지, 언제까지인지 검증

서명이 유효하다는 것은 토큰이 진짜라는 뜻일 뿐입니다. 나를 위한 토큰이고 아직 유효한지는 별도의 확인입니다.

RFC 7519는 aud가 있을 때 처리하는 주체가 그 값에 자신을 포함하지 않으면 JWT를 거부해야 한다고, exp는 그 시각 이후에는 JWT를 받아들여서는 안 되는 만료 시각이라고 정합니다(nbf는 반대쪽 끝에서 같은 역할). RFC 8725는 사용된 키가 발급자의 것인지도 애플리케이션이 검증해야 한다고 덧붙입니다. 다른 서비스용으로 정당하게 발급된 토큰이 내 서비스에서 받아들여지는 것이 이 확인을 건너뛴 결과입니다.

4

payload에 시크릿을 넣지 않기

누구나 내용을 디코딩할 수 있다고 가정하세요. 시크릿 — 비밀번호, 개인 데이터, API 키 — 는 거기에 두지 마세요. 식별자나 권한처럼 보여도 치명적이지 않은 클레임만 포함하세요.

5

만료를 짧게 두고 폐기 방법을 마련

액세스 토큰을 짧은 수명으로 만드세요. 만료가 지나치게 길면 하나가 탈취됐을 때 피해 기간이 늘어납니다. 폐기가 필요하면, 짧은 수명의 액세스 토큰을 서버가 관리하는 리프레시/세션과 짝지으세요.

6

강한 키를 쓰고 탈취당하지 않는 곳에 보관

서명 키를 강하게 만들고, 재사용하지 말며, 서버 측 안전한 곳에 보관하세요. 토큰 자체도 보호하세요 — 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 디코더에 붙여 넣고 다음을 확인하세요. 디코딩은 브라우저 안에서 이루어지며 토큰은 서버로 전송되지 않습니다. 그래도 운영 중인 실제 토큰이라면 확인 후 로그아웃해 무효화해 두는 것이 좋습니다.

  1. header의 alg가 기대한 값인가(none이 아닌가)
  2. exp가 있고, iat(발급 시각)부터의 유효 기간이 용도에 맞는가
  3. aud와 iss가 있고, 내 서비스를 가리키는가
  4. 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에 저장하지 않기)

다음으로 읽기

FAQ

QJWT의 내용은 암호화되어 있나요?
A

아니요. 표준 JWT의 header와 payload는 암호화가 아니라 base64url로 '인코딩'된 것이라, 토큰을 가진 누구나 내용을 읽을 수 있습니다. 그러니 비밀번호나 개인 데이터 같은 시크릿을 payload에 절대 넣지 마세요. JWT가 보호하는 것은 '내용의 기밀성'이 아니라 '변조되지 않았다는 것'(서명을 통한 무결성)입니다.

Q가장 위험한 JWT 설정은 무엇인가요?
A

서명을 검증하지 않거나, 'alg:none'(서명 없음)을 받아들이는 것입니다. 그것을 허용하면 공격자가 내용을 마음대로 고쳐 쓴 위조 토큰을 통과시킬 수 있습니다. 방어는 항상 서버 측에서 서명을 검증하고, 받아들이는 서명 알고리즘(alg)을 기대하는 값으로 고정하는 것입니다.

Q디코딩과 검증은 같은 것인가요?
A

아니요. 디코딩은 그저 내용을 읽는 것이고 누구나 할 수 있습니다(JWT 디코더로 확인 가능). 검증은 서버가 비밀/공개 키로 서명이 유효한지, 토큰이 만료되지 않았는지, 발급자/대상이 올바른지를 확인하는 것입니다. '디코딩됐다'가 '유효한 토큰이다'를 뜻하지 않으며 — 진위는 서버 측 검증이 결정합니다.

QJWT와 세션(쿠키) 중 무엇을 써야 하나요?
A

하나의 도메인에서 동작하는 평범한 웹 앱이라면 서버 측 세션과 쿠키로 충분한 경우가 많고, 로그아웃이나 계정 정지가 바로 반영됩니다. JWT는 여러 서비스나 API가 같은 토큰을 검증하는 경우, 또는 외부 ID 제공자(OAuth/OpenID Connect)에게서 토큰을 받는 경우에 맞습니다. JWT를 쓴다면 만료를 짧게 두고 서버가 폐기할 수 있는 방식과 함께 쓰세요.

QJWT를 localStorage에 저장해도 되나요?
A

권장하지 않습니다. localStorage는 언제나 JavaScript로 읽을 수 있어, XSS 결함 하나로 공격자가 토큰을 훔칠 수 있습니다. OWASP도 세션 식별자를 localStorage에 저장하지 말라고 권고합니다. 브라우저에서는 HttpOnly, Secure, SameSite를 붙인 쿠키로 토큰을 나르는 편이 안전하며, 쿠키를 쓴다면 CSRF 대책도 함께 두세요.

QJWT의 'alg:none'이란 무엇인가요?
A

header의 alg(서명 알고리즘)를 none으로 둔, 서명이 없는 JWT입니다. 사양상 허용되지만 로그인 같은 인증에 쓰는 토큰에서는 절대 받아들이면 안 됩니다. 그렇지 않으면 누구나 내용을 고쳐 쓴 토큰을 만들 수 있습니다. 서버 측에서 받아들이는 알고리즘을 기대하는 값(예: RS256만)으로 고정해 막으세요.