票据生成机制:服务端生成策略、JWT标准详解、签名算法对比与选择
聊到会话票据,很多人第一反应就是 JWT。没错,JWT 确实是目前最主流的票据格式。但我要说一句:会用 JWT 和用好 JWT,中间差着好几个坑。这一章,我就把票据生成的底层逻辑掰开揉碎,从服务端策略到签名算法选择,一次性讲透。
服务端生成策略:不是所有票据都该一样
票据怎么生成?很多人直接一把梭,所有用户都用同一套逻辑。我建议你换个思路——按场景分策略。
核心原则:票据的生成策略,取决于它的生命周期和敏感度。
我个人习惯把票据分成三类:
- 短期访问票据(Access Token):15分钟到2小时过期。生成时只放必要信息,比如用户ID、角色、时间戳。别塞一堆业务数据进去。
- 长期刷新票据(Refresh Token):7天到30天。这东西要单独存数据库,生成时加一个随机串,保证不可预测。
- 一次性票据(One-Time Token):比如密码重置链接里的token。用完即废,服务端必须记录已使用状态。
我在项目中遇到过一个问题:有人把用户的所有权限列表都塞进 Access Token 里。结果 token 体积暴涨,每次请求都多传好几 KB。你想想看,一个系统几万个用户,网关带宽就这么被浪费了。后来我改成只存角色 ID,权限实时查缓存,问题就解决了。
我的习惯:服务端生成票据时,永远先问自己三个问题——这个票据谁用?用多久?丢了后果多严重?答案不同,策略就不同。
JWT 标准详解:结构拆开看
JWT 说白了就是一个三段式字符串,用点号分隔。我刚开始学的时候觉得这东西很神秘,拆开一看,其实就三部分。
// 一个典型的 JWT 长这样
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
三段分别是:
- Header(头部):声明类型和签名算法。比如
{"alg":"HS256","typ":"JWT"}。Base64URL 编码后就是第一段。 - Payload(载荷):放实际数据的地方。注意,这里只是 Base64 编码,不是加密!我见过有人把密码放进去,以为别人看不到——这是大忌。
- Signature(签名):用 Header 里声明的算法,对前两段加一个密钥算出来的。这是防篡改的关键。
我曾经踩过的坑:有人把 JWT 的 Payload 当加密用,放了敏感信息。记住,Payload 是明文,谁拿到都能解码。真要保密,请用 JWE(加密的 JWT),或者干脆别放敏感数据。
JWT 标准里定义了一些注册声明(Registered Claims),我建议你尽量用标准字段:
| 字段 | 含义 | 我是否必用 |
|---|---|---|
iss |
签发者 | 必用。多服务间做来源校验 |
sub |
主题(通常是用户ID) | 必用。标识谁在用这个票据 |
exp |
过期时间 | 必用。服务端必须校验 |
iat |
签发时间 | 建议用。方便做签发时间校验 |
jti |
JWT唯一ID | 建议用。用于防重放攻击 |
一个小技巧:我习惯在 Payload 里加一个 scope 字段,用来做细粒度权限控制。比如 "scope":"read:orders write:profile"。这样网关层就能直接做权限校验,不用每次都查数据库。
签名算法对比:HS256 vs RS256
这是很多人纠结的地方。选 HS256 还是 RS256?我直接说结论:看你的架构场景。
先看一张对比图,把核心差异说清楚:
HS256:简单但别大意
HS256 用的是 HMAC-SHA256,同一个密钥既签名又验证。好处是快,坏处是——密钥一旦泄露,整个系统就完了。
我记得有一次帮一个团队做代码审查,发现他们把 HS256 的密钥硬编码在配置文件里,还提交到了 Git 仓库。嗯,这相当于把家门钥匙贴在门上。后来我建议他们用密钥管理服务(KMS)来托管密钥,运行时动态获取。
避坑指南:用 HS256 时,密钥长度至少 256 位(32字节)。别用短密钥,也别用常见字符串。我曾经见过有人用 "secret123" 当密钥——这种密钥,暴力破解用不了几分钟。
RS256:微服务架构的首选
RS256 用 RSA 非对称算法,私钥签名,公钥验证。为什么我推荐微服务用这个?
- 公钥可以公开:各个微服务只需要拿到公钥就能验证 JWT,不需要知道私钥。
- 私钥集中管理:只有认证服务持有私钥,泄露面大大缩小。
- 支持密钥轮换:公钥可以发布到 /.well-known/jwks.json,客户端自动拉取。
你想想看,一个微服务架构里可能有十几个服务。如果用 HS256,每个服务都得配置同一个密钥,密钥轮换时所有服务都得更新——这运维成本太高了。RS256 就简单得多,认证服务换私钥,公钥自动同步,其他服务无感。
我的实践经验:在微服务架构中,我习惯用 RS256 + JWKS(JSON Web Key Set)。认证服务把公钥列表发布到一个标准端点,其他服务定时拉取。这样密钥轮换时,只需要更新认证服务的私钥,其他服务自动适配。
签名算法选择决策表
为了方便你快速决策,我整理了一个对比表:
| 维度 | HS256 | RS256 |
|---|---|---|
| 算法类型 | 对称(HMAC-SHA256) | 非对称(RSA-SHA256) |
| 签名速度 | 快(约 0.01ms) | 慢(约 0.5ms) |
| 验证速度 | 快(约 0.01ms) | 较慢(约 0.3ms) |
| 密钥管理 | 单一密钥,分发困难 | 私钥保密,公钥公开 |
| 密钥轮换 | 所有服务同步更新 | 仅更新私钥,公钥自动同步 |
| 适用场景 | 单体应用、内部服务 | 微服务、开放API、第三方 |
| 安全风险 | 密钥泄露即全盘崩溃 | 私钥泄露风险低 |
代码示例:两种签名算法的实现
光说不练假把式。我写两个简单的示例,用 Node.js 的 jsonwebtoken 库演示:
// HS256 签名与验证
const jwt = require('jsonwebtoken');
// 签名
const hs256Token = jwt.sign(
{ userId: 123, role: 'admin' },
'your-256-bit-secret-key-here-must-be-long-enough',
{ algorithm: 'HS256', expiresIn: '1h' }
);
// 验证
try {
const decoded = jwt.verify(hs256Token, 'your-256-bit-secret-key-here-must-be-long-enough');
console.log('HS256 验证通过:', decoded);
} catch (err) {
console.error('HS256 验证失败:', err.message);
}
// RS256 签名与验证
const jwt = require('jsonwebtoken');
const fs = require('fs');
// 读取密钥文件(生产环境请用密钥管理服务)
const privateKey = fs.readFileSync('private.pem', 'utf8');
const publicKey = fs.readFileSync('public.pem', 'utf8');
// 签名(用私钥)
const rs256Token = jwt.sign(
{ userId: 456, role: 'user' },
privateKey,
{ algorithm: 'RS256', expiresIn: '2h' }
);
// 验证(用公钥)
try {
const decoded = jwt.verify(rs256Token, publicKey);
console.log('RS256 验证通过:', decoded);
} catch (err) {
console.error('RS256 验证失败:', err.message);
}
一个小建议:生产环境别把密钥文件放在代码目录里。用环境变量或者专门的密钥管理服务。我习惯用 HashiCorp Vault 或者 AWS KMS 来管理私钥,运行时通过 API 获取。
总结一下核心要点
票据生成这件事,说复杂也复杂,说简单也简单。关键在于想清楚你的场景:
- 服务端生成策略:按票据类型区分策略,别一刀切。
- JWT 结构:Header、Payload、Signature 三段,Payload 不加密,别放敏感信息。
- 签名算法选择:单体用 HS256,微服务用 RS256。别为了省事选错。