# 第九章:JWT 深入解析 > "JWT 不是银弹,但用对了就是利器。" ```{mermaid} mindmap root((JWT)) 结构 Header Payload Signature 签名算法 HS256 RS256 ES256 EdDSA 安全陷阱 alg none 密钥混淆 无法撤销 最佳实践 短过期 JWKS 轮换 Audience 验证 ``` ## 9.1 JWT 结构 JWT(JSON Web Token)由三部分组成,用点号分隔: ``` eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiJ1c2VyLTEyMyIsIm5hbWUiOiJXYWx0ZXIiLCJyb2xlcyI6WyJhZG1pbiJdLCJleHAiOjE3MDk1MTA0MDB9. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │←── Header ──→│←────────── Payload ──────────→│←── Signature ──→│ (Base64URL) (Base64URL) (Base64URL) ``` ### Header ```json { "alg": "RS256", // 签名算法 "typ": "JWT", // Token 类型 "kid": "key-2024" // 密钥 ID(用于 JWKS 轮换) } ``` ### Payload(Claims) ```json { "iss": "https://auth.example.com", // 签发者 "sub": "user-123", // 主体 "aud": "my-api", // 受众 "exp": 1709510400, // 过期时间 "nbf": 1709506800, // 生效时间 "iat": 1709506800, // 签发时间 "jti": "unique-token-id", // Token ID "name": "Walter", // 自定义 Claim "roles": ["admin", "editor"] // 自定义 Claim } ``` ## 9.2 签名算法选择 | 算法 | 类型 | 密钥 | 适用场景 | 推荐 | |------|------|------|---------|------| | HS256 | 对称 | 共享密钥 | 单体应用 | ⚠️ 简单场景 | | RS256 | 非对称 | RSA 密钥对 | 微服务 | ✅ 通用 | | ES256 | 非对称 | ECDSA P-256 | 高性能 | ✅ 推荐 | | EdDSA | 非对称 | Ed25519 | 最新 | ✅ 最佳性能 | ```python import jwt from cryptography.hazmat.primitives.asymmetric import rsa, ec from cryptography.hazmat.primitives import serialization # === RS256 签名 === private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() token = jwt.encode( {"sub": "user-123", "roles": ["admin"], "exp": 1709510400}, private_key, algorithm="RS256", headers={"kid": "key-2024"} ) # 验证(只需要公钥) decoded = jwt.decode( token, public_key, algorithms=["RS256"], audience="my-api", # 验证 audience issuer="https://auth.example.com" # 验证 issuer ) # === ES256 签名(更快、更小) === ec_private = ec.generate_private_key(ec.SECP256R1()) ec_token = jwt.encode({"sub": "user-123"}, ec_private, algorithm="ES256") ``` ## 9.3 JWT 安全陷阱 ### 陷阱 1:alg:none 攻击 ```python # ❌ 攻击者构造无签名的 JWT malicious_header = {"alg": "none", "typ": "JWT"} malicious_payload = {"sub": "admin", "roles": ["superadmin"]} # ✅ 防御:始终指定允许的算法 decoded = jwt.decode( token, public_key, algorithms=["RS256"], # 明确指定,拒绝 "none" ) ``` ### 陷阱 2:密钥混淆攻击 ```python # ❌ 攻击者用 RS256 的公钥作为 HS256 的密钥 # 如果服务端不限制算法,攻击者可以: # 1. 获取 RS256 公钥(公开的) # 2. 用公钥作为 HS256 密钥签名 # 3. 服务端用同一个公钥验证 HS256 → 通过! # ✅ 防御:严格限制算法 decoded = jwt.decode(token, public_key, algorithms=["RS256"]) # 只接受 RS256 ``` ### 陷阱 3:Token 无法撤销 JWT 一旦签发就无法撤销(除非等它过期)。解决方案: ```python import redis r = redis.Redis() def revoke_token(jti: str, exp: int): """将 Token 加入黑名单""" ttl = exp - int(time.time()) if ttl > 0: r.setex(f"revoked:{jti}", ttl, "1") def is_token_revoked(jti: str) -> bool: """检查 Token 是否已被撤销""" return r.exists(f"revoked:{jti}") def verify_token(token: str): payload = jwt.decode(token, public_key, algorithms=["RS256"]) if is_token_revoked(payload.get("jti", "")): raise Exception("Token has been revoked") return payload ``` ## 9.4 JWKS(JSON Web Key Set) JWKS 用于发布公钥,支持密钥轮换: ```json // GET https://auth.example.com/.well-known/jwks.json { "keys": [ { "kty": "RSA", "kid": "key-2024", "use": "sig", "alg": "RS256", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM...", "e": "AQAB" }, { "kty": "RSA", "kid": "key-2023", "use": "sig", "alg": "RS256", "n": "...", "e": "AQAB" } ] } ``` ```python from jwt import PyJWKClient # 自动从 JWKS 端点获取公钥 jwks_client = PyJWKClient("https://auth.example.com/.well-known/jwks.json") def verify_with_jwks(token: str): """使用 JWKS 验证 JWT(支持密钥轮换)""" signing_key = jwks_client.get_signing_key_from_jwt(token) return jwt.decode( token, signing_key.key, algorithms=["RS256"], audience="my-api", ) ``` ## 9.5 JWT vs Session | 维度 | JWT | Session | |------|-----|---------| | 存储位置 | 客户端 | 服务端 | | 状态 | 无状态 | 有状态 | | 扩展性 | 天然分布式 | 需要共享存储 | | 撤销 | 困难(需黑名单) | 简单(删除 Session) | | 大小 | 较大(含 Claims) | 小(Session ID) | | 适用场景 | 微服务、API | 传统 Web 应用 | ## 9.6 JWT 最佳实践清单 ``` ✅ 使用非对称算法(RS256/ES256) ✅ 设置短过期时间(15分钟-1小时) ✅ 验证所有标准 Claims(iss, aud, exp) ✅ 使用 JWKS 支持密钥轮换 ✅ 明确指定允许的算法列表 ✅ 包含 jti 用于撤销和防重放 ✅ 最小化 Payload(不放敏感数据) ✅ 使用 HTTPS 传输 ❌ 不要在 URL 中传递 JWT ❌ 不要在 JWT 中存储密码或密钥 ❌ 不要使用 HS256 的公开密钥 ❌ 不要忽略 exp 验证 ``` ## 9.7 小结 - JWT 由 Header.Payload.Signature 三部分组成 - 推荐使用 **RS256 或 ES256** 非对称签名算法 - 注意安全陷阱:alg:none、密钥混淆、无法撤销 - **JWKS** 支持密钥轮换,是生产环境必备 - JWT 适合微服务和 API 场景,Session 适合传统 Web 应用