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. 위조가 왜 불가능한지 (검증 원리)
- 정상 토큰: 서버가 받은
Header.Payload를 secret(또는 공개키)으로 다시 계산 → 서명 일치 → 통과 - payload 위조 시도(role을 admin으로 변경 등): payload는 Base64라 고칠 수 있지만, 서명은 옛것을 붙일 수밖에 없음 → 바뀐 payload로 다시 계산한 서명과 안 맞음 → 위조 감지
- 서명까지 다시 만들기 시도: secret(또는 개인키)이 없으면 유효한 서명 생성 불가 → 위조 감지
핵심: secret(대칭) 또는 개인키(비대칭) 없이는 유효한 서명을 만들 수 없다. 내용을 한 글자만 고쳐도 서명이 깨진다.
면접 멘트: "JWT는 Header.Payload.Signature 구조인데, 앞 두 부분은 암호화가 아니라 Base64 인코딩이라 누구나 읽을 수 있습니다. 보안의 핵심은 서명이고, HS256이면 대칭키(secret 하나로 서명·검증), RS256이면 비대칭키(개인키로 서명, 공개키로 검증)입니다. 검증자가 위조할 수 없어야 하는 MSA나 소셜 로그인에서는 RS256을 씁니다. 프론트는 토큰을 검증하지 않고 들고 다니며, 저장 위치는 XSS와 CSRF 트레이드오프로 결정합니다."