jwt的工作过程
一个 JWT 通常长这样:
xxxxx.yyyyy.zzzzz
由三个部分组成:
Header.Payload.Signature
中间使用 . 分隔。
- Header
Header 描述 JWT 使用什么算法进行签名,以及 Token 类型。
| Header 参数 | 英文全称 | 中文含义 | 是否常见 | 示例 |
|---|---|---|---|---|
alg |
Algorithm | 签名/加密算法 | 核心 | HS256 |
typ |
Type | Token 类型 | 常见 | JWT |
cty |
Content Type | 内容类型 | 较少 | JWT |
kid |
Key ID | 密钥标识符 | 常见 | key-2026-01 |
jku |
JWK Set URL | JWK Set 地址 | 较少 | https://example.com/jwks.json |
jwk |
JSON Web Key | 直接提供公钥 | 较少 | { "kty":"RSA", ... } |
x5u |
X.509 URL | X.509 证书地址 | 较少 | https://example.com/cert.pem |
x5c |
X.509 Certificate Chain | X.509 证书链 | 较少 | ["MIIC..."] |
x5t |
X.509 Certificate SHA-1 Thumbprint | 证书 SHA-1 指纹 | 较少 | abc123... |
x5t#S256 |
X.509 Certificate SHA-256 Thumbprint | 证书 SHA-256 指纹 | 较少 | xyz789... |
crit |
Critical | 必须处理的扩展字段 | 较少 | ["exp"] |
b64 |
Base64url-Encode Payload | 是否对 Payload 做 Base64URL 编码 | 很少 | false |
header中的算法
alg |
全称 | 类型 | 签名方式 | 密钥 |
|---|---|---|---|---|
none |
None | 无签名 | 不签名 | 无 |
HS256 |
HMAC using SHA-256 | HMAC | HMAC-SHA256 | 对称 Secret |
HS384 |
HMAC using SHA-384 | HMAC | HMAC-SHA384 | 对称 Secret |
HS512 |
HMAC using SHA-512 | HMAC | HMAC-SHA512 | 对称 Secret |
RS256 |
RSASSA-PKCS1-v1_5 using SHA-256 | RSA | RSA + SHA-256 | 公钥/私钥 |
RS384 |
RSASSA-PKCS1-v1_5 using SHA-384 | RSA | RSA + SHA-384 | 公钥/私钥 |
RS512 |
RSASSA-PKCS1-v1_5 using SHA-512 | RSA | RSA + SHA-512 | 公钥/私钥 |
PS256 |
RSASSA-PSS using SHA-256 | RSA-PSS | RSA-PSS + SHA-256 | 公钥/私钥 |
PS384 |
RSASSA-PSS using SHA-384 | RSA-PSS | RSA-PSS + SHA-384 | 公钥/私钥 |
PS512 |
RSASSA-PSS using SHA-512 | RSA-PSS | RSA-PSS + SHA-512 | 公钥/私钥 |
ES256 |
ECDSA using P-256 and SHA-256 | ECDSA | ECDSA + SHA-256 | 公钥/私钥 |
ES384 |
ECDSA using P-384 and SHA-384 | ECDSA | ECDSA + SHA-384 | 公钥/私钥 |
ES512 |
ECDSA using P-521 and SHA-512 | ECDSA | ECDSA + SHA-512 | 公钥/私钥 |
EdDSA |
Edwards-curve Digital Signature Algorithm | EdDSA | Ed25519/Ed448 等 | 公钥/私钥 |
HMAC256是对成加密,RSA256是非对称加密,ES256是椭圆曲线非对称密钥
payload/claims
| 参数 | 英文全称 | 中文含义 | 是否必须 | 常见用途 | 示例 |
|---|---|---|---|---|---|
iss |
Issuer | 签发者 | 否 | 表示 JWT 是谁签发的 | "https://auth.example.com" |
sub |
Subject | 主题/主体 | 否 | 表示 JWT 代表谁,通常是用户 ID | "10001" |
aud |
Audience | 接收者/受众 | 否 | 表示 JWT 是给哪个服务使用的 | "api.example.com" |
exp |
Expiration Time | 过期时间 | 否 | Token 到什么时候失效 | 1786500000 |
nbf |
Not Before | 生效时间 | 否 | 在什么时间之前不能使用 | 1786490000 |
iat |
Issued At | 签发时间 | 否 | JWT 在什么时候生成 | 1786490000 |
jti |
JWT ID | JWT 唯一标识符 | 否 | 标识某一个具体 Token,可用于防重放/撤销 | "abc123456" |
应用自定义 Private Claims
这些不是 JWT 标准固定参数,由后端开发人员自己定义。
| 参数示例 | 英文/含义 | 中文含义 | 常见用途 | 示例 |
|---|---|---|---|---|
userId |
User ID | 用户 ID | 标识用户 | 10001 |
username |
Username | 用户名 | 用户名称 | "admin" |
role |
Role | 角色 | 权限控制 | "admin" |
roles |
Roles | 多个角色 | 多角色权限 | ["admin","user"] |
permissions |
Permissions | 权限 | 细粒度权限控制 | ["read","write"] |
scope |
Scope | 权限范围 | OAuth/API 权限 | "read write" |
email |
邮箱 | 用户信息 | "a@example.com" |
|
department |
Department | 部门 | 业务属性 | "security" |
tenantId |
Tenant ID | 租户 ID | 多租户系统 | "tenant001" |
isAdmin |
Is Administrator | 是否管理员 | 管理员判断 | true |
工作过程是:
用户首次登录,会生成jwt token返回给客户的
当客户端再次向服务器发送请求时,会携带token
服务器收到 JWT 后,会使用自己掌握的 Secret 或 Private Key,对“收到的 Header + Payload”重新计算/验证 Signature。并把计算结果和用户携带的token中的签名进行对比,如果相同则证明权限正确.只要 Header 或 Payload 被修改,重新计算出来的签名就与 JWT 携带的原 Signature 不一致,于是 Token 被判定为无效。**
Jwt攻击面分析
JWT
│
┌─────────────────┼─────────────────┐
│ │ │
Header Payload Signature
│ │ │
│ │ │
alg / kid Claims HS/RS/ES
jku / jwk exp Secret/Key
│ iss/aud
│ sub
│ role
│ │
▼ ▼ ▼
算法攻击 Claims攻击 签名攻击
│ │ │
└─────────────────┼─────────────────┘
│
▼
JWT验证逻辑
│
▼
业务授权逻辑
│
▼
权限绕过 / 提权
===================================================================================================================================================================================================================
JWT 攻击面
│
├── ① 算法攻击
│ ├── alg 混淆
│ ├── none
│ └── HS256 / RS256 混淆
│
├── ② 密钥攻击
│ ├── HS256 弱 Secret
│ ├── Secret 泄露
│ ├── Private Key 泄露
│ ├── kid
│ ├── jwk
│ ├── jku
│ └── JWKS / Key Rotation
│
├── ③ Claims 攻击
│ ├── exp
│ ├── nbf
│ ├── iss
│ ├── aud
│ ├── jti
│ └── 自定义 Claims
│
├── ④ Token 攻击
│ ├── Replay
│ ├── JWT 泄露
│ └── XSS
│
└── ⑤ 业务/实现攻击
├── 权限逻辑
├── Claims 信任
├── Parser 差异
└── 自定义验证
攻击总结表
| 类型 | 核心问题 | 如何实现攻击 / 基本思路 | 典型后果 |
|---|---|---|---|
alg 混淆 |
服务器没有严格限制算法 | 修改 Header 中的 alg,尝试让服务端使用与原 Token 不同的算法进行验证;重点检查服务端是否根据客户端提供的 alg 动态选择验证方式 |
伪造 Token |
none |
服务器错误接受无签名 Token | 将 Header 的 alg 改为 none,移除 Signature;如果服务端仍接受,则无需密钥即可构造 Token |
伪造 Token、认证绕过 |
| HS256 弱密钥 | HMAC Secret 强度不足 | 获取一个合法 HS256 JWT 后,对 Secret 进行离线字典/暴力猜测;找到 Secret 后即可重新计算合法 Signature | 任意伪造 Token |
| HS/RS 混淆 | 对称/非对称算法处理错误 | 原 Token 使用 RS256 时,尝试将 alg 改成 HS256;如果服务端错误地把 RSA Public Key 当 HMAC Secret 使用,就可能利用该 Public Key 生成伪造签名 |
任意伪造 Token |
kid |
Key ID 使用方式不安全 | 修改 kid,观察服务器如何根据它寻找 Key;如果 kid 被拼接到文件路径、SQL 查询等位置,则进一步检查路径遍历、SQL 注入等问题 |
Key 操纵、注入、认证绕过 |
jwk |
信任 JWT 自带公钥 | 在 Header 中提供攻击者自己的 JWK,并用对应 Private Key 签名;如果服务端直接信任 Header 中的 JWK,则可能接受伪造 Token | 伪造 Token |
jku |
信任任意 JWKS URL | 修改 jku 指向攻击者控制的 JWKS;如果服务器会从任意 URL 获取公钥,则可让服务器使用攻击者的 Public Key 验证 Token |
伪造 Token |
| JWKS | Key 管理/选择错误 | 检查 JWKS 中的 kid、算法、Key 类型及旧 Key;测试服务端是否接受错误 Key、过期 Key 或跨服务 Key |
伪造 Token、旧 Token 持续有效 |
exp |
未检查过期时间 | 使用已经超过 exp 的 JWT 请求 API;如果仍被接受,说明过期验证存在问题 |
Token 长期有效 |
nbf |
未检查生效时间 | 构造/使用 nbf 位于未来的 Token;如果服务器立即接受,则说明没有正确检查 nbf |
提前使用 Token |
iss |
未验证签发者 | 使用另一个可信系统签发的 JWT,或者修改 iss;测试目标服务是否接受非预期 Issuer |
跨系统 Token 滥用 |
aud |
未验证接收者 | 获取针对 Service A 的合法 Token,然后尝试访问 Service B;如果 B 不验证 aud,可能接受 A 的 Token |
跨服务认证绕过 |
| Replay | Token 本身可以重复使用 | 获取一个合法 Token 后,在不同请求、设备或时间重复使用;如果系统没有撤销/重放控制,则可持续冒用身份 | 账号冒用 |
| JWT 泄露 | Bearer Token 被窃取 | 从浏览器、日志、URL、代理记录、源码、前端存储等位置发现 JWT,然后直接携带该 Token 请求 API | 账号接管 |
| XSS | JWT 存储位置容易被 JS 读取 | 如果 Token 存在 localStorage 等 JS 可访问位置,存在 XSS 时可读取 Token 并发送到攻击者控制的位置 |
Token 窃取、账号接管 |
| Secret 泄露 | HS256 Secret 暴露 | 从源码、.env、配置文件、Git、CI/CD、容器环境变量等位置寻找 JWT Secret;获得后自行签名 Token |
任意伪造 Token |
| Private Key 泄露 | RS256 Private Key 暴露 | 从服务器、备份、配置、CI/CD 等位置获得 RSA Private Key,然后使用该私钥生成合法 JWT | 任意伪造 Token |
| 权限逻辑 | Authentication ≠ Authorization | 获得合法普通用户 Token 后,修改/测试 role、permissions、scope 等 Claims,并观察后端是否真正执行权限检查 |
越权、权限提升 |
| Parser 差异 | 不同组件解析 JWT 的方式不同 | 构造边界/异常 Token,让 API Gateway、WAF、JWT 库和后端对同一个 Token 得出不同解析结果 | 验证绕过 |
| Key Rotation | 旧 Key 未及时撤销 | 获取旧 Key 或旧 Token 后,在密钥轮换之后继续使用;如果服务端仍接受旧 Key/Token,则可以持续使用 | 持续伪造/长期冒用 |
| Claims 信任 | 过度信任 Payload | 修改 userId、role、tenantId、permissions 等 Claims;如果签名验证、身份查询或授权逻辑设计不当,则可能控制身份/权限 |
越权、IDOR、提权 |
| 自定义验证 | 开发者自行实现验证逻辑错误 | 分析 Token 验证代码,寻找“只解析不验签”“异常时默认通过”“算法未限制”“Claims 验证缺失”等逻辑错误 | 认证绕过 |
1、JWT 签名与业务数据不一致
例如 Payload:
{
"userId": 1001,
"role": "user"
}
Signature 完全正确。
但后端真正查询数据库:
userId = 1001
得到:
role = admin
于是出现:
JWT role
↓
user
Database role
↓
admin
如果不同模块使用不同来源的权限信息,就可能产生:
Identity / Authorization Inconsistency
这种问题严格来说不一定是 JWT 漏洞,但在 JWT 渗透测试里非常重要。
2、alg 算法混淆
这是 JWT 最经典的攻击面之一。
例如服务器正常使用:
{
"alg": "RS256"
}
攻击者尝试让服务器按照另一种算法处理。
典型历史问题是:
RS256
↓
RSA 非对称签名
HS256
↓
HMAC 对称签名
如果服务端错误地把 RSA 公钥当成 HMAC Secret 使用,就可能出现:
原本:
Private Key → RS256 → Signature
Public Key → Verify
错误情况:
Public Key → HS256 → Signature
↓
服务器错误接受
这就是经典的:
Algorithm Confusion / RS256 → HS256
现在成熟 JWT 库通常已经对此有防护,但老旧库、自定义实现仍值得检查。
3、alg: none
JWT 标准存在 none 算法,表示:
没有数字签名
理论上:
Header.Payload.
就是一个合法的 JWS 形式。
历史上一些 JWT 实现错误地:
接受客户端指定的 alg
↓
发现 alg = none
↓
跳过 Signature 验证
↓
直接信任 Payload
攻击者于是可能修改:
{
"role": "user"
}
为:
{
"role": "admin"
}
现代成熟框架通常默认禁止这种情况。
所以今天测试时,不应该看到 none 就认为一定存在漏洞,而应该验证:
服务器是否真的接受 none
4、弱密钥 / Secret 爆破
这是 HS256 特别值得关注的问题。
例如:
HS256
实际上是:
HMAC-SHA256(
Header.Payload,
Secret
)
如果 Secret 是:
123456
secret
admin
password
jwtsecret
company2024
攻击者可以进行离线猜测。
重点在于:
不需要不断请求服务器。
攻击者拿到 JWT 后,可以本地验证候选 Secret。
例如逻辑上:
JWT
│
├── Header
├── Payload
└── Signature
│
▼
猜测 Secret
│
▼
重新计算 HMAC
│
▼
Signature 是否一致?
如果一致:
Secret 找到了
然后理论上就可以重新签发任意 Payload。
所以:
HS256 + 弱 Secret
是非常经典的 JWT 风险组合。
5、HS256/RS256密钥类型混淆
这个和单纯的 alg 攻击相关,但值得单独理解。
HS256
Secret
/ \
/ \
签名服务器 验证服务器
签名和验证使用同一个 Secret。
RS256
Private Key
│
▼
签名
Public Key
│
▼
验证
因此:
HS256 → 对称算法
RS256 → 非对称算法
如果应用没有严格限制:
允许的算法
允许的密钥类型
允许的 Key ID
就可能出现算法混淆问题。
6、kid 攻击
kid:
Key ID
例如:
{
"alg": "RS256",
"kid": "key-2026-01"
}
服务器可能根据:
kid
选择对应的公钥:
kid = key-2026-01
↓
找到对应 Public Key
↓
验证 Signature
问题在于:
如果开发人员把
kid当成普通字符串直接参与文件、数据库、命令或其他操作,就可能产生注入类漏洞。
例如概念上:
kid
↓
文件路径
↓
读取 Key
如果实现不安全,就可能产生:
Path Traversal
或者:
SQL Injection
等问题。
因此:
kid
本身不是漏洞。
漏洞来自:
服务器如何使用 kid。
7、jku 攻击
jku:
JSON Web Key Set URL
JWT Header 可能出现:
{
"alg": "RS256",
"jku": "https://example.com/.well-known/jwks.json"
}
服务器可能:
读取 jku
↓
下载 JWKS
↓
找到对应 kid
↓
使用公钥验证 JWT
危险点是:
如果服务器完全信任客户端提供的
jku,攻击者可能尝试让服务器加载攻击者控制的公钥。
逻辑:
攻击者
│
│ JWT
│ jku = 攻击者控制的 JWKS
▼
服务器
│
│ 下载 JWKS
▼
攻击者公钥
│
▼
服务器使用该公钥验证
于是攻击者可以:
自己的 Private Key
↓
签名 JWT
↓
服务器拿攻击者 Public Key 验证
↓
验证成功
所以安全实现通常应该:
允许的 JWKS 地址 = 白名单
而不是:
任意 jku = 任意 URL
8、jwk 直接提供公钥
类似的问题还有:
{
"alg": "RS256",
"jwk": {
"kty": "RSA",
...
}
}
如果服务器直接信任 Header 中提供的 JWK:
JWT Header
│
▼
jwk = 攻击者公钥
│
▼
服务器拿它验证
就会导致:
自己生成密钥
↓
自己签 JWT
↓
把自己的公钥塞进 JWT
↓
服务器用自己的公钥验证
因此:
验证密钥必须来自可信来源,而不能由待验证的 JWT 自己决定。
9、JWK / JWKS 配置错误
JWKS:
JSON Web Key Set
例如:
/.well-known/jwks.json
里面可能有:
{
"keys": [
{
"kty": "RSA",
"kid": "key1",
"use": "sig"
}
]
}
常见安全问题包括:
kid 管理错误
旧密钥没有撤销
错误的 key 用途
算法与 key 类型不匹配
JWKS 来源不可信
缓存更新问题
多租户之间 Key 混用
10、Payload / Claims 篡改
这是最直观的一类。
例如:
{
"sub": "1001",
"role": "user"
}
攻击者尝试:
{
"sub": "1001",
"role": "admin"
}
如果服务器:
没有验证 Signature
或者:
Signature 验证存在逻辑漏洞
就可能直接导致:
普通用户 → 管理员
11、JWT 权限绕过
这是实际项目里非常重要的一类。
即使 JWT 签名完全正确:
Signature ✓
也不代表:
Authorization ✓
例如:
{
"sub": "1001",
"role": "user"
}
服务器正确验证了 JWT。
但是 API:
GET /api/admin/users
只检查:
Token 是否有效
却没有检查:
role == admin
那么:
认证成功 ≠ 授权成功
这属于:
Authentication / Authorization 混淆
也是 JWT 项目里非常常见的问题。
12、exp 过期时间验证问题
JWT:
{
"sub": "1001",
"exp": 1754993600
}
如果服务器没有正确检查:
exp
那么 Token 可能永久有效。
风险:
JWT 泄露
↓
攻击者长期使用
另外还有:
nbf
iat
iss
aud
等 Claims 的验证问题。
13、aud / iss 验证缺失
例如一个 JWT 是给:
Service A
签发的。
但:
Service B
也接受同一个 JWT。
如果 B 应该只接受:
{
"aud": "service-B"
}
但它完全不检查 aud:
Service A Token
↓
Service B
↓
接受
就可能产生:
Cross-Service Token Abuse
在微服务环境尤其值得关注。
14、JWT 重放攻击 Replay
JWT 是 Bearer Token:
谁拿到
谁就可以使用
例如:
用户
↓
JWT
↓
攻击者窃取
攻击者无需知道:
用户名
密码
直接:
Authorization: Bearer <stolen-JWT>
即可使用。
如果 Token:
有效期很长
没有撤销机制
没有绑定客户端
没有 jti 防重放
风险会更大。
15、JWT 泄露
JWT 本身可能没有密码,但它相当于:
身份证 + 通行证
常见泄露位置:
LocalStorage
SessionStorage
Cookie
URL
Referer
日志
浏览器插件
前端错误日志
代理日志
聊天记录
Git 仓库
前端源码
尤其不要把 JWT 放到 URL:
https://example.com/api?token=xxxxx
因为 URL 很容易进入:
Browser History
Proxy Logs
Web Server Logs
Referer
16、XSS + JWT
如果 JWT 放:
localStorage
那么:
XSS
↓
JavaScript
↓
读取 localStorage
↓
JWT 被窃取
于是:
XSS
+
JWT
可能直接变成:
账号接管
这也是为什么很多安全架构会考虑:
HttpOnly Cookie
不过 Cookie 又需要考虑:
CSRF
SameSite
Secure
所以并不是简单地说:
Cookie 永远比 localStorage 安全
17、签名验证逻辑错误
这是自定义 JWT 实现特别危险的一类。
例如开发人员自己写:
verify(token)
可能出现:
只解析 Payload
没有验证 Signature
验证 Signature 后没有检查 exp
检查 Signature 的算法与 Header 不一致
错误处理导致验证失败后继续执行
异常情况下默认 Token 有效
这类问题往往比某一个具体 JWT 算法漏洞更加现实。
18、时间验证问题
例如:
exp
nbf
iat
涉及 Unix Timestamp:
exp = 1754993600
如果不同服务器:
时钟不同步
或者应用:
时间单位搞错
秒 / 毫秒
可能造成:
Token 提前失效
Token 永久有效
Token 尚未生效却被接受
19、JWT 密钥泄露
这其实是非常严重但经常被忽视的一类。
例如:
.env
config.yaml
application.properties
Docker Compose
GitHub
GitLab
CI/CD
容器环境变量
源码
备份文件
日志
出现:
JWT_SECRET=xxxx
那么:
JWT Secret 泄露
↓
攻击者可以自行签名
↓
伪造 JWT
对于 HS256:
Secret 泄露
↓
几乎等于可以伪造 Token
对于 RS256:
Private Key 泄露
↓
攻击者可以伪造 Token
而:
Public Key 泄露
通常不是漏洞,因为 RS256 的设计本来就是允许公开 Public Key。
20、密钥轮换 / kid 管理问题
大型系统通常不会永远使用:
同一个 Secret
而会:
key1
key2
key3
...
通过:
kid
标识。
如果旧 Key:
已经泄露
但是服务器:
仍然接受旧 kid
那么攻击者可能继续伪造 Token。
所以 JWT Key Management 本身也是一个攻击面。
21、嵌套 JWT/JWT Parser 差异
比较高级的一类问题是:
不同 JWT 库
不同语言
不同组件
对于 Token 的解析规则可能不完全一致。
例如:
API Gateway
↓
JWT Parser A
↓
认为 Token 有效
↓
后端
↓
JWT Parser B
↓
按照不同规则解释
这类问题属于:
Parser Differential / Parsing Confusion
在复杂微服务系统中尤其值得注意。
浙公网安备 33010602011771号