4. Token 生成与签发:服务端生成 Token 流程、签名算法选择(HS256/RS256)、Payload 设计规范
Token 的生成与签发是鉴权链路的第一道关口。服务端必须在用户登录或凭证校验通过后,按照标准流程构造 JWT(JSON Web Token),选择合适的签名算法,并设计合理的 Payload 结构。本章将拆解这三个核心环节,确保生成的 Token 既安全又具备可扩展性。
4.1 服务端生成 Token 的标准流程
一个完整的 Token 签发流程包含以下 6 个步骤,每一步都直接影响后续的验证效率与安全性:
- 用户身份校验:接收用户名/密码、OAuth 授权码或第三方凭证,通过数据库或认证服务验证合法性。
- 构造 JWT Header:固定包含
typ: "JWT"和alg字段(如HS256或RS256)。 - 构造 JWT Payload:按规范填充标准声明(iss、sub、exp、iat、jti)和自定义声明(用户角色、权限范围、会话 ID)。
- 计算签名:使用选定的算法和密钥对 Header + Payload 进行签名,生成 Signature 部分。
- 组装 Token:将 Base64URL 编码的 Header、Payload 和 Signature 用点号(.)连接。
- 缓存与下发:将 Token 存入 Redis(用于黑名单/刷新逻辑),并通过 HTTP 响应返回给客户端。
关键指标:Token 签发耗时应控制在 5ms 以内(含 Redis 写入),若超过 10ms 需检查签名算法或 Redis 连接池配置。
4.2 签名算法选择:HS256 vs RS256
签名算法决定了 Token 的防篡改能力和密钥管理方式。HS256(HMAC-SHA256)和 RS256(RSA-SHA256)是生产环境中最常用的两种算法,它们的核心差异如下:
| 对比维度 | HS256(对称) | RS256(非对称) |
|---|---|---|
| 密钥类型 | 单一共享密钥(Secret) | 私钥签名 + 公钥验证 |
| 签名速度 | 快(约 0.5ms) | 慢(约 2-5ms) |
| 验证速度 | 快(约 0.5ms) | 快(约 0.8ms) |
| 密钥分发 | 需安全通道共享 Secret | 公钥可公开,私钥仅签发端持有 |
| 适用场景 | 单体服务、内部微服务 | 多服务、第三方集成、开放 API |
| 密钥轮换 | 需同步更新所有服务 | 仅需更新签发端私钥 |
选型建议:如果你的后端架构是单体应用或内部微服务(所有服务都在同一信任域),优先选择 HS256 以获得更快的签名速度。如果涉及第三方服务、开放 API 或需要独立验证 Token(如 API 网关),选择 RS256 更安全,因为公钥可以安全地分发给所有验证方。
4.3 Payload 设计规范
JWT Payload 是 Token 的信息载体,设计时需遵循“最小必要原则”——只包含验证和授权所必需的字段,避免携带敏感数据。Payload 分为标准声明(Registered Claims)和自定义声明(Custom Claims)两类。
4.3.1 标准声明字段
| 声明 | 全称 | 说明 | 是否必选 |
|---|---|---|---|
iss |
Issuer | 签发者标识,如 "auth-service" |
推荐 |
sub |
Subject | 用户唯一标识(如 user_id) | 必选 |
exp |
Expiration Time | 过期时间(Unix 时间戳),建议 15-60 分钟 | 必选 |
iat |
Issued At | 签发时间(Unix 时间戳) | 推荐 |
jti |
JWT ID | Token 唯一标识(UUID),用于防重放 | 推荐 |
4.3.2 自定义声明设计示例
以下是一个生产级 Payload 示例,包含标准声明和必要的自定义声明:
{
"iss": "auth-api.v2.example.com",
"sub": "user_8a3f7b2e",
"exp": 1712345678,
"iat": 1712341778,
"jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"role": "admin",
"scope": ["read:orders", "write:orders"],
"session_id": "sess_9f8e7d6c5b4a"
}
安全红线:
• 禁止在 Payload 中存储密码、身份证号、银行卡号等敏感信息(Payload 仅 Base64URL 编码,非加密)。
• 自定义声明键名使用小驼峰或下划线命名,避免与标准声明冲突。
•
• 禁止在 Payload 中存储密码、身份证号、银行卡号等敏感信息(Payload 仅 Base64URL 编码,非加密)。
• 自定义声明键名使用小驼峰或下划线命名,避免与标准声明冲突。
•
exp 和 iat 必须使用 UTC 时间戳,避免时区问题。
4.4 代码实现:Token 签发核心逻辑
以下是一个基于 Node.js + jsonwebtoken 库的 Token 签发示例,展示了 HS256 和 RS256 两种算法的实现方式:
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
// ========== HS256 对称签名 ==========
const HS256_SECRET = process.env.JWT_HS256_SECRET || 'your-256-bit-secret';
function signHS256(userId, role, scope) {
const payload = {
iss: 'auth-api.v2.example.com',
sub: userId,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600, // 1小时过期
jti: crypto.randomUUID(),
role: role,
scope: scope
};
return jwt.sign(payload, HS256_SECRET, { algorithm: 'HS256' });
}
// ========== RS256 非对称签名 ==========
const fs = require('fs');
const PRIVATE_KEY = fs.readFileSync('./keys/private.pem', 'utf8');
function signRS256(userId, role, scope) {
const payload = {
iss: 'auth-api.v2.example.com',
sub: userId,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600,
jti: crypto.randomUUID(),
role: role,
scope: scope
};
return jwt.sign(payload, PRIVATE_KEY, { algorithm: 'RS256' });
}
// 使用示例
const token = signHS256('user_8a3f7b2e', 'admin', ['read:orders', 'write:orders']);
console.log('Generated Token:', token);
生产建议:
• 密钥(HS256 Secret 或 RS256 私钥)必须通过环境变量或密钥管理服务(如 AWS KMS、HashiCorp Vault)注入,禁止硬编码在代码仓库中。
• 每次签发 Token 后,将
• 建议在签发时记录审计日志,包含
• 密钥(HS256 Secret 或 RS256 私钥)必须通过环境变量或密钥管理服务(如 AWS KMS、HashiCorp Vault)注入,禁止硬编码在代码仓库中。
• 每次签发 Token 后,将
jti 和 exp 写入 Redis(SETEX jti:<value> <ttl> 1),用于后续的黑名单校验和防重放检测。• 建议在签发时记录审计日志,包含
sub、jti、iat、客户端 IP 和 User-Agent。