2. JWT签发与基础验证实现
JWT(JSON Web )是目前分布式系统中最主流的签发格式。它将用户身份元数据编码为自包含的载荷,并通过数字签名确保完整性与真实性。本章将手把手实现HS256(对称)和RS256(非对称)两种签名算法的签发与验证,并编写可断言的单元测试,为后续自动化授权测试奠定基础。
2.1 原理概述:HS256与RS256
| 对比维度 | HS256(对称) | RS256(非对称) |
|---|---|---|
| 密钥类型 | 单一密钥(Secret),签发和验证使用同一密钥 | 密钥对(Private/Public),签发用私钥,验证用公钥 |
| 性能 | 签名与验证速度极快(HMAC计算) | 签名较慢(大数模幂运算),验证较快 |
| 安全模型 | 密钥必须保密,验证方需知晓密钥 | 私钥保密,公钥可以公开分发 |
| 适用场景 | 单一服务内部,或强信任网关环境 | 微服务架构、第三方开放API、跨组织信任链 |
| 常见算法标识 | HS256 |
RS256 |
标准化选择:在自动化授权测试中,RS256是更推荐的选择,因为验证方只持有公钥,即使被攻破也无法签发新的,大大降低了横向攻击面。
2.2 核心Claims格式规范
JWT的负载(Payload)由一组“声明(Claims)”组成。遵循标准化的Claims字段可以确保跨平台的互操作性:
- iss(Issuer):签发者,标识签发的服务方。
- sub(Subject):主题,通常是用户的唯一标识(如user_id)。
- aud(Audience):受众,接收的服务。
- exp(Expiration Time):过期时间,使用UTC时间戳(秒级或毫秒级)。
- nbf(Not Before):不早于时间,在此时间之前无效。
- iat(Issued At):签发时间,用于刷新和时效性审计。
时区陷阱:
exp和iat必须使用datetime.timezone.utc生成,禁止使用本地时间或未指定时区的datetime.now(),否则在跨时区验证时会直接抛出签名异常或过期异常。2.3 签发实现流程
下图展示了JWT从签发到验证的完整生命周期,其中重点标出了签名算法和Claims校验的关键路径:
2.4 代码实战:使用PyJWT签发与验证
本节使用Python的PyJWT库演示核心实现。所有示例均使用UTC时间并包含完整的错误处理。
2.4.1 HS256实现
import jwt
import datetime
# 配置
SECRET_KEY = "my-super-secret-key-2024"
payload = {
"sub": "user_12345",
"name": "John Doe",
"role": "editor",
"iat": datetime.datetime.now(datetime.timezone.utc),
"exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=1)
}
# 签发
= jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(f"HS256 : {}")
# 验证
try:
decoded = jwt.decode(, SECRET_KEY, algorithms=["HS256"])
print(f"验证成功,用户ID: {decoded['sub']}")
except jwt.ExpiredSignatureError:
print("已过期")
except jwt.InvalidSignatureError:
print("签名无效")
except jwt.InvalidTokenError:
print("格式错误")
2.4.2 RS256实现
import jwt
import datetime
# 从文件读取密钥对(实际生产环境应使用密钥管理服务)
with open("private.pem", "r") as f:
PRIVATE_KEY = f.read()
with open("public.pem", "r") as f:
PUBLIC_KEY = f.read()
payload = {
"sub": "user_67890",
"iat": datetime.datetime.now(datetime.timezone.utc),
"exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=8)
}
# 使用私钥签发
= jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")
print(f"RS256 : {}")
# 使用公钥验证
try:
decoded = jwt.decode(, PUBLIC_KEY, algorithms=["RS256"])
print(f"RS256 验证通过,Subject: {decoded['sub']}")
except jwt.ExpiredSignatureError:
print("已过期")
except jwt.PyJWTError as e:
print(f"验证失败: {e}")
2.4.3 单元测试编写
自动化授权测试的核心在于可重复、可断言。以下是一个基于pytest的单元测试套件,覆盖了签名有效性、过期时间和负载完整性三个关键维度:
import jwt
import datetime
import pytest
SECRET = "test-secret-for-unit-test"
def test_hs256_signature_valid():
"""测试HS256签名验证通过"""
= jwt.encode({"sub": "1"}, SECRET, algorithm="HS256")
decoded = jwt.decode(, SECRET, algorithms=["HS256"])
assert decoded["sub"] == "1"
def test_expired__raises_error():
"""测试过期的正确异常处理"""
payload = {
"sub": "1",
"exp": datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=1)
}
= jwt.encode(payload, SECRET, algorithm="HS256")
with pytest.raises(jwt.ExpiredSignatureError):
jwt.decode(, SECRET, algorithms=["HS256"])
def test_invalid_signature_fails():
"""测试错误密钥导致的签名验证失败"""
= jwt.encode({"sub": "1"}, "secret-key-a", algorithm="HS256")
with pytest.raises(jwt.InvalidSignatureError):
jwt.decode(, "secret-key-b", algorithms=["HS256"])
def test_iat_not_before():
"""测试iat(签发时间