무상태라는 매력
세션은 서버가 상태를 갖는다. 요청마다 세션 저장소를 조회해야 하고, 서버를 여러 대로 늘리면 저장소를 공유해야 한다.
JWT는 토큰 자체에 정보를 담고 서명으로 위조를 막는다. 서버는 서명만 검증하면 되니 저장소를 볼 필요가 없다. 수평 확장이 쉬워진다는 게 가장 큰 매력이다.
대가 — 되돌릴 수 없다
발급한 토큰은 만료 전까지 유효하다. 서버가 참조하는 상태가 없으니 취소할 방법이 없다.
이게 문제가 되는 순간들:
- 사용자가 로그아웃했다 → 토큰은 여전히 유효하다
- 관리자가 권한을 회수했다 → 토큰 안의 권한 정보는 그대로다
- 토큰이 탈취됐다 → 만료를 기다리는 수밖에 없다
세션이라면 저장소에서 지우면 그 즉시 끝나는 일들이다.
흔한 대응과 그 결과
짧은 만료 + 리프레시 토큰. 액세스 토큰을 몇 분짜리로 만들고 갱신용 토큰을 따로 둔다. 노출 창을 줄이는 현실적인 방법이다. 다만 리프레시 토큰은 취소할 수 있어야 하므로 결국 서버에 저장한다.
블랙리스트. 무효화된 토큰 목록을 두고 요청마다 조회한다. 확실하지만, 이 순간 무상태는 사라진다. 요청마다 저장소를 보는 건 세션과 같다.
즉, 즉시 무효화를 원하는 만큼 상태가 돌아온다.
어디에 두느냐도 문제다
- localStorage — XSS로 스크립트가 실행되면 그대로 읽힌다
- httpOnly 쿠키 — 스크립트가 못 읽지만 CSRF 대비가 필요하다
토큰은 세션 ID보다 크다. 매 요청 헤더에 실리므로 권한 정보를 잔뜩 담으면 트래픽에 영향을 준다.
어떻게 고를까
- 단일 서비스에 사용자 세션이 필요하다 → 세션이 대체로 단순하고 안전하다
- 여러 서비스가 인증을 공유하거나 서버 간 호출이 많다 → JWT가 유리하다
- JWT를 쓴다면 만료를 짧게 가져가고, 즉시 무효화가 필요한 지점을 미리 정한다
정리
JWT는 상태를 없앤 게 아니라 클라이언트로 옮긴 것이다. 옮긴 상태는 회수할 수 없다는 것이 대가다.