JWT 的安全边界
JWT(RFC 7519)是接口与服务间常用的认证载体。它只做签名,不做加密:载荷是 Base64URL 编码而非密文,任何人都能解出内容,因此手机号、身份证号这类信息不应放进载荷。常见的实现错误集中在三处:服务端信任令牌头里声明的算法、HS 与 RS 算法混淆、密钥长期不轮换。这三类问题的共同点是攻击者能借此伪造令牌。
基本信息
| 规范 | RFC 7519;密钥分发常配合 JWKS |
|---|---|
| 推荐算法 | RS256 / ES256 等非对称算法,服务端白名单固定 |
| 必须校验 | 签名、iss、aud、exp,必要时校验 nbf 与 jti |
| 密钥管理 | 用 JWKS 配合 kid 支持轮换,避免硬编码密钥 |
| 载荷原则 | 只放非敏感声明,敏感数据由服务端查询获取 |
典型使用场景
- 前后端分离:接口用 JWT 承载会话,需限制有效期
- 服务间调用:内部服务用 JWT 做身份传递与授权
- 开放平台:第三方接入用非对称签名验签,公钥通过 JWKS 分发
- 单点登录联动:id_token 与访问令牌的校验逻辑需要区分
- 安全测试:把算法与声明校验作为固定测试项
PyJWT 校验示例
PyJWT 校验示例
import jwt
# 固定算法白名单,禁止接受客户端指定的算法
payload = jwt.decode(
token, key=public_key_pem,
algorithms=["RS256"],
audience="api.example.com", issuer="https://idp.example.com",
options={"require": ["exp", "iss", "aud"]})
# 密钥轮换:用 JWKS 客户端按 kid 自动取公钥
from jwt import PyJWKClient
signing_key = PyJWKClient("https://idp.example.com/.well-known/jwks.json").get_signing_key_from_jwt(token)风险与缓解
| 风险 | 说明 | 缓解 |
|---|---|---|
| alg=none | 伪造无签名令牌 | 服务端固定算法白名单 |
| HS/RS 混淆 | 用公钥当 HMAC 密钥伪造签名 | 按密钥类型限定算法族 |
| 密钥不轮换 | 泄露后影响面长期存在 | JWKS + kid 支持平滑轮换 |
| 载荷含敏感信息 | Base64 可解出明文 | 载荷不放敏感数据,改用服务端查询 |
历史漏洞多与「信任 alg 头」有关:务必在服务端白名单校验算法,不接受客户端指定算法,也不要因为旧客户端兼容而放宽。
令牌有效期与吊销要一起设计:短有效期加刷新令牌是常见做法,服务端吊销名单或版本号可用于强制下线。