JWT 安全架构与防御全景
JWT 防御的核心原则是不信任来自 Token Header 的任何控制指令,也不把无状态 Token 当作永久可信权限凭证。签发与验证双方必须由服务端配置决定算法、密钥来源与期望受众,任何对 alg、kid、jku、x5u、typ 或 role 的盲从都会演变为签名绕过或越权。
四大风险面与防御索引
| 编号 | 对应防御文档 | 防御目标 | 核心控制基线 |
|---|---|---|---|
| 01 | 01-alg-none-defense.md | 无签名绕过 | 服务端硬编码算法白名单,彻底禁止 none/空算法/算法数组 |
| 02 | 02-algorithm-confusion-defense.md | RS/ES → HS 算法混淆 | 密钥类型与算法强绑定,非对称公钥绝不进入对称 HMAC 分支 |
| 03 | 03-weak-key-bruteforce-defense.md | 弱 HMAC 密钥爆破 | >= 256-bit CSPRNG 高熵密钥,或迁移至 RS256/ES256/EdDSA |
| 04 | 04-kid-injection-defense.md | kid 路径/SQL/命令注入 | kid 仅作静态映射表键或严格正则白名单查询,禁用拼接 |
| 05 | 05-jku-x5u-abuse-defense.md | jku/x5u/x5c 密钥源劫持 | 禁用 Token 内自带远程 URL,仅从本地静态配置的 JWKS 端点拉取 |
| 06 | 06-claim-missing-defense.md | Claim 缺失与 Token 混用 | 强制验证 exp/nbf/iss/aud/typ,敏感权限二次查库 |
| 07 | 07-theft-replay-defense.md | 窃取、重放与持久化 | HttpOnly+SameSite Cookie、CSP 防 XSS、jti 吊销列表与 Refresh 轮换 |
| 08 | 08-cve-library-defense.md | 依赖库 CVE 与解析差异 | 锁定安全库版本、统一网关与业务解析器、禁止 key callback 返回空值 |
| 09 | 09-toolchain-operations-defense.md | 工程化验证与发布门禁 | 将 JWT 拒绝矩阵接入 CI/CD 回归套件,验证所有失败样本同源拒绝 |
防御工作流
text
1. 入口格式预检(三段式结构 + 长度上限)
└─ 拒绝非三段结构、空签名、过大 Header 或嵌套非法 JSON 类型
2. 服务端固定算法与密钥解析(Ignore Untrusted Header Directives)
└─ 从服务端配置读取 EXPECTED_ALG 与静态 KeyStore;忽略 jku/x5u,严格白名单匹配 kid
3. 密码学验签与异常收敛
└─ 使用常量时间比对或成熟库安全入口验签,统一返回通用 401,不泄露库指纹
4. 全量标准 Claim 校验
└─ 强制校对 exp、nbf、iat、iss、aud 与 typ(例如区分 at+jwt 与 ID Token)
5. 状态撤销与上下文检查
└─ 校验 jti 未在吊销黑名单中,敏感操作回源校验用户状态与最新角色权限统一服务端 JWT 验证基线(Python)
python
import base64
import hashlib
import hmac
import json
import time
from typing import Any
class JWTVerificationError(Exception):
"""统一认证拒绝异常,对外只暴露通用 401 错误。"""
def _b64u_decode(segment: str) -> bytes:
if not segment:
raise JWTVerificationError("empty segment")
padded = segment + "=" * (-len(segment) % 4)
return base64.urlsafe_b64decode(padded.encode("ascii"))
def verify_hs256_baseline(
token: str,
keystore: dict[str, bytes],
expected_iss: str,
expected_aud: str,
revoked_jtis: set[str],
now: int | None = None,
) -> dict[str, Any]:
"""示范防御基线:固定 HS256、静态 kid 查表、强制完整 Claim 与吊销检查。"""
parts = token.split(".")
if len(parts) != 3 or not parts[2]:
raise JWTVerificationError("malformed token or empty signature")
h_b64, p_b64, s_b64 = parts
try:
header = json.loads(_b64u_decode(h_b64))
payload = json.loads(_b64u_decode(p_b64))
except Exception as exc:
raise JWTVerificationError("invalid JSON encoding") from exc
if not isinstance(header, dict) or not isinstance(payload, dict):
raise JWTVerificationError("header/payload must be JSON objects")
# 1. 严格锁定服务端期望算法与类型
if header.get("alg") != "HS256":
raise JWTVerificationError("algorithm mismatch")
if header.get("typ") not in ("JWT", "at+jwt"):
raise JWTVerificationError("unexpected token typ")
if any(forbidden in header for forbidden in ("jku", "x5u", "jwk", "x5c")):
raise JWTVerificationError("inline or remote key headers forbidden")
# 2. kid 严格静态字典查找(拒绝路径/SQL/null/数组)
kid = header.get("kid")
if not isinstance(kid, str) or kid not in keystore:
raise JWTVerificationError("unknown key id")
secret = keystore[kid]
if len(secret) < 32:
raise JWTVerificationError("insufficient key length")
# 3. 常量时间签名验证
signing_input = f"{h_b64}.{p_b64}".encode("ascii")
expected_sig = hmac.new(secret, signing_input, hashlib.sha256).digest()
actual_sig = _b64u_decode(s_b64)
if not hmac.compare_digest(expected_sig, actual_sig):
raise JWTVerificationError("signature verification failed")
# 4. 强制 Claim 校验
ts = int(time.time()) if now is None else now
for req in ("sub", "iss", "aud", "exp", "nbf", "jti"):
if req not in payload:
raise JWTVerificationError(f"missing required claim: {req}")
if payload["iss"] != expected_iss:
raise JWTVerificationError("issuer mismatch")
aud = payload["aud"]
if not (aud == expected_aud or (isinstance(aud, list) and expected_aud in aud)):
raise JWTVerificationError("audience mismatch")
if not isinstance(payload["exp"], (int, float)) or ts >= payload["exp"]:
raise JWTVerificationError("token expired")
if not isinstance(payload["nbf"], (int, float)) or ts < payload["nbf"]:
raise JWTVerificationError("token not yet valid")
if payload["jti"] in revoked_jtis:
raise JWTVerificationError("token revoked")
return payload验证与判定标准
运行下方测试脚本确认 00-overview.md 中的典型绕过路径(无签名、空签名、非法 kid、jku 注入、跨 aud/iss、过期与已吊销 jti)在基线验证器下全部失败:
python
def _b64u_encode(obj: dict[str, Any]) -> str:
raw = json.dumps(obj, separators=(",", ":")).encode("utf-8")
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii")
def run_overview_defense_checks() -> None:
secret = b"0123456789abcdef0123456789abcdef"
keystore = {"key-v1": secret}
now = 1700000000
good_header = {"alg": "HS256", "typ": "at+jwt", "kid": "key-v1"}
good_payload = {
"sub": "u-100",
"iss": "https://auth.example.com",
"aud": "api.example.com",
"nbf": now - 10,
"exp": now + 600,
"jti": "jti-ok",
}
def sign(h: dict[str, Any], p: dict[str, Any]) -> str:
msg = f"{_b64u_encode(h)}.{_b64u_encode(p)}"
sig = base64.urlsafe_b64encode(hmac.new(secret, msg.encode(), hashlib.sha256).digest()).rstrip(b"=").decode()
return f"{msg}.{sig}"
valid_tok = sign(good_header, good_payload)
assert verify_hs256_baseline(valid_tok, keystore, "https://auth.example.com", "api.example.com", set(), now)["sub"] == "u-100"
bad_tokens = [
f"{_b64u_encode({'alg': 'none', 'typ': 'at+jwt'})}.{_b64u_encode(good_payload)}.",
sign({**good_header, "jku": "https://evil.example/jwks.json"}, good_payload),
sign({**good_header, "kid": "../../dev/null"}, good_payload),
sign(good_header, {**good_payload, "aud": "other-service"}),
sign(good_header, {**good_payload, "exp": now - 1}),
sign(good_header, {**good_payload, "jti": "jti-revoked"}),
]
for tok in bad_tokens:
try:
verify_hs256_baseline(tok, keystore, "https://auth.example.com", "api.example.com", {"jti-revoked"}, now)
except JWTVerificationError:
pass
else:
raise AssertionError(f"bad token unexpectedly accepted: {tok}")
print("PASS: all JWT overview defense checks passed")
if __name__ == "__main__":
run_overview_defense_checks()判定标准:
- 零 Header 控制面:所有包含
alg=none、jku/x5u/jwk/x5c或未登记kid的 Token 统一返回401。 - 完整生命周期封闭:过期、未生效、跨受众、跨签发者及已登出吊销
jti均无法通过验证。 - 错误文本收敛:网关与服务端对外统一输出固定错误体与状态码,不回显库名称或解析堆栈。
MCP 工具映射
| 防御阶段 | MCP 工具 | 说明 |
|---|---|---|
| 防御路线选择 | kb_router | 按 JWT、alg:none、kid、jku、Claim 搜索对应子防御文档 |
| 规程读取 | kb_read_file | 读取 01–09 各篇针对性加固指南 |
| 目录编排 | kb_catalog | 核对 02-auth/jwt/ 下全部防御条目与配对关系 |