← Blog
보안

JWT는 어떻게 위조를 막는가 — 서명 방식이 아키텍처를 가른다

면접에서 "말로 설명한다"는 가정으로 작성. 답을 외우기보다 "왜"와 "트레이드오프"를 설명할 수 있는 것이 목표입니다. (5장 TLS·비대칭 암호의 실전 응용)


6-1. JWT는 비대칭키와 관련 있는가?

결론: 관련 있을 수도, 없을 수도 있다. JWT 자체는 서명 방식을 강제하지 않고, 대칭키 방식과 비대칭키 방식 둘 다 지원한다. alg 헤더 값이 무엇이냐에 따라 갈린다.

  • HS256 → 대칭키 (HMAC)
  • RS256, ES256 → 비대칭키 (RSA, ECDSA)

6-2. JWT 구조

점(.)으로 구분된 세 부분: Header.Payload.Signature

  • Header: 서명 알고리즘(alg)과 토큰 타입(typ)
  • Payload: 담는 정보(클레임). 사용자 ID(sub), 발급 시각(iat), 만료 시각(exp) 등
  • Signature: 앞 두 부분을 합쳐 서명한 값

반드시 짚어야 할 오해

Header와 Payload는 암호화가 아니라 Base64 인코딩일 뿐이다. 누구나 디코딩해서 읽을 수 있다. JWT는 내용을 숨기는 기술이 아니라 위조를 막는 기술이다. 서명의 역할은 "이 토큰이 진짜 서버가 발급했고 변조되지 않았다"를 보장하는 것. 그래서 JWT에 비밀번호 같은 민감 정보를 넣으면 안 된다.

팁: eyJ로 시작하는 문자열은 JWT다. eyJ{"의 Base64이기 때문.

실제 예시 (HS256)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9      ← Header
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6...   ← Payload
.Hgs7k-3kyACpiR3VL1v0QsFUZtxCovcg6WxLrCafy_8  ← Signature

디코딩하면:

Header:  {"alg":"HS256","typ":"JWT"}
Payload: {"sub":"1234567890","name":"Hong Gildong","role":"admin","iat":1718000000,"exp":1718003600}

6-3. 서명 방식 1 — HMAC (대칭키, HS256)

HS256 = HMAC + SHA-256. 하나의 비밀 키(secret) 를 쓴다.

  • 서명할 때도 secret, 검증할 때도 같은 secret
  • 검증 로직: 받은 Header.Payload를 같은 secret으로 다시 HMAC 계산 → 토큰의 서명과 일치하는지 비교

한계

secret을 아는 쪽은 누구나 토큰을 발급할 수 있다. 검증하려면 secret을 가져야 하는데, 가지면 위조도 가능. 따라서 발급자와 검증자가 같은 주체(단일 서버) 일 때 적합.


6-4. 서명 방식 2 — RSA/ECDSA (비대칭키, RS256)

RS256 = RSA + SHA-256. 발급과 검증에 다른 키를 쓴다. (5장 디지털 서명 원리 그대로)

  • 서명 = 개인키 (발급 서버만 보유)
  • 검증 = 공개키 (누구나 보유 가능)

핵심 이점

검증자는 공개키만 가지므로 토큰을 위조할 수 없다. 공개키로는 서명 확인만 가능하고 새 토큰 생성은 불가. 그래서 공개키는 마음껏 배포해도 안전.

두 방식 비교

구분HS256 (대칭)RS256 (비대칭)
서명비밀키(secret)개인키(발급 서버만)
검증같은 비밀키공개키(누구나)
검증자의 위조가능불가능
적합한 구조발급=검증 같은 주체발급≠검증 분리(MSA, 소셜 로그인)

6-5. 왜 이 차이가 아키텍처를 결정하는가

  • 대칭키(HS256): 검증 서버가 여러 개면 그 모두에 secret을 뿌려야 하고, 하나만 새도 전체가 위조 위험에 노출.
  • 비대칭키(RS256): 인증 서버만 개인키로 발급, 나머지 여러 마이크로서비스는 공개키로 검증. 공개키는 유출돼도 위조에 못 쓰니 안전하게 배포 가능.

실제로 구글·애플 등 소셜 로그인(OAuth/OIDC)은 RS256을 쓰고, 검증용 공개키를 JWKS(JSON Web Key Set) 공개 엔드포인트로 제공한다.


6-6. 프론트엔드 관점 포인트

  • 내용은 누구나 읽을 수 있다: 프론트에서 atob()로 payload를 디코딩해 만료 시각·사용자 정보를 읽는 건 정상 패턴. 단, 민감 정보가 있으면 안 됨.
  • 프론트는 검증하지 않는다: 서명 검증은 서버 몫. 프론트는 토큰을 들고 다니며 요청에 실어 보내는 역할(Authorization: Bearer <token>).
  • 저장 위치 트레이드오프:
    • LocalStorage → JS 접근 가능 → XSS에 탈취 위험
    • HttpOnly 쿠키 → JS 접근 차단(XSS 안전) → CSRF 대비 필요(SameSite로 완화)
  • JWT의 근본적 한계: 서명된 토큰은 발급 후 강제 무효화가 어렵다(서버가 상태를 안 갖는 게 장점이자 단점). 보완책은 짧은 만료 + refresh token, 또는 블랙리스트 별도 관리.

6-7. 위조가 왜 불가능한지 (검증 원리)

  1. 정상 토큰: 서버가 받은 Header.Payload를 secret(또는 공개키)으로 다시 계산 → 서명 일치 → 통과
  2. payload 위조 시도(role을 admin으로 변경 등): payload는 Base64라 고칠 수 있지만, 서명은 옛것을 붙일 수밖에 없음 → 바뀐 payload로 다시 계산한 서명과 안 맞음 → 위조 감지
  3. 서명까지 다시 만들기 시도: secret(또는 개인키)이 없으면 유효한 서명 생성 불가 → 위조 감지

핵심: secret(대칭) 또는 개인키(비대칭) 없이는 유효한 서명을 만들 수 없다. 내용을 한 글자만 고쳐도 서명이 깨진다.


면접 멘트: "JWT는 Header.Payload.Signature 구조인데, 앞 두 부분은 암호화가 아니라 Base64 인코딩이라 누구나 읽을 수 있습니다. 보안의 핵심은 서명이고, HS256이면 대칭키(secret 하나로 서명·검증), RS256이면 비대칭키(개인키로 서명, 공개키로 검증)입니다. 검증자가 위조할 수 없어야 하는 MSA나 소셜 로그인에서는 RS256을 씁니다. 프론트는 토큰을 검증하지 않고 들고 다니며, 저장 위치는 XSS와 CSRF 트레이드오프로 결정합니다."