JWT 완벽 가이드: 구조·클레임·서명 알고리즘 총정리
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
목차
API 호출 중 갑자기 401 Unauthorized 오류가 발생한다면? 가장 먼저 확인해야 할 것이 JWT 토큰의 만료(exp) 클레임이다. JWT를 눈으로 읽을 수 있어야 디버깅 시간이 줄어든다.
JWT란 무엇인가#
JWT(JSON Web Token)는 당사자 간에 정보를 안전하게 전달하기 위한
컴팩트하고 자기 완결적인 URL-safe 토큰이다. IETF RFC 7519에 의해 표준화되었으며, 로그인 세션 관리와 API 인증에 광범위하게 사용된다.
JWT의 핵심 특징은 서명(Signature) 덕분에 위변조 감지가 가능하다는 점이다. 서버는 토큰을 DB에 저장하지 않아도 서명 검증만으로 토큰의 유효성을 확인할 수 있다.
JWT 구조: Header.Payload.Signature#
JWT는 점(.)으로 구분된 세 부분으로 구성되며, 각 부분은 Base64URL 인코딩이다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
| 부분 | 이름 | 설명 |
|---|---|---|
| 첫 번째 | Header | 알고리즘(alg) + 토큰 타입(typ) |
| 두 번째 | Payload | 클레임(Claims), 실제 전달 데이터 |
| 세 번째 | Signature | 위변조 방지용 서명값 |
중요: Base64URL은 암호화가 아닌 인코딩이다. 디코딩하면 원문이 그대로 보인다. Payload에 비밀 정보를 담아서는 안 된다.
Header#
{
"alg": "HS256",
"typ": "JWT"
}
alg 필드는 서명에 사용된 알고리즘을 지정한다. 주요 값은 다음 섹션에서 상세히 다룬다.
Payload: 클레임 종류#
Payload에는 클레임(Claims)이라 불리는 키-값 쌍이 담긴다. RFC 7519는 세 가지 클레임 범주를 정의한다.
등록된 클레임(Registered Claims)
| 클레임 | 전체 이름 | 설명 |
|---|---|---|
iss | Issuer | 토큰 발급자 |
sub | Subject | 토큰 주체 (보통 사용자 ID) |
aud | Audience | 토큰 수신 대상 |
exp | Expiration Time | 만료 시각 (Unix 타임스탬프) |
nbf | Not Before | 유효 시작 시각 |
iat | Issued At | 발급 시각 |
jti | JWT ID | 토큰 고유 식별자 |
exp는 가장 자주 확인하는 클레임이다. Unix 타임스탬프 형식이므로 현재 시각과 비교해 만료 여부를 판단한다.
공개 클레임(Public Claims)
IANA JSON Web Token Registry에 등록하거나,
충돌 방지를 위해 URI 형식으로 정의하는 클레임이다.
비공개 클레임(Private Claims)
서버와 클라이언트 간 사전 합의하에 사용하는 커스텀 클레임이다. 예: "role": "admin", "user_id": 42.
Signature#
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
서명은 Header와 Payload를 비밀키(또는 개인키)로 서명한 결과값이다. 서명 알고리즘에 따라 방식이 달라진다.
서명 알고리즘 비교: HS256 vs RS256 vs ES256#
| 알고리즘 | 종류 | 키 구조 | 주요 사용처 |
|---|---|---|---|
| HS256 | HMAC-SHA256 | 대칭키 (비밀키 1개) | 단일 서버, 마이크로서비스 내부 |
| HS384 | HMAC-SHA384 | 대칭키 | HS256보다 높은 보안 필요 시 |
| RS256 | RSA-SHA256 | 비대칭키 (공개키 + 개인키) | 외부 서비스, OAuth2, OIDC |
| ES256 | ECDSA-SHA256 | 비대칭키 (타원 곡선) | 모바일·IoT, 짧은 키 길이 요구 |
HS256은 구현이 간단하지만 비밀키가 발급자·검증자 양쪽에 공유되어야 한다. 비밀키가 유출되면 토큰을 임의로 발급할 수 있어 보안 경계가 중요하다.
RS256은 개인키로 서명하고 공개키로만 검증한다. 검증 측에 개인키를 공유할 필요가 없으므로 여러 서비스가 토큰을 검증하는 구조에 적합하다.
ES256은 동일 보안 강도 기준 RSA보다 키 크기가 작아 토큰 크기 절약에 유리하다.
JWT 디코더로 토큰 분석하기#
실제 JWT를 붙여넣으면 Header·Payload를 즉시 디코딩하고
만료 시각(exp)도 사람이 읽기 쉬운 형식으로 변환해준다.
디버깅 시 유용한 체크포인트:
exp클레임 값이 현재 시각 이후인지 확인aud클레임이 호출 대상 API와 일치하는지 확인alg필드에 예상한 알고리즘이 표시되는지 확인
보안 주의사항#
alg: none 취약점#
초기 JWT 구현에는 "alg": "none"을 지정해 서명을 생략할 수 있는 취약점이 있었다. 서버 측에서 alg 필드를 명시적으로 허용 목록(allowlist)과 대조해야 한다.
// ❌ 위험: alg 필드를 그대로 신뢰
jwt.verify(token, secret);
// ✅ 안전: 허용 알고리즘 명시
jwt.verify(token, secret, { algorithms: ['HS256'] });
비밀 정보는 Payload에 넣지 말 것#
Payload는 Base64URL 디코딩만으로 원문이 노출된다. 비밀번호, 카드번호, 주민번호 등 민감 정보는 절대 포함하지 않는다.
짧은 만료 시간 + Refresh Token 패턴#
Access Token은 만료 시간(exp)을 짧게 설정(15분–1시간)하고,
만료 시 Refresh Token으로 새 토큰을 발급받는 패턴이 일반적이다. 토큰 탈취 피해를 최소화한다.
JWE(암호화 JWT)와의 구분#
RFC 7516이 정의하는 JWE(JSON Web Encryption)는 Payload 자체를 암호화한다. 민감 정보 전달이 필요하다면 JWS(서명만) 대신 JWE를 고려한다.
실무 구현 패턴#
JavaScript (jsonwebtoken 라이브러리)#
const jwt = require('jsonwebtoken');
// 토큰 발급
const token = jwt.sign(
{ sub: 'user123', role: 'admin' },
process.env.JWT_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' }
);
// 토큰 검증
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256']
});
console.log(decoded.sub); // 'user123'
} catch (err) {
if (err.name === 'TokenExpiredError') {
// 만료된 토큰 처리
}
}
Python (PyJWT 라이브러리)#
import jwt
from datetime import datetime, timedelta, timezone
# 토큰 발급
payload = {
"sub": "user123",
"exp": datetime.now(timezone.utc) + timedelta(hours=1)
}
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
# 토큰 검증
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
print("Token expired")
except jwt.InvalidTokenError:
print("Invalid token")
자주 묻는 질문#
Q. JWT는 세션 쿠키를 완전히 대체할 수 있나요?
A. 상황에 따라 다르다. JWT는 서버 측 저장소 없이 수평 확장(scale-out)이 유리하지만,
토큰 강제 무효화가 필요한 경우(로그아웃, 권한 변경)에는 추가 구현이 필요하다. 세션 쿠키는 즉각적인 무효화가 쉬운 반면 서버 메모리를 사용한다.
Q. exp 값은 어떤 시간대 기준인가요?
A. RFC 7519 기준 exp는 UTC 기반 Unix 타임스탬프
(1970-01-01T00:00:00Z 이후 경과 초)다. 시간대 변환 없이 Date.now() / 1000과 직접 비교하면 된다.
Q. JWT Payload를 변경하면 어떻게 되나요?
A. Payload를 수정하면 서명값과 일치하지 않아
검증 단계에서 InvalidSignatureError가 발생한다. 서명 검증이 JWT 위변조 방지의 핵심이다.
Q. RS256에서 공개키는 어떻게 배포하나요?
A. JWKS(JSON Web Key Set) 엔드포인트(/.well-known/jwks.json)를 통해
공개키를 자동으로 제공하는 방식이 표준이다(RFC 7517). Auth0, Keycloak 등 주요 인증 서버가 이 방식을 사용한다.
참고: 이 가이드는 IETF RFC 7519(JWT), RFC 7515(JWS), RFC 7517(JWK), RFC 7518(JWA) 표준을 기반으로 작성되었습니다. 보안 구현은 사용하는 언어·프레임워크의 최신 라이브러리 문서를 함께 확인하세요.
관련 도구: Base64 인코더/디코더 → · UUID 생성기 →