스프링 시큐리티: JWT(JSON Web Token)는 무엇이고, 왜 쓰는가
서버가 세션을 기억하지 않아도 "이 사용자가 누구인지"를 어떻게 알 수 있을까요?
세션 기반 인증에서는 서버가 사용자 정보를 저장해야 합니다. 서버를 수평 확장하면 세션 공유 문제가 발생합니다. JWT(JSON Web Token) 는 인증·인가 정보를 토큰 자체에 담아 서버가 상태를 저장하지 않아도 되는 토큰 기반 인증 방식입니다.
JWT(JSON Web Token)란
JWT 는 사용자 정보와 권한을 JSON 형태로 담고 서명을 붙인 토큰입니다. 서버는 매 요청마다 토큰의 서명만 검증하면 되고, 서명이 유효하면 토큰 안의 정보를 신뢰할 수 있습니다.
JWT 토큰의 구조

JWT는 헤더(Header) · 페이로드(Payload) · 시그니처(Signature) 세 부분으로 구성됩니다. 각 부분을 Base64Url로 인코딩한 뒤 .으로 이어 붙여 Header.Payload.Signature 형태의 문자열이 됩니다.
헤더(Header)

헤더는 ** 토큰의 메타데이터 **를 담습니다. 토큰 타입(typ = JWT)과 서명 알고리즘(alg = HS256, RS256 등)이 들어가며, 서버는 이 정보를 보고 검증 방식을 결정합니다.
페이로드(Payload)

페이로드는 ** 클라이언트와 서버가 공유할 데이터(Claim)**를 담는 부분입니다. 사용자 ID, 권한, 발급 시간(iat), 만료 시간(exp) 같은 값이 들어갑니다.
이 부분은 Base64Url ** 인코딩 **만 되어 있어서 누구나 디코딩해서 내용을 확인할 수 있습니다. 비밀번호나 주민등록번호 같은 민감한 정보는 절대 넣으면 안 됩니다.
시그니처(Signature)

시그니처는 인코딩된 헤더와 페이로드에 비밀키(또는 개인키)로 ** 서명한 값 **입니다.
서버는 매 요청마다 시그니처를 다시 계산해서 토큰에 들어 있는 값과 비교합니다. 토큰 내용이 한 글자라도 바뀌면 시그니처가 달라지기 때문에, 위변조를 감지할 수 있습니다.
서명에 사용하는 키는 알고리즘에 따라 다릅니다.
| 알고리즘 | 서명 | 검증 |
|---|---|---|
| HS256 (대칭키) | Secret Key | 같은 Secret Key |
| RS256 (비대칭키) | Private Key | Public Key |
HS256은 키 하나로 서명과 검증을 모두 수행하므로 간단하지만, 검증하는 모든 서버가 비밀키를 알아야 합니다. RS256은 Private Key는 발급 서버만 가지고, Public Key만 공개하면 되므로 마이크로서비스 환경에 유리합니다.
JWT 인증 흐름

- 클라이언트가 ID/비밀번호로 로그인 요청을 보낸다.
- 서버가 인증에 성공하면 사용자 정보와 권한을 담은 JWT를 발급한다.
- 클라이언트는 이후 요청마다
Authorization헤더에 토큰을 포함시킨다.
curl -X GET "https://example.com/api/v1/resources" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"
- 서버는 토큰의 서명과 만료 시간을 검증한다.
- 검증이 통과하면 토큰에 담긴 정보로 요청을 처리하고 응답을 반환한다.
이 과정에서 서버는 세션 저장소를 조회하지 않습니다. 토큰 자체가 인증 정보를 담고 있기 때문입니다.
JWT의 장단점
JWT는 Stateless하기 때문에 수평 확장 시 세션 동기화가 필요 없고, 별도 세션 저장소 없이도 여러 마이크로서비스에서 공통 인증 수단으로 쓸 수 있습니다.
반면 한 번 발급된 토큰이 유출되면 만료 전까지 공격자가 사용할 수 있고, 서버에서 즉시 무효화하기 어렵습니다.
| 장점 | 설명 |
|---|---|
| Stateless | 서버가 상태를 저장하지 않아 수평 확장이 쉽다 |
| ** 세션 저장소 불필요** | Redis 같은 별도 저장소 없이 토큰만으로 인증 가능 |
| ** 마이크로서비스 적합** | 여러 서비스가 같은 JWT를 검증해서 공통 인증 수단으로 사용 |
| 단점 | 대응 방법 |
|---|---|
| ** 토큰 탈취 시 위험** | 만료 시간을 짧게 + HTTPS 강제 + Refresh 토큰 조합 |
| ** 강제 로그아웃 어려움** | 짧은 Access Token + Refresh Token + 블랙리스트 |
| ** 페이로드 평문 노출** | 민감정보 제외, 필요시 JWE(암호화된 JWT) 도입 |
주의할 점
JWT payload는 암호화되지 않는다
JWT의 payload는 Base64 ** 인코딩 **일 뿐 암호화가 아닙니다. 누구나 디코딩해서 내용을 볼 수 있으므로, 비밀번호나 개인정보를 payload에 넣으면 안 됩니다.
토큰이 탈취되면 만료될 때까지 막을 수 없다
서버에 상태를 저장하지 않기 때문에, 토큰이 유출되어도 서버에서 즉시 무효화할 방법이 없습니다. Access Token의 만료 시간을 짧게 설정(15분~30분)하고, Refresh Token으로 갱신하는 구조를 사용해야 합니다. 로그아웃 시 Refresh Token을 블랙리스트에 등록하는 방식으로 보완합니다.
localStorage에 저장하면 XSS에 취약하다
localStorage는 JavaScript에서 자유롭게 접근할 수 있기 때문에, XSS 공격이 성공하면 토큰이 바로 탈취됩니다. httpOnly 쿠키에 저장하면 JavaScript에서 접근할 수 없어 XSS 탈취 위험을 줄일 수 있습니다. 다만 쿠키 기반이면 CSRF 보호가 다시 필요해집니다.
정리
| 항목 | 설명 |
|---|---|
| JWT | 사용자 정보 + 권한 + 서명을 담은 Stateless 토큰 |
| 구조 | Header(메타데이터) + Payload(클레임) + Signature(서명) |
| 서명 검증 | 토큰 내용이 바뀌면 서명이 달라져 위변조 감지 가능 |
| HS256 vs RS256 | 대칭키(키 하나) vs 비대칭키(서명/검증 키 분리) |
| 보안 핵심 | payload 민감정보 금지, 짧은 만료 + Refresh Token, httpOnly 쿠키 |