JWT 结构 #
JWT 的三部分组成 #
JWT 由三个部分组成,用点号(.)分隔:
text
┌─────────────────────────────────────────────────────────────┐
│ JWT 结构概览 │
├─────────────────────────────────────────────────────────────┤
│ │
│ xxxxx.yyyyy.zzzzz │
│ │ │ │ │
│ │ │ └── Signature(签名) │
│ │ └───────── Payload(载荷) │
│ └───────────────── Header(头部) │
│ │
│ 完整示例: │
│ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. │
│ eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0 │
│ IjoxNTE2MjM5MDIyfQ. │
│ SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │
│ │
└─────────────────────────────────────────────────────────────┘
第一部分:Header(头部) #
Header 的作用 #
Header 用于描述 JWT 的元数据,主要包括:
- 令牌类型(typ)
- 签名算法(alg)
- 其他可选参数
Header 结构 #
json
{
"alg": "HS256",
"typ": "JWT"
}
Header 字段详解 #
text
┌─────────────────────────────────────────────────────────────┐
│ Header 字段说明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 字段名 │ 必需 │ 描述 │
│ ─────────┼───────┼─────────────────────────────────────── │
│ alg │ 是 │ 签名算法 │
│ typ │ 否 │ 令牌类型,通常为 JWT │
│ cty │ 否 │ 内容类型(嵌套 JWT 时使用) │
│ kid │ 否 │ 密钥标识符 │
│ jku │ 否 │ JWKS URL │
│ x5u │ 否 │ X.509 URL │
│ x5c │ 否 │ X.509 证书链 │
│ x5t │ 否 │ X.509 证书指纹 │
│ │
└─────────────────────────────────────────────────────────────┘
alg 字段(签名算法) #
text
┌─────────────────────────────────────────────────────────────┐
│ 常用签名算法 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 对称算法(HMAC): │
│ ┌─────────┬───────────────────────────────────────────┐ │
│ │ HS256 │ HMAC SHA-256 │ │
│ │ HS384 │ HMAC SHA-384 │ │
│ │ HS512 │ HMAC SHA-512 │ │
│ └─────────┴───────────────────────────────────────────┘ │
│ │
│ 非对称算法(RSA): │
│ ┌─────────┬───────────────────────────────────────────┐ │
│ │ RS256 │ RSASSA-PKCS1-v1_5 SHA-256 │ │
│ │ RS384 │ RSASSA-PKCS1-v1_5 SHA-384 │ │
│ │ RS512 │ RSASSA-PKCS1-v1_5 SHA-512 │ │
│ └─────────┴───────────────────────────────────────────┘ │
│ │
│ 非对称算法(ECDSA): │
│ ┌─────────┬───────────────────────────────────────────┐ │
│ │ ES256 │ ECDSA P-256 SHA-256 │ │
│ │ ES384 │ ECDSA P-384 SHA-384 │ │
│ │ ES512 │ ECDSA P-521 SHA-512 │ │
│ └─────────┴───────────────────────────────────────────┘ │
│ │
│ 其他: │
│ ┌─────────┬───────────────────────────────────────────┐ │
│ │ PS256 │ RSASSA-PSS SHA-256 │ │
│ │ none │ 无签名(不推荐) │ │
│ └─────────┴───────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
kid 字段(密钥标识符) #
json
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-2024-01-01"
}
kid 用于标识用于验证签名的密钥,在密钥轮换场景中非常重要:
text
┌─────────────────────────────────────────────────────────────┐
│ kid 的作用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 密钥轮换 │
│ - 多个密钥同时有效 │
│ - 通过 kid 选择正确的密钥验证 │
│ │
│ 2. JWKS 集成 │
│ - 从 JWKS 端点获取公钥 │
│ - 根据 kid 匹配对应的公钥 │
│ │
│ 示例流程: │
│ │
│ JWT Header: { "kid": "key-001" } │
│ │ │
│ ▼ │
│ JWKS: { "keys": [ │
│ { "kid": "key-001", "n": "...", "e": "AQAB" }, │
│ { "kid": "key-002", "n": "...", "e": "AQAB" } │
│ ]} │
│ │ │
│ ▼ │
│ 使用 kid="key-001" 的公钥验证 │
│ │
└─────────────────────────────────────────────────────────────┘
Header 编码 #
text
┌─────────────────────────────────────────────────────────────┐
│ Header 编码过程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 原始 JSON │
│ { │
│ "alg": "HS256", │
│ "typ": "JWT" │
│ } │
│ │
│ 2. UTF-8 编码 │
│ {"alg":"HS256","typ":"JWT"} │
│ │
│ 3. Base64URL 编码 │
│ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 │
│ │
└─────────────────────────────────────────────────────────────┘
第二部分:Payload(载荷) #
Payload 的作用 #
Payload 包含实际传输的数据,称为"声明"(Claims)。声明是关于实体(通常是用户)和其他数据的声明。
Payload 结构 #
json
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
声明类型 #
text
┌─────────────────────────────────────────────────────────────┐
│ JWT 声明类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Registered Claims(注册声明) │ │
│ │ - 预定义的标准声明 │ │
│ │ - 推荐使用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Public Claims(公共声明) │ │
│ │ - 公开定义的声明 │ │
│ │ - 避免冲突 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Private Claims(私有声明) │ │
│ │ - 自定义声明 │ │
│ │ - 仅在双方之间使用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Registered Claims(注册声明) #
text
┌─────────────────────────────────────────────────────────────┐
│ 标准注册声明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 声明 │ 名称 │ 描述 │
│ ────┼────────────────┼─────────────────────────────────── │
│ iss │ Issuer │ 签发者 │
│ sub │ Subject │ 主题(用户标识) │
│ aud │ Audience │ 受众(接收方) │
│ exp │ Expiration │ 过期时间 │
│ nbf │ Not Before │ 生效时间 │
│ iat │ Issued At │ 签发时间 │
│ jti │ JWT ID │ 唯一标识符 │
│ │
└─────────────────────────────────────────────────────────────┘
注册声明详解 #
iss(Issuer)- 签发者 #
json
{
"iss": "https://auth.example.com"
}
text
作用:
- 标识 JWT 的签发者
- 验证时检查是否来自可信来源
- 通常是授权服务器的 URL
验证示例:
if (decoded.iss !== 'https://auth.example.com') {
throw new Error('Invalid issuer');
}
sub(Subject)- 主题 #
json
{
"sub": "user-123456"
}
text
作用:
- 标识 JWT 的主体
- 通常是用户唯一标识
- 在 iss 范围内必须唯一
使用场景:
- 用户 ID
- 服务标识
- 资源标识
aud(Audience)- 受众 #
json
{
"aud": ["https://api.example.com", "client-app"]
}
text
作用:
- 标识 JWT 的接收者
- 防止 JWT 被错误的服务接受
- 可以是单个值或数组
验证示例:
const validAudiences = ['https://api.example.com'];
if (!validAudiences.includes(decoded.aud)) {
throw new Error('Invalid audience');
}
exp(Expiration Time)- 过期时间 #
json
{
"exp": 1516239022
}
text
作用:
- 定义 JWT 的过期时间
- 必须是 NumericDate(Unix 时间戳)
- 过期后 JWT 无效
验证示例:
const now = Math.floor(Date.now() / 1000);
if (decoded.exp < now) {
throw new Error('Token expired');
}
nbf(Not Before)- 生效时间 #
json
{
"nbf": 1516239000
}
text
作用:
- 定义 JWT 开始生效的时间
- 在此时间之前 JWT 无效
- 用于延迟生效场景
验证示例:
const now = Math.floor(Date.now() / 1000);
if (decoded.nbf > now) {
throw new Error('Token not yet valid');
}
iat(Issued At)- 签发时间 #
json
{
"iat": 1516239022
}
text
作用:
- 记录 JWT 的签发时间
- 可用于判断 Token 年龄
- 辅助安全验证
使用场景:
- 计算 Token 有效期
- 检测异常签发
- 审计追踪
jti(JWT ID)- 唯一标识符 #
json
{
"jti": "550e8400-e29b-41d4-a716-446655440000"
}
text
作用:
- 为 JWT 提供唯一标识
- 用于防止重放攻击
- 可用于 Token 黑名单
使用场景:
- Token 撤销
- 重放防护
- 审计追踪
Public Claims(公共声明) #
text
┌─────────────────────────────────────────────────────────────┐
│ 公共声明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 公共声明是公开定义的,用于避免命名冲突。 │
│ 应该在 IANA JSON Web Token Registry 注册, │
│ 或使用抗冲突命名方式(如 URI)。 │
│ │
│ IANA 注册示例: │
│ - name: 用户名 │
│ - email: 邮箱 │
│ - picture: 头像 URL │
│ │
│ URI 命名示例: │
│ { │
│ "https://example.com/claims/role": "admin", │
│ "https://example.com/claims/tenant": "acme" │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
Private Claims(私有声明) #
json
{
"sub": "user-123",
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"permissions": ["read", "write"],
"tenant_id": "tenant-001"
}
text
┌─────────────────────────────────────────────────────────────┐
│ 私有声明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 私有声明是自定义的声明,仅在签发方和接收方之间使用。 │
│ │
│ 注意事项: │
│ ⚠️ 避免与注册声明冲突 │
│ ⚠️ 不要存储敏感信息 │
│ ⚠️ 控制声明数量(影响 Token 大小) │
│ │
│ 常见私有声明: │
│ - role: 用户角色 │
│ - permissions: 权限列表 │
│ - tenant_id: 租户 ID │
│ - department: 部门 │
│ - level: 用户等级 │
│ │
└─────────────────────────────────────────────────────────────┘
完整 Payload 示例 #
json
{
"iss": "https://auth.example.com",
"sub": "user-123456",
"aud": "https://api.example.com",
"exp": 1516239022,
"nbf": 1516239000,
"iat": 1516238900,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"email": "john@example.com",
"email_verified": true,
"role": "admin",
"permissions": ["read", "write", "delete"],
"tenant_id": "tenant-001"
}
Payload 编码 #
text
┌─────────────────────────────────────────────────────────────┐
│ Payload 编码过程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 原始 JSON │
│ { │
│ "sub": "1234567890", │
│ "name": "John Doe", │
│ "iat": 1516239022 │
│ } │
│ │
│ 2. UTF-8 编码 │
│ {"sub":"1234567890","name":"John Doe","iat":1516239022} │
│ │
│ 3. Base64URL 编码 │
│ eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwi │
│ aWF0IjoxNTE2MjM5MDIyfQ │
│ │
└─────────────────────────────────────────────────────────────┘
第三部分:Signature(签名) #
Signature 的作用 #
签名用于验证:
- 发送者身份(真实性)
- 消息未被篡改(完整性)
签名生成过程 #
text
┌─────────────────────────────────────────────────────────────┐
│ 签名生成流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 准备签名内容 │
│ signatureContent = base64UrlEncode(header) + "." + │
│ base64UrlEncode(payload) │
│ │
│ 2. 使用算法签名 │
│ HMACSHA256( │
│ signatureContent, │
│ secret │
│ ) │
│ │
│ 3. Base64URL 编码签名 │
│ signature = base64UrlEncode(hmacResult) │
│ │
│ 4. 组合最终 JWT │
│ jwt = header + "." + payload + "." + signature │
│ │
└─────────────────────────────────────────────────────────────┘
HMAC 签名示例(HS256) #
javascript
const header = {
"alg": "HS256",
"typ": "JWT"
};
const payload = {
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
};
const encodedHeader = base64UrlEncode(JSON.stringify(header));
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
const signatureContent = encodedHeader + "." + encodedPayload;
const signature = HMACSHA256(signatureContent, 'your-256-bit-secret');
const jwt = encodedHeader + "." + encodedPayload + "." + base64UrlEncode(signature);
RSA 签名示例(RS256) #
javascript
const signatureContent = encodedHeader + "." + encodedPayload;
const signature = RSASign(
signatureContent,
privateKey,
'sha256'
);
const jwt = encodedHeader + "." + encodedPayload + "." + base64UrlEncode(signature);
签名验证过程 #
text
┌─────────────────────────────────────────────────────────────┐
│ 签名验证流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 输入:JWT Token │
│ │
│ 1. 分割 JWT │
│ [header, payload, signature] = jwt.split('.') │
│ │
│ 2. 解码 Header │
│ headerObj = JSON.parse(base64UrlDecode(header)) │
│ │
│ 3. 获取签名密钥 │
│ - HMAC: 使用共享密钥 │
│ - RSA: 使用公钥 │
│ │
│ 4. 重新计算签名 │
│ expectedSignature = sign(header + "." + payload, key) │
│ │
│ 5. 比对签名 │
│ if (signature === expectedSignature) { │
│ // 签名验证通过 │
│ } else { │
│ // 签名验证失败 │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
Base64URL 编码 #
为什么使用 Base64URL? #
text
┌─────────────────────────────────────────────────────────────┐
│ Base64URL vs Base64 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 标准 Base64 问题: │
│ - 包含 + / = 字符 │
│ - + 和 / 在 URL 中有特殊含义 │
│ - = 作为填充字符 │
│ │
│ Base64URL 改进: │
│ - + 替换为 - │
│ - / 替换为 _ │
│ - 去掉 = 填充 │
│ │
│ 对比示例: │
│ Base64: a+b/c== │
│ Base64URL: a-b_c │
│ │
└─────────────────────────────────────────────────────────────┘
Base64URL 编码实现 #
javascript
function base64UrlEncode(str) {
return Buffer.from(str)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
function base64UrlDecode(str) {
str = str.replace(/-/g, '+').replace(/_/g, '/');
const pad = str.length % 4;
if (pad) {
str += '='.repeat(4 - pad);
}
return Buffer.from(str, 'base64').toString();
}
编码示例 #
text
┌─────────────────────────────────────────────────────────────┐
│ 编码示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原始字符串: │
│ {"alg":"HS256","typ":"JWT"} │
│ │
│ Base64 编码: │
│ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 │
│ │
│ Base64URL 编码(相同,无特殊字符): │
│ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 原始字符串(包含特殊字符): │
│ {"name":"张三?+"} │
│ │
│ Base64 编码: │
│ eyJuYW1lIjoi5byg5LiJPyoi │
│ │
│ Base64URL 编码: │
│ eyJuYW1lIjoi5byg5LiJPyoi │
│ │
└─────────────────────────────────────────────────────────────┘
完整 JWT 示例 #
生成 JWT #
javascript
const header = {
"alg": "HS256",
"typ": "JWT"
};
const payload = {
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
};
const encodedHeader = base64UrlEncode(JSON.stringify(header));
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
const signature = HMACSHA256(
encodedHeader + "." + encodedPayload,
'your-256-bit-secret'
);
const jwt = encodedHeader + "." + encodedPayload + "." + base64UrlEncode(signature);
console.log(jwt);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
解析 JWT #
javascript
function parseJwt(token) {
const [header, payload, signature] = token.split('.');
return {
header: JSON.parse(base64UrlDecode(header)),
payload: JSON.parse(base64UrlDecode(payload)),
signature: signature
};
}
const parsed = parseJwt(jwt);
console.log(parsed.header);
// { alg: "HS256", typ: "JWT" }
console.log(parsed.payload);
// { sub: "1234567890", name: "John Doe", iat: 1516239022 }
JWT 大小计算 #
text
┌─────────────────────────────────────────────────────────────┐
│ JWT 大小分析 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Header(约 35-50 字节): │
│ {"alg":"HS256","typ":"JWT"} │
│ │
│ Payload(取决于内容): │
│ 最小:约 50 字节(仅标准声明) │
│ 典型:约 150-300 字节 │
│ 较大:500+ 字节(包含大量自定义声明) │
│ │
│ Signature: │
│ HS256:32 字节 → Base64URL 后 43 字符 │
│ RS256:256 字节 → Base64URL 后 342 字符 │
│ │
│ 总大小示例: │
│ - 简单 JWT(HS256):约 200 字节 │
│ - 典型 JWT(HS256):约 300-500 字节 │
│ - 典型 JWT(RS256):约 600-800 字节 │
│ │
│ 注意: │
│ ⚠️ 每次请求都要传输 │
│ ⚠️ 控制声明数量 │
│ ⚠️ 考虑网络开销 │
│ │
└─────────────────────────────────────────────────────────────┘
下一步 #
现在你已经了解了 JWT 的结构,接下来学习 JWT 签名算法,深入了解不同签名算法的特点和使用场景!
最后更新:2026-03-28