파이썬 PyJWT 완벽 가이드: JWT 생성·클레임·만료 검증
PyJWT로 JWT 토큰을 생성하고 exp·iss·aud 클레임과 서명을 안전하게 검증하는 방법을 예제와 체크리스트로 정리합니다.

파이썬에서 JWT를 만들고 검증하려면 PyJWT의 jwt.encode()와 jwt.decode()를 사용하면 됩니다. 다만 토큰을 단순히 해독하는 데서 끝내지 말고, 허용 알고리즘을 코드에 고정하고 서명과 exp·iss·aud 같은 클레임을 함께 검증해야 안전합니다.
핵심 요약
- JWT는 점으로 구분된 헤더·페이로드·서명 구조이며, 일반적인 서명형 JWT의 페이로드는 암호문이 아닙니다.
exp는 만료,nbf는 사용 시작 시각,iss는 발급자,aud는 수신 대상을 나타냅니다.- 검증할 알고리즘은 토큰 헤더를 믿고 선택하지 말고 서버 설정으로 고정해야 합니다.
- 비밀키는 소스 코드에 넣지 않고 환경변수나 비밀 관리 서비스에서 불러오며, 토큰 원문은 로그에 남기지 않습니다.
JWT와 PyJWT를 먼저 이해하기
JWT(JSON Web Token)는 당사자 사이에서 클레임을 간결하고 URL에 안전한 형태로 전달하기 위한 표준입니다. 흔히 header.payload.signature처럼 세 부분으로 보이며, PyJWT는 파이썬에서 이 토큰을 생성하고 서명 검증하는 라이브러리입니다.
중요한 점은 서명과 암호화를 구분하는 것입니다. 서명은 내용이 바뀌지 않았고 올바른 키로 발급됐는지 확인하는 장치입니다. 페이로드는 Base64URL 인코딩일 뿐 누구나 읽을 수 있으므로 비밀번호, 주민등록번호, API 키 같은 비밀정보를 넣으면 안 됩니다.
PyJWT 설치와 토큰 생성
기본 기능은 pip install PyJWT로 설치할 수 있습니다. RSA나 ECDSA 계열 알고리즘을 쓸 때는 공식 문서가 안내하는 암호화 의존성을 함께 설치해야 합니다. 프로젝트마다 가상환경을 분리하고 잠금 파일에 버전을 기록해 재현 가능성을 확보하세요.
from datetime import datetime, timedelta, timezone
import os
import jwt
secret = os.environ["JWT_SECRET"]
now = datetime.now(timezone.utc)
payload = {
"sub": "user-1234",
"iss": "https://api.example.com",
"aud": "https://app.example.com",
"iat": now,
"nbf": now,
"exp": now + timedelta(minutes=15),
}
token = jwt.encode(payload, secret, algorithm="HS256")
예제의 15분은 사용법을 보여주기 위한 값입니다. 실제 만료시간은 서비스 위험도, 재인증 비용, 갱신 토큰 정책을 함께 고려해 정해야 합니다. 키는 충분히 강한 무작위 값으로 만들고 저장소에 커밋하지 마세요.
클레임은 무엇을 넣어야 하나요?
| 클레임 | 의미 | 검증 포인트 |
|---|---|---|
sub |
토큰의 주체 | 사용자 ID처럼 변하지 않는 식별자 사용 |
iss |
발급자 | 서버가 기대하는 정확한 문자열과 비교 |
aud |
수신 대상 | 현재 API가 대상에 포함되는지 확인 |
iat |
발급 시각 | 시간 형식과 비정상적인 미래 시각 확인 |
nbf |
사용 가능 시작 | 해당 시각 전에는 거부 |
exp |
만료 시각 | 만료 후에는 거부 |
jti |
토큰 고유 ID | 필요하면 폐기 목록과 재사용 방지에 활용 |
RFC에서 등록 클레임의 사용은 상황에 따라 선택적이지만, 애플리케이션 보안 요구사항은 별개입니다. 예를 들어 인증용 액세스 토큰이라면 exp, iss, aud, sub의 존재와 값을 정책으로 요구하는 편이 일반적입니다. 권한은 단순 문자열 하나보다 서버가 이해하는 최소 범위로 표현하고, 민감한 개인정보는 넣지 마세요.
서명과 만료를 함께 검증하는 방법
try:
claims = jwt.decode(
token,
secret,
algorithms=["HS256"],
issuer="https://api.example.com",
audience="https://app.example.com",
options={"require": ["exp", "iss", "aud", "sub"]},
leeway=5,
)
except jwt.ExpiredSignatureError:
# 만료된 토큰
...
except jwt.InvalidTokenError:
# 서명·형식·클레임 등이 유효하지 않은 토큰
...
jwt.decode()는 만료된 exp를 검사하고 만료 시 ExpiredSignatureError를 발생시킵니다. 시간은 시간대가 지정된 UTC datetime으로 만들면 서버 지역 설정 차이로 생기는 오류를 줄일 수 있습니다. leeway는 서버 시계 차이를 흡수하는 작은 여유일 뿐, 길게 설정해 만료 정책을 무력화해서는 안 됩니다.
options={"require": [...]}는 필수 클레임의 존재 여부를 요구합니다. 그와 별개로 issuer와 audience 인수를 전달해 예상 값까지 검증해야 합니다. 서명 검증을 끄고 읽은 페이로드는 인증이나 권한 판단에 사용하지 마세요.
HS256과 RS256, 무엇이 다른가요?
| 구분 | HS256 | RS256 |
|---|---|---|
| 키 구조 | 같은 비밀키로 서명·검증 | 개인키로 서명, 공개키로 검증 |
| 적합한 환경 | 한 조직·한 보안 경계의 단순 구조 | 여러 서비스가 발급자를 검증하는 구조 |
| 주의점 | 검증 서버도 서명 능력을 갖게 됨 | 개인키 보호와 공개키 배포·교체 필요 |
어떤 알고리즘이 무조건 우월한 것은 아닙니다. 신뢰 경계와 키 배포 방식에 맞춰 선택해야 합니다. 특히 디코딩할 때 algorithms를 토큰 헤더의 alg 값에서 동적으로 가져오면 안 됩니다. 서버 설정에 허용 목록을 고정하고, 대칭키와 비대칭키 알고리즘을 같은 키 해석 경로에 무분별하게 섞지 마세요.
운영 환경의 오류 처리와 토큰 수명주기
클라이언트에는 보통 “인증이 만료됐습니다”처럼 필요한 수준의 메시지만 보내고, 서명 실패 원인이나 키 정보를 노출하지 않습니다. 서버 로그에도 토큰 원문을 남기지 말고 요청 식별자와 오류 종류 정도만 기록하세요. 예외를 모두 성공으로 처리하거나, 검증 실패 후 페이로드를 신뢰하는 코드는 피해야 합니다.
JWT가 서명 검증에 성공해도 계정이 정지됐거나 권한이 변경됐을 수 있습니다. 짧은 수명의 액세스 토큰, 안전하게 보관하는 갱신 토큰, 키 교체, 로그아웃·탈취 대응 정책을 함께 설계해야 합니다. 즉 JWT는 세션과 권한 시스템 전체가 아니라 그 안의 전달 형식입니다.
검증 테스트에 반드시 포함할 사례
정상 토큰만 시험하면 운영 중 경계 조건을 놓치기 쉽습니다. 페이로드 한 글자를 바꾼 변조 토큰, 이미 만료된 토큰, 아직 nbf 시각이 오지 않은 토큰, 다른 iss와 aud를 가진 토큰, 필수 sub가 빠진 토큰을 각각 준비해 모두 거부되는지 확인하세요. 서버 시간이 UTC 기준으로 동기화됐는지, 키를 교체하는 동안 구키로 발급한 토큰을 언제까지 인정할지도 배포 전에 정해야 합니다.
브라우저에서 토큰을 다룬다면 저장 위치만 결정하고 끝내지 마세요. 스크립트가 접근할 수 있는 저장소는 XSS 영향을 검토해야 하고, 쿠키를 사용하면 Secure·HttpOnly·SameSite 속성과 CSRF 방어를 함께 설계해야 합니다. 모바일 앱이나 서버 간 호출도 토큰을 평문 채널로 보내지 말고 TLS를 사용해야 합니다.
실수 방지 체크리스트
- 허용 알고리즘을 서버 설정으로 고정했는지 확인합니다.
- 서명 검증을 비활성화한 결과를 인증에 사용하지 않습니다.
exp·iss·aud·sub를 서비스 정책에 맞게 요구합니다.- UTC 기반의 시간대 인식
datetime을 사용합니다. - 페이로드에 비밀번호, 비밀키, 민감 개인정보를 넣지 않습니다.
- 비밀키를 소스 저장소와 로그에서 제외합니다.
- 짧은 액세스 토큰 수명과 갱신·폐기 정책을 함께 마련합니다.
- 키 교체 시 구키와 신키의 전환 절차를 테스트합니다.
- 만료·잘못된 발급자·잘못된 대상·변조 토큰을 각각 테스트합니다.
- HTTPS를 사용하고 브라우저 저장 위치의 XSS·CSRF 위험을 검토합니다.
자주 묻는 질문
1. JWT 페이로드는 암호화되나요?
일반적인 서명형 JWT는 암호화되지 않습니다. 인코딩된 내용을 읽을 수 있으므로 비밀정보를 넣지 마세요.
2. decode만 성공하면 로그인된 사용자로 봐도 되나요?
아닙니다. 서명, 허용 알고리즘, 만료, 발급자, 대상, 필수 클레임을 검증하고 계정 상태와 권한도 확인해야 합니다.
3. exp는 반드시 넣어야 하나요?
RFC상 모든 상황에서 필수는 아니지만 인증 토큰에는 무기한 사용을 막기 위해 서비스 정책상 요구하는 것이 안전합니다.
4. leeway는 얼마나 줘야 하나요?
서버 시계 오차를 보정할 최소 범위만 사용하세요. PyJWT 문서는 작은 시간 여유를 지원하지만 구체적인 값은 인프라 시간 동기화 상태에 맞춰 정해야 합니다.
5. HS256 비밀키를 사용자에게 줘도 되나요?
안 됩니다. HS256 키를 가진 주체는 검증뿐 아니라 토큰 서명도 할 수 있으므로 서버의 비밀로 보호해야 합니다.
6. RS256 공개키는 공개해도 되나요?
검증용 공개키는 배포할 수 있지만 개인키는 발급 서버에서 엄격히 보호해야 합니다.
7. 토큰에서 alg를 읽어 algorithms에 넣어도 되나요?
피해야 합니다. 허용 알고리즘은 신뢰할 수 있는 서버 설정에서 고정하세요.
8. 로그아웃하면 JWT가 즉시 무효화되나요?
JWT 자체는 자동 폐기되지 않습니다. 짧은 만료, 갱신 토큰 폐기, 필요 시 jti 기반 차단 목록 같은 별도 정책이 필요합니다.
9. jwt.decode에서 exp는 자동 검사되나요?
정상 검증 경로에서는 만료를 확인하고 만료 시 ExpiredSignatureError가 발생합니다. 검증 옵션을 임의로 끄지 마세요.
10. JWT가 세션보다 항상 좋은가요?
아닙니다. 폐기 요구, 서비스 구조, 운영 복잡도에 따라 서버 세션이 더 단순할 수 있습니다. 요구사항을 먼저 비교해야 합니다.
공식 출처와 확인일
- PyJWT 공식 Usage Examples — 인코딩·디코딩, 등록 클레임, 만료와 leeway 확인
- RFC 7519: JSON Web Token — JWT 구조와 표준 클레임 정의 확인
실제 확인일: 2026년 9월 9일. 보안 설정은 라이브러리 버전과 서비스 구조에 따라 달라질 수 있으므로 배포 전 현재 공식 문서와 보안 요구사항을 다시 확인하세요.