4. JWT 生成与解析:使用 Python PyJWT 库

在分布式鉴权系统中,JWT 的生成与解析是核心操作。Python 生态中最成熟的 JWT 库是 PyJWT,它提供了简洁的 API 用于创建、解码和验证 Token。本节将带你从零开始,掌握使用 PyJWT 进行 Token 生命周期管理的完整流程。

4.1 环境准备与库安装

首先,确保你的 Python 环境(3.8+)已安装 PyJWT 库。推荐同时安装 cryptography 包以支持 RSA/ECDSA 非对称算法。

# 安装 PyJWT(支持 HMAC 对称算法)
pip install pyjwt

# 安装 cryptography(支持 RS256/ES256 等非对称算法)
pip install cryptography
版本提示: 本文基于 PyJWT 2.8+ 版本。旧版本(1.x)的 decode() 方法默认不验证签名,存在安全风险,请务必升级。

4.2 生成 Token(编码)

生成 JWT 的核心是调用 jwt.encode() 方法。你需要准备三要素:Payload(载荷)、Secret Key(密钥)、Algorithm(算法)。

4.2.1 使用 HS256 对称算法生成 Token

import jwt
import datetime

# 1. 定义密钥(生产环境应从环境变量或密钥管理服务获取)
SECRET_KEY = "your-256-bit-secret-key-here"

# 2. 构建 Payload(标准声明 + 私有声明)
payload = {
    # 标准注册声明
    "iss": "auth-service",           # 签发者
    "sub": "user_12345",             # 主题(用户ID)
    "aud": "api-gateway",            # 接收方
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=2),  # 过期时间
    "iat": datetime.datetime.utcnow(),  # 签发时间
    "nbf": datetime.datetime.utcnow(),  # 生效时间

    # 私有声明(自定义数据)
    "username": "alice",
    "role": "admin",
    "permissions": ["read:users", "write:users"]
}

# 3. 生成 Token
token = jwt.encode(
    payload=payload,
    key=SECRET_KEY,
    algorithm="HS256"
)

print(f"生成的 JWT:\n{token}")

4.2.2 使用 RS256 非对称算法生成 Token

非对称算法使用私钥签名、公钥验证,适合微服务间跨信任域的场景。

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
import jwt, datetime

# 生成 RSA 密钥对(生产环境应使用预先生成的密钥文件)
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)

# 序列化私钥(PEM 格式)
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)

# 提取公钥
public_key = private_key.public_key()
public_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

# 使用私钥签名生成 Token
payload = {
    "sub": "user_67890",
    "role": "viewer",
    "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}

token_rs256 = jwt.encode(
    payload=payload,
    key=private_pem,
    algorithm="RS256"
)

print(f"RS256 Token:\n{token_rs256}")

4.3 解析 Token(解码与验证)

解析 Token 包含两个步骤:解码(Decode) 和 验证(Verify)。PyJWT 的 jwt.decode() 方法会同时完成这两步。

4.3.1 解析 HS256 Token

try:
    # 解码并验证签名
    decoded_payload = jwt.decode(
        jwt=token,                    # 待解析的 Token 字符串
        key=SECRET_KEY,               # 对称密钥(与生成时相同)
        algorithms=["HS256"],         # 允许的算法列表
        audience="api-gateway",       # 验证 aud 声明
        issuer="auth-service",        # 验证 iss 声明
        options={
            "verify_exp": True,       # 验证过期时间
            "verify_iat": True,       # 验证签发时间
            "require": ["exp", "sub", "aud"]  # 强制要求这些声明存在
        }
    )
    print(f"解析成功,Payload: {decoded_payload}")
    print(f"用户: {decoded_payload['username']}, 角色: {decoded_payload['role']}")

except jwt.ExpiredSignatureError:
    print("错误:Token 已过期")
except jwt.InvalidAudienceError:
    print("错误:接收方不匹配")
except jwt.InvalidIssuerError:
    print("错误:签发者不匹配")
except jwt.InvalidTokenError as e:
    print(f"错误:无效 Token - {e}")

4.3.2 解析 RS256 Token

try:
    # 使用公钥验证签名
    decoded_rs256 = jwt.decode(
        jwt=token_rs256,
        key=public_pem,               # 公钥(非对称验证)
        algorithms=["RS256"],
        options={"verify_exp": True}
    )
    print(f"RS256 解析成功: {decoded_rs256}")

except jwt.PyJWTError as e:
    print(f"验证失败: {e}")
安全警告: 永远不要使用 jwt.decode(token, verify=False) 或旧版 jwt.decode(token, key=None)。这会跳过签名验证,攻击者可以伪造任意 Token 绕过鉴权。

4.4 签名验证流程详解

下图展示了 PyJWT 在 decode() 方法内部执行的完整验证流水线:

PyJWT decode() 验证流水线 输入 Token 字符串 步骤1: Base64URL 解码 Header 步骤2: 检查算法是否在白名单中 步骤3: 使用密钥验证签名 步骤4: 验证 exp/aud/iss/nbf 等声明 输出: 解析后的 Payload 抛出异常 任何一步验证失败都会抛出 jwt.PyJWTError 子类异常,不会返回部分解析结果

4.5 常用算法对比与选择

算法 类型 密钥管理 性能 适用场景
HS256 对称(HMAC+SHA256) 单一密钥,需安全共享 高(纯哈希运算) 单体应用、内部服务间信任
RS256 非对称(RSA+SHA256) 私钥签名,公钥验证 中(RSA 运算较慢) 微服务、第三方集成、跨信任域
ES256 非对称(ECDSA+P-256) 私钥签名,公钥验证 高(椭圆曲线更高效) 移动端、IoT、高性能要求场景
选型建议: 对于大多数分布式系统,推荐使用 RS256 或 ES256。非对称算法允许认证服务(持有私钥)独立于资源服务(持有公钥),公钥可以安全地通过配置中心或 JWKS 端点分发,无需共享敏感密钥。

4.6 常见错误与调试

  • ExpiredSignatureError:Token 已过期。检查系统时间是否同步(NTP),或延长 exp 时间。
  • InvalidSignatureError:签名不匹配。检查密钥是否一致,或 Token 是否被篡改。
  • DecodeError:Token 格式错误。确认 Token 是否为三段式(header.payload.signature),且使用 Base64URL 编码。
  • InvalidAlgorithmError:算法不匹配。检查 algorithms 参数是否包含 Token header 中的算法。
  • MissingRequiredClaimError:缺少必要声明。检查 require 参数中指定的声明是否在 Payload 中存在。

4.7 完整实战:Token 生命周期管理

以下代码演示了从生成到验证的完整闭环,包含错误处理和最佳实践:

import jwt
import datetime
from typing import Dict, Optional

class JWTManager:
    """JWT 管理器(HS256 示例)"""

    def __init__(self, secret_key: str, algorithm: str = "HS256"):
        self.secret_key = secret_key
        self.algorithm = algorithm

    def create_token(self, user_id: str, role: str,
                     expire_hours: int = 2) -> str:
        """生成访问 Token"""
        payload = {
            "sub": user_id,
            "role": role,
            "iat": datetime.datetime.utcnow(),
            "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=expire_hours)
        }
        return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)

    def verify_token(self, token: str) -> Optional[Dict]:
        """验证并解析 Token,成功返回 Payload,失败返回 None"""
        try:
            payload = jwt.decode(
                token,
                self.secret_key,
                algorithms=[self.algorithm],
                options={"require": ["sub", "exp"]}
            )
            return payload
        except jwt.ExpiredSignatureError:
            print("Token 已过期,请重新登录")
        except jwt.InvalidTokenError as e:
            print(f"Token 无效: {e}")
        return None

# 使用示例
manager = JWTManager("my-secret-key-12345")
token = manager.create_token("user_001", "admin")
print(f"生成 Token: {token[:50]}...")

payload = manager.verify_token(token)
if payload:
    print(f"验证通过: 用户 {payload['sub']}, 角色 {payload['role']}")

4.8 安全最佳实践

  • 密钥强度:HS256 密钥至少 256 位(32 字节),使用 secrets.token_hex(32) 生成。
  • 过期时间:访问 Token 建议 15 分钟 - 2 小时;刷新 Token 可设置 7-30 天。
  • 算法白名单:decode() 时始终指定 algorithms 参数,防止算法混淆攻击。
  • 声明验证:验证 aud(接收方)和 iss(签发者)防止 Token 被重放到其他服务。
  • 密钥轮换:定期更换密钥,旧密钥保留一段缓冲期用于验证已签发的 Token。
生产环境提示: 不要将密钥硬编码在代码中。使用环境变量、Vault、AWS Secrets Manager 或 Kubernetes Secrets 管理密钥。对于非对称算法,使用 JWKS(JSON Web Key Set)端点动态分发公钥。