Python JWT 解码 PyJWT 指南
直接在浏览器中使用免费的 JWT解码工具,无需安装。
在线试用 JWT解码工具 →所有使用基于令牌认证的 API 都会在某个时刻返回一个 JWT, 而弄清楚里面的内容是开发过程中频繁出现的任务。Python 中的 JWT 解码器可以 将那串不透明的 base64 字符串转换为可读的声明字典,方便直接使用。你需要的 PyPI 包是 PyJWT——通过 pip install PyJWT 安装,但导入时使用 import jwt。 本指南将介绍带完整签名验证的 jwt.decode(), 无需密钥的快速检查解码方式,不依赖任何库的手动 base64 解码,RS256 公钥验证, 以及在生产认证系统中常见的问题。如果只是临时查看, 使用在线 JWT 解码器无需编写任何代码即可即时完成。 所有示例适用于 Python 3.10+ 和 PyJWT 2.x。
- ✓pip install PyJWT,然后使用 import jwt——包名和导入名不同,这几乎会让所有人踩坑。
- ✓jwt.decode(token, key, algorithms=["HS256"]) 返回包含声明的普通字典。始终明确传入 algorithms 参数。
- ✓如需在不验证签名的情况下检查声明:jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"])。
- ✓对于 RSA/EC 令牌:pip install PyJWT cryptography——非对称算法需要 cryptography 后端。
- ✓手动解码(base64 + json)无需任何库,但会跳过所有签名和过期时间验证。
什么是 JWT 解码?
JSON Web Token 由三个以点号分隔的 base64url 编码段组成:头部(算法和令牌类型)、 载荷(声明——用户 ID、角色、过期时间)和签名。解码 JWT 意味着提取头部和载荷段, 对其进行 base64url 解码,并将得到的 JSON 解析为声明字典。
头部告诉你令牌使用了哪种签名算法,有时还包含 kid (密钥 ID)用于查找正确的验证密钥。载荷包含实际数据:令牌颁发给谁(sub), 何时过期(exp), 面向哪个服务(aud), 以及你的应用定义的自定义声明。 签名段用于证明令牌未被篡改,但你需要密钥或公钥才能验证它。解码和验证是两个独立的操作。 你可以在不验证签名的情况下解码载荷(适用于调试), 但绝不要将未经验证的声明用于授权决策。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
"sub": "usr_8f2a",
"role": "admin",
"exp": 1711815600
}jwt.decode() — 使用 PyJWT 解码并验证
jwt.decode() 是 PyJWT 库的核心函数。它接受编码后的令牌字符串、密钥(HMAC 算法使用共享密钥,RSA/EC 使用公钥), 以及必填的 algorithms 列表。 该函数会验证签名、检查 exp 和 nbf 等标准声明,并以 Python 字典的形式返回载荷。如果出现任何问题——签名错误、令牌过期、算法不匹配—— 它会抛出相应的异常。
最简工作示例
import jwt
# A shared secret between the issuer and this service
SECRET_KEY = "k8s-webhook-signing-secret-2026"
# Encode a token first (simulating what an auth server would issue)
token = jwt.encode(
{"sub": "usr_8f2a", "role": "admin", "team": "platform"},
SECRET_KEY,
algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3Jf...
# Decode and verify the token
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin', 'team': 'platform'}
print(payload["role"])
# adminalgorithms 参数是一个列表而非单个字符串,在 PyJWT 2.x 中为必填项。 这是一项安全设计:若省略该参数,攻击者可以在头部构造一个 alg: none 的令牌,从而完全绕过验证。始终明确指定你的应用所接受的算法。 如果你只签发 HS256 令牌,列表应为 ["HS256"], 而不是 ["HS256", "RS256", "none"]。 缩小列表范围可以减少攻击面。
早期让我困惑的一点:PyJWT 2.x 将 jwt.encode() 的返回类型从字节改为字符串。如果你在旧版 Stack Overflow 回答中看到对编码令牌调用 .decode("utf-8") 的代码,那是 PyJWT 1.x 时代的写法,在 2.x 版本中会抛出 AttributeError。 令牌已经是字符串了,直接使用即可。
带过期时间的完整往返示例
import jwt
from datetime import datetime, timedelta, timezone
SECRET_KEY = "webhook-processor-secret"
# Create a token that expires in 1 hour
payload = {
"sub": "svc_payment_processor",
"iss": "auth.internal.example.com",
"aud": "https://api.example.com",
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
"permissions": ["orders:read", "refunds:create"],
}
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
# Later, when the token arrives in a request header:
try:
decoded = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"],
audience="https://api.example.com",
issuer="auth.internal.example.com",
)
print(f"Service: {decoded['sub']}")
print(f"Permissions: {decoded['permissions']}")
except jwt.ExpiredSignatureError:
print("Token expired — request re-authentication")
except jwt.InvalidAudienceError:
print("Token not intended for this API")
except jwt.InvalidIssuerError:
print("Token issued by unknown authority")datetime 对象转换为 Unix 时间戳。 解码时, exp、 iat 和 nbf 声明会以整数形式返回,而非 datetime 对象。 如需转换,请使用 datetime.fromtimestamp(payload["exp"], tz=timezone.utc)。不验证签名地解码 JWT
有时你需要在验证令牌之前先读取声明。一个常见场景: 令牌头部包含 kid (密钥 ID)字段,在验证之前需要先从 JWKS 端点获取对应的公钥。 PyJWT 通过 verify_signature: False 选项支持这种方式。你仍然需要传入 algorithms 列表,但 key 参数会被忽略。
import jwt
token = (
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZy0xNzI2In0"
".eyJzdWIiOiJ1c3JfM2M3ZiIsInNjb3BlIjoicmVhZDpvcmRlcnMiLCJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIn0"
".signature_placeholder"
)
# Step 1: Read claims without verification to get routing info
unverified = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["RS256"]
)
print(unverified)
# {'sub': 'usr_3c7f', 'scope': 'read:orders', 'iss': 'auth.example.com'}
# Step 2: Read the header to find which key to use
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'RS256', 'typ': 'JWT', 'kid': 'sig-1726'}
# Now use header['kid'] to fetch the correct public key from your JWKS endpoint这里有一个细微的区别。 jwt.get_unverified_header() 只读取头部——即第一段。带有 verify_signature: False 的 jwt.decode() 调用读取载荷——即第二段。结合两者,可以在不持有密钥的情况下提取令牌中的全部内容。 即使关闭了签名验证,PyJWT 仍然会验证令牌的结构是否正确(三个点号分隔的段、有效的 base64、有效的 JSON)。 如果令牌在结构上存在问题,无论传入什么选项,都会抛出 DecodeError。
jwt.decode() 参数参考
完整签名为 jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require)。 algorithms 之后的所有参数均为仅关键字参数。
options 字典提供了对 PyJWT 执行哪些验证的精细控制。各键对应独立的检查项: verify_signature、 verify_exp、 verify_nbf、 verify_iss、 verify_aud 以及 verify_iat。 所有选项默认为 True, 除非你明确将其设为 False。 在生产环境中,请保持所有选项的默认值。我只会在开发阶段处理过期测试令牌、需要临时绕过过期检查时才禁用某些检查项。
import jwt
# Require specific claims to be present — raises MissingRequiredClaimError if absent
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"],
options={"require": ["exp", "iss", "sub"]},
issuer="auth.internal.example.com",
)
# During development only: skip expiry to test with old tokens
dev_payload = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"],
options={"verify_exp": False}, # DO NOT use in production
)使用 base64 和 json 手动解码 JWT
你可以仅使用 Python 标准库解码 JWT 载荷——无需 pip install。 这在多种场景下很实用:不适合添加依赖的调试脚本,只有标准库可用的受限 CI 环境, 希望减少冷启动时间的 AWS Lambda 函数,或者仅仅是想理解 JWT 的底层机制。 整个过程很简单:按点号分割,取目标段,补充 base64 填充,解码,然后解析 JSON。
base64 和 json 模块均属于 Python 标准库,因此此方法适用于 3.6 及以上的任何 Python 安装环境。 下面的函数分别处理头部(第一段)和载荷(第二段):
import base64
import json
def decode_jwt_payload(token: str) -> dict:
"""Decode the JWT payload without signature verification.
Works with any JWT — HS256, RS256, ES256, etc.
"""
parts = token.split(".")
if len(parts) != 3:
raise ValueError(f"Expected 3 JWT segments, got {len(parts)}")
payload_b64 = parts[1]
# base64url uses - and _ instead of + and /
# Python's urlsafe_b64decode handles this, but needs padding
payload_b64 += "=" * (-len(payload_b64) % 4)
payload_bytes = base64.urlsafe_b64decode(payload_b64)
return json.loads(payload_bytes)
def decode_jwt_header(token: str) -> dict:
"""Decode the JWT header (algorithm, key ID, type)."""
header_b64 = token.split(".")[0]
header_b64 += "=" * (-len(header_b64) % 4)
return json.loads(base64.urlsafe_b64decode(header_b64))
# Example usage
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
header = decode_jwt_header(token)
print(f"Algorithm: {header['alg']}")
# Algorithm: HS256
claims = decode_jwt_payload(token)
print(f"Subject: {claims['sub']}")
print(f"Role: {claims['role']}")
# Subject: usr_8f2a
# Role: admin填充技巧(+= "=" * (-len(s) % 4)) 是每个人都会忘记的部分。JWT 的 base64url 编码会省略末尾的 = 字符,但 Python 的 urlsafe_b64decode 需要这些填充字符。如果不修复填充,你会得到 binascii.Error: Incorrect padding。
jwt.decode()。从 API 响应和令牌文件中解码 JWT
两种最常见的实际场景:从 HTTP 响应中提取 JWT(OAuth 令牌端点、登录 API), 以及从文件中读取令牌(服务账号凭证、Kubernetes 挂载的密钥、磁盘上缓存的令牌)。 两者都需要妥善的错误处理。网络请求可能失败,文件可能丢失, 令牌可能在缓存后到读取前已经过期。
以下示例使用 httpx 进行 HTTP 调用(可替换为 requests, 写法完全一致),使用 pathlib.Path 进行文件操作。每个示例都捕获具体的 PyJWT 异常而非通用的 except Exception, 以便针对每种失败模式做出适当响应:过期时重新认证、签名失败时告警、网络超时时重试。
从 API 响应中解码 JWT
import jwt
import httpx # or requests
TOKEN_ENDPOINT = "https://auth.example.com/oauth/token"
SECRET_KEY = "shared-webhook-signing-key"
def get_and_decode_token() -> dict:
"""Fetch an access token from the auth server and decode it."""
try:
response = httpx.post(
TOKEN_ENDPOINT,
data={
"grant_type": "client_credentials",
"client_id": "svc_order_processor",
"client_secret": "cs_9f3a7b2e",
},
timeout=10.0,
)
response.raise_for_status()
except httpx.HTTPError as exc:
raise RuntimeError(f"Token request failed: {exc}") from exc
token_data = response.json()
access_token = token_data["access_token"]
try:
payload = jwt.decode(
access_token,
SECRET_KEY,
algorithms=["HS256"],
audience="https://api.example.com",
)
return payload
except jwt.InvalidTokenError as exc:
raise RuntimeError(f"Invalid token from auth server: {exc}") from exc
claims = get_and_decode_token()
print(f"Service: {claims['sub']}, Scopes: {claims.get('scope', 'none')}")从文件中解码 JWT
import jwt
from pathlib import Path
from datetime import datetime, timezone
TOKEN_PATH = Path("/var/run/secrets/service-account-token")
PUBLIC_KEY_PATH = Path("/etc/ssl/auth/public_key.pem")
def decode_token_from_file() -> dict:
"""Read a JWT from a file, verify with a PEM public key."""
try:
token = TOKEN_PATH.read_text().strip()
public_key = PUBLIC_KEY_PATH.read_text()
except FileNotFoundError as exc:
raise RuntimeError(f"Missing file: {exc.filename}") from exc
try:
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience="https://internal-api.example.com",
)
except jwt.ExpiredSignatureError:
exp_time = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["RS256"],
).get("exp", 0)
expired_at = datetime.fromtimestamp(exp_time, tz=timezone.utc)
raise RuntimeError(f"Token expired at {expired_at.isoformat()}")
except jwt.InvalidTokenError as exc:
raise RuntimeError(f"Token verification failed: {exc}") from exc
return payload
claims = decode_token_from_file()
print(f"Subject: {claims['sub']}, Issuer: {claims['iss']}")命令行 JWT 解码
有时你只需要在终端查看一个令牌,不想专门编写脚本。 也许你在调试 OAuth 流程,想看看 Authorization 头部里有什么,或者从浏览器开发者工具中拿到了一个令牌,想检查它的过期时间。 Python 的 -c 标志可以将这一切变成单行命令。将令牌通过管道传入,即可获得格式化 JSON 格式的声明, 无需脚本文件,无需虚拟环境。
# Decode JWT payload from clipboard or variable (no verification)
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.sig" \
| python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
payload = token.split('.')[1]
payload += '=' * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
"
# {
# "sub": "usr_8f2a",
# "role": "admin"
# }# Decode JWT header to check algorithm and key ID
echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InNpZy0xNzI2In0.payload.sig" \
| python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
header = token.split('.')[0]
header += '=' * (-len(header) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(header)), indent=2))
"
# {
# "alg": "RS256",
# "kid": "sig-1726"
# }# If PyJWT is installed, verify and decode in one step
python3 -c "
import jwt, sys, json
token = sys.argv[1]
payload = jwt.decode(token, options={'verify_signature': False}, algorithms=['HS256'])
print(json.dumps(payload, indent=2))
" "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.sig"如果不想使用终端,可以将令牌粘贴到ToolDeck JWT 解码器, 即可立即查看头部、载荷和签名验证状态。
python-jose 及其他替代方案
python-jose 是一个替代 JWT 库,原生支持 JWS、JWE(加密令牌)和 JWK。 如果你的应用需要处理加密 JWT(JWE)——即载荷本身被加密而非仅签名—— python-jose 是正确的选择,因为 PyJWT 完全不支持 JWE。 该库还内置了 JWKS 密钥集处理,这简化了与 Auth0、Okta 或 Keycloak 等 会暴露轮换密钥集的身份提供商的集成。其解码接口与 PyJWT 几乎完全相同, 因此两者之间的迁移只需最少的代码改动:
# pip install python-jose[cryptography]
from jose import jwt as jose_jwt
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInNjb3BlIjoib3JkZXJzOnJlYWQifQ.signature"
# Verified decode — same pattern as PyJWT
payload = jose_jwt.decode(
token,
"signing-secret-key",
algorithms=["HS256"],
audience="https://api.example.com",
)
print(payload)
# {'sub': 'usr_8f2a', 'scope': 'orders:read'}
# Unverified decode
claims = jose_jwt.get_unverified_claims(token)
header = jose_jwt.get_unverified_header(token)
print(f"Algorithm: {header['alg']}, Subject: {claims['sub']}")我的建议:从 PyJWT 开始。它覆盖了 95% 的 JWT 使用场景,社区最活跃,API 简洁。 如果需要 JWE 支持或更偏好其 JWKS 处理方式,再切换到 python-jose。 另一个值得一提的选择是 Authlib, 它将 JWT 处理集成在一个更大的 OAuth/OIDC 框架中。如果你的项目已经在使用 Authlib 处理 OAuth 客户端流程, 其 authlib.jose.jwt 模块可以避免引入第二个 JWT 依赖。否则,仅为令牌解码引入这么重的依赖并不划算。
带语法高亮的终端输出
在终端查看原始 JWT 声明进行快速检查没什么问题,但如果你频繁调试令牌载荷 (我在构建内部认证网关期间每天都在做这件事),彩色输出会带来显著改善。 字符串、数字、布尔值和 null 值以不同颜色渲染,这意味着你不需要逐字符阅读, 就能一眼发现缺失的权限或错误的过期时间戳。
rich 库(pip install rich) 提供了 print_json 函数,接受 JSON 字符串或 Python 字典,并将其带完整语法高亮输出到终端。 结合 PyJWT 使用,只需两行代码即可实现 JWT 检查工作流:
# pip install rich PyJWT
import jwt
from rich import print_json
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbIm9yZGVyczpyZWFkIiwicmVmdW5kczpjcmVhdGUiXSwiZXhwIjoxNzExODE1NjAwfQ.sig"
payload = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["HS256"]
)
# Colorized, indented JSON output in the terminal
print_json(data=payload)
# {
# "sub": "usr_8f2a", ← strings in green
# "role": "admin",
# "permissions": [
# "orders:read",
# "refunds:create"
# ],
# "exp": 1711815600 ← numbers in cyan
# }rich 的输出包含 ANSI 转义码。不要将其写入文件或从 API 端点返回—— 它仅用于终端显示。需要纯文本输出时,请使用 json.dumps()。批量处理大量令牌
JWT 令牌本身很小(通常不超过 2 KB),但在某些场景下需要批量处理。 安全事件后的审计日志分析、切换认证提供商时的会话迁移脚本、 需要证明过去 90 天内签发的每个令牌都使用了正确密钥的合规批量验证—— 如果你有一个包含数万个令牌的 NDJSON 日志文件, 逐行处理可以避免将整个文件加载到内存中,并支持增量报告结果。
批量验证审计日志中的令牌
import jwt
import json
from pathlib import Path
SECRET_KEY = "audit-log-signing-key"
def validate_token_log(log_path: str) -> dict:
"""Process an NDJSON file where each line has a 'token' field.
Returns counts of valid, expired, and invalid tokens.
"""
stats = {"valid": 0, "expired": 0, "invalid": 0}
with open(log_path) as fh:
for line_num, line in enumerate(fh, 1):
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
token = record["token"]
except (json.JSONDecodeError, KeyError):
stats["invalid"] += 1
continue
try:
jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
stats["valid"] += 1
except jwt.ExpiredSignatureError:
stats["expired"] += 1
except jwt.InvalidTokenError:
stats["invalid"] += 1
return stats
result = validate_token_log("auth-events-2026-03.ndjson")
print(f"Valid: {result['valid']}, Expired: {result['expired']}, Invalid: {result['invalid']}")
# Valid: 14832, Expired: 291, Invalid: 17从 NDJSON 令牌导出文件中提取声明
import base64
import json
from datetime import datetime, timezone
def extract_claims_stream(input_path: str, output_path: str):
"""Read tokens line by line, decode payloads, write flattened claims."""
with open(input_path) as infile, open(output_path, "w") as outfile:
for line in infile:
line = line.strip()
if not line:
continue
record = json.loads(line)
token = record.get("access_token", "")
parts = token.split(".")
if len(parts) != 3:
continue
payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
claims = json.loads(base64.urlsafe_b64decode(payload_b64))
# Flatten into an audit-friendly record
flat = {
"timestamp": record.get("timestamp"),
"subject": claims.get("sub"),
"issuer": claims.get("iss"),
"expired_at": datetime.fromtimestamp(
claims.get("exp", 0), tz=timezone.utc
).isoformat(),
}
outfile.write(json.dumps(flat) + "\n")
extract_claims_stream("token-audit.ndjson", "claims-extract.ndjson")multiprocessing.Pool 将验证任务分发到多个核心, 因为每个令牌的处理是相互独立的。常见错误
以下四种错误在代码审查和 Stack Overflow 问题中反复出现。 每种都很容易犯,而且 PyJWT 给出的错误信息并不总能直接指向根本原因。 仅包名混淆这一个问题,就曾在有人安装了错误的库并得到完全意外的 API 时, 浪费了数小时的调试时间。
问题: 运行 pip install jwt 或 pip install python-jwt 会安装完全不同的包。import jwt 随后会失败,或者给你一个完全陌生的 API。
解决方案: 始终使用 pip install PyJWT 安装。导入时使用 import jwt。通过 pip show PyJWT 确认安装了正确的包。
pip install jwt # or pip install python-jwt import jwt # wrong package — different API entirely
pip install PyJWT import jwt # correct — this is PyJWT print(jwt.__version__) # 2.x
问题: 在 PyJWT 1.x 中,algorithms 是可选的,默认允许任意算法。这造成了安全漏洞,攻击者可以设置 alg: none 来绕过验证。PyJWT 2.x 现在在缺少 algorithms 时会抛出 DecodeError。
解决方案: 始终将 algorithms 作为明确的列表传入。只使用你的应用实际签发令牌所用的算法。
# PyJWT 2.x — this raises DecodeError payload = jwt.decode(token, SECRET_KEY) # DecodeError: algorithms must be specified
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
问题: 向 jwt.decode() 传入字符串密钥并使用 algorithms=["RS256"] 时会抛出 InvalidSignatureError。RS256 需要 PEM 格式的公钥,而非共享的字符串密钥。
解决方案: 从文件或环境变量加载 PEM 公钥。安装 cryptography 包:pip install PyJWT cryptography。
# This fails — RS256 needs a public key, not a string secret payload = jwt.decode(token, "my-secret", algorithms=["RS256"]) # InvalidSignatureError
public_key = open("public_key.pem").read()
payload = jwt.decode(token, public_key, algorithms=["RS256"])问题: JWT 的 base64url 编码会去掉末尾的 = 字符。如果不修复填充,Python 的 base64.urlsafe_b64decode 会抛出 binascii.Error: Incorrect padding。
解决方案: 解码前添加填充:segment += '=' * (-len(segment) % 4)。该公式总能生成正确数量的填充字符(0、1、2 或 3 个)。
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect paddingimport base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64) # worksPyJWT 与替代方案对比
对于大多数 Python 应用来说,PyJWT 是最佳起点。它涵盖了 HMAC,以及(配合 cryptography 后端)RSA 和 EC 签名验证。 如果需要 JWE(加密令牌),请切换到 python-jose 或 Authlib。手动 base64 解码适用于调试, 但不提供任何安全保证。
以下是我选择各方案的依据:标准 Web 服务做 HS256 或 RS256 验证时使用 PyJWT; 架构涉及加密令牌或 JWKS 轮换时使用 python-jose; 在无法使用 pip 的环境中(CI 容器、受限的生产主机、希望减少依赖的 AWS Lambda 冷启动)进行快速检查时使用手动 base64; 项目已经使用 Authlib 处理 OAuth 客户端流程、不想引入另一个 JWT 库时使用 Authlib。
如需无代码替代方案,将任意令牌粘贴到JWT 解码器, 即可查看解码后的头部和载荷,以及声明验证反馈。
常见问题
如何在 Python 中不验证签名地解码 JWT?
向 jwt.decode() 传入 options={"verify_signature": False} 和 algorithms=["HS256"]。这将返回载荷字典而不检查签名。仅在需要检查内容时使用此方式——例如在获取正确公钥之前读取声明,或在开发环境中调试。对于任何涉及授权决策的令牌,绝对不要跳过验证。
import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
payload = jwt.decode(
token,
options={"verify_signature": False},
algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}PyJWT 和 python-jwt 有什么区别?
PyJWT(pip install PyJWT,import jwt)是 Python 最流行的 JWT 库,每月下载量超过 8000 万次。python-jwt(pip install python_jwt)是另一个使用率极低、API 完全不同的独立库。如果你在别人的代码中看到 import jwt,他们用的是 PyJWT。这种命名混乱来自 PyPI 包名(PyJWT)与导入名称(jwt)不一致。除非有特殊原因,否则请使用 PyJWT。
如何在 Python 中解码 RS256 格式的 JWT?
同时安装 PyJWT 和 cryptography 后端:pip install PyJWT cryptography。然后将 PEM 格式的公钥作为 key 参数传入,并设置 algorithms=["RS256"]。PyJWT 会将 RSA 签名验证委托给 cryptography 库处理。如果未安装 cryptography 包,PyJWT 在使用 RSA 或 EC 算法时会抛出错误。
import jwt
public_key = open("public_key.pem").read()
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"],
audience="https://api.example.com"
)为什么 PyJWT 会抛出 ExpiredSignatureError?
PyJWT 默认会检查 exp(过期时间)声明。如果当前 UTC 时间超过了 exp 时间戳,则会抛出 jwt.ExpiredSignatureError。可以通过 leeway 参数添加时钟偏差容忍度:jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)),这将提供 30 秒的宽限期。如需完全禁用过期检查(不建议在生产环境使用),可以传入 options={"verify_exp": False}。
import jwt
from datetime import timedelta
try:
payload = jwt.decode(token, secret, algorithms=["HS256"], leeway=timedelta(seconds=30))
except jwt.ExpiredSignatureError:
print("Token has expired — prompt re-authentication")在 Python 中不使用任何库能读取 JWT 声明吗?
可以。将令牌按点号分割,取第二段(载荷),补充 = 字符使其长度为 4 的倍数,然后进行 base64url 解码并解析 JSON。这样可以获取声明字典,但不会验证签名。在无法安装 PyJWT 的受限环境中,或者编写快速调试脚本时,这种方式非常有用。
import base64, json
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.signature"
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4) # fix padding
claims = json.loads(base64.urlsafe_b64decode(payload_b64))
print(claims) # {'sub': 'usr_8f2a'}如何使用 PyJWT 验证 audience 声明?
向 jwt.decode() 传入 audience 参数:jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com")。PyJWT 会将令牌中的 aud 声明与你提供的值进行比较。如果令牌没有 aud 声明,或值不匹配,则会抛出 jwt.InvalidAudienceError。如果你的服务需要接受面向多个 API 的令牌,也可以传入可接受的 audience 列表。
import jwt
payload = jwt.decode(
token,
secret,
algorithms=["HS256"],
audience=["https://api.example.com", "https://admin.example.com"]
)相关工具
Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.
Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.