사용자 정보를 토큰 자체에 담고 서명해, 서버가 따로 보관하지 않아도 검증 가능한 토큰(JSON Web Token).
구조 — 점으로 구분된 세 부분
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxIiwiZXhwIjoxNzAwfQ . SflKxwRJSMeKKF2QT4
└──── 헤더 ────┘ └────── 페이로드 ──────┘ └──── 서명 ────┘
헤더 — 서명 알고리즘
{ "alg": "HS256", "typ": "JWT" }
페이로드 — 담을 정보(클레임)
{ "sub": "1", "role": "USER", "exp": 1700000000, "iat": 1699996400 }
서명 — HMAC(base64(헤더) + "." + base64(페이로드), 비밀키)
가장 중요한 오해 — 암호화가 아니다
페이로드는 Base64로 인코딩만 되어 있다. 누구나 디코딩해서 읽을 수 있다.
jwt.io에 붙여 넣으면 내용이 그대로 보인다.
서명이 보장하는 것은 "변조되지 않았다"(무결성)이지 "안 보인다"(기밀성)가 아니다.
→ 비밀번호, 주민번호, 개인정보를 절대 페이로드에 넣으면 안 된다. 정말 숨겨야 하면 JWE(암호화된 JWT)를 써야 한다.
서명이 왜 안전한가
공격자가 페이로드를 "role": "ADMIN" 으로 바꾸면?
→ 서명이 안 맞는다. 올바른 서명을 만들려면 비밀키가 필요한데 없다.
→ 서버가 검증에서 거부한다.
표준 클레임
| 클레임 | 뜻 |
|---|---|
sub | 주체(사용자 ID) |
iss | 발급자 |
aud | 대상 |
exp | 만료 시각 — 반드시 넣는다 |
iat | 발급 시각 |
jti | 토큰 고유 ID (블랙리스트용) |
최대 약점 — 무효화가 안 된다
-
서버는 토큰을 보관하지 않는다
-
"이 토큰 취소" 라고 할 대상이 없다
-
만료 전까지 계속 유효하다
-
비밀번호를 바꿔도, 계정을 정지시켜도, 이미 발급된 토큰은 살아 있다
대응 방법과 그 모순
| 방법 | 문제 |
|---|---|
| 만료를 짧게(5~15분) | 가장 현실적. 재발급 흐름 필요 |
| 블랙리스트 | 저장소를 조회해야 한다 → JWT의 무상태 장점이 사라진다 |
| 토큰 버전 | 사용자 테이블에 버전 컬럼 → 역시 DB 조회 필요 |
"완전한 무효화를 원하면 결국 상태를 갖게 된다." 이 모순을 이해하고 말하는 것이 면접에서 중요하다.
대표 취약점 두 가지
① alg: none 공격
{ "alg": "none" } ← 서명 없이도 유효하다고 주장
라이브러리가 이를 받아들이면 누구나 토큰을 위조할 수 있다. 검증 시 알고리즘을 서버가 고정해야 한다. 토큰이 알려 주는 대로 따르면 안 된다.
② RS256 → HS256 혼동 공격
RSA 공개키는 공개되어 있다. 공격자가 alg를 HS256으로 바꾸고
공개키를 HMAC 비밀키처럼 사용해 서명하면, 서버가 같은 공개키로 검증해 통과한다.
역시 알고리즘 고정이 해법이다.
어디에 저장하나 — 정답이 없는 문제
| 저장소 | 장점 | 위험 |
|---|---|---|
| localStorage | 간단, 크로스 도메인 쉬움 | XSS에 그대로 노출 |
| HttpOnly 쿠키 | XSS로 못 읽음 | CSRF 대비 필요 |
| 메모리(변수) | 가장 안전 | 새로고침하면 사라짐 |
실무 절충 — Access Token은 메모리에, Refresh Token은 HttpOnly 쿠키에.
언제 쓰는 것이 맞나
✅ 여러 서비스가 하나의 인증을 공유(MSA) ✅ 서버 간 통신, 외부 API ✅ 짧은 수명의 일회성 토큰(이메일 인증 링크)
❌ 즉시 로그아웃이 중요한 서비스 ❌ 권한이 자주 바뀌는 경우 ❌ 단일 서버 웹 애플리케이션 (세션이 더 단순하고 안전하다)