4、Token体积优化:精简Payload字段、使用短字段名、压缩算法(如gzip)在JWT中的应用
JWT 令牌通常通过 HTTP Header(Authorization: Bearer)或 Cookie 传输。每次请求都携带令牌,其体积直接影响网络传输延迟、带宽消耗以及后端解析性能。一个臃肿的 JWT 可能包含冗余的 Payload 字段、过长的字段名,甚至未经过压缩的 Base64 编码数据。本章从三个维度系统性地优化 Token 体积:精简 Payload 字段、使用短字段名、应用压缩算法(如 gzip)。
4.1 精简 Payload 字段:只保留最小必要信息
JWT 的 Payload 部分存储声明(Claims),但并非所有声明都需要放在令牌中。冗余字段不仅增加体积,还扩大泄露面。核心原则是:只放身份验证与权限校验的必需字段,非关键信息通过后端缓存或数据库查询获取。
| 字段类别 | 推荐保留 | 建议移除或移至缓存 | 说明 |
|---|---|---|---|
| 用户标识 | sub(用户ID) |
用户名、邮箱、手机号 | 唯一标识即可,其他信息通过 ID 查询 |
| 权限/角色 | role(角色编码) |
权限列表、资源路径、菜单树 | 角色编码短,权限列表可放缓存 |
| 会话控制 | iat、exp、jti |
nbf、auth_time |
过期时间和唯一ID必须保留 |
| 业务扩展 | 租户ID、客户端类型 | 用户昵称、头像URL、配置项 | 仅保留路由/鉴权必需的扩展字段 |
sub 从 Redis 或本地缓存按需加载。
4.2 使用短字段名:压缩 Key 长度
JWT 的 Payload 是 JSON 格式,字段名(Key)本身会重复出现在每个令牌中。将长字段名替换为短缩写,能显著减少体积,尤其在令牌数量大、传输频繁的场景下效果明显。
| 原始字段名 | 短字段名 | 节省字节数(每次) |
|---|---|---|
user_id |
uid |
4 字节 |
role_code |
role |
5 字节 |
tenant_id |
tid |
6 字节 |
issued_at |
iat |
7 字节 |
expiration_time |
exp |
12 字节 |
使用短字段名时,需在服务端维护一份 字段映射表,确保解析时能正确还原。映射表可以放在配置中心或代码常量中,避免硬编码混乱。
// 字段映射表示例(Java)
public static final Map<String, String> JWT_FIELD_MAP = Map.of(
"uid", "user_id",
"role", "role_code",
"tid", "tenant_id",
"iat", "issued_at",
"exp", "expiration_time"
);
4.3 压缩算法(gzip)在 JWT 中的应用
即使 Payload 字段精简到极致,当令牌包含较长的权限编码或签名时,体积仍可能超过 1KB。此时,对 JWT 整体或 Payload 部分进行 gzip 压缩 可以进一步减少传输字节数。但需注意:JWT 是 Base64 编码的字符串,直接压缩效果有限;更优的做法是 先压缩 JSON Payload,再 Base64 编码。
- 传统方式: JSON Payload → Base64 编码 → 拼接 Header → 生成 JWT → 传输(Base64 字符串对压缩不友好)
- 优化方式: JSON Payload → gzip 压缩 → Base64 编码 → 拼接 Header → 生成 JWT → 传输(压缩率可达 40%~60%)
以下是一个 Java 实现示例,展示如何在生成 JWT 时对 Payload 进行 gzip 压缩:
import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPOutputStream;
import java.util.Base64;
public class JwtCompressionUtil {
public static String compressPayload(String jsonPayload) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) {
gzipOut.write(jsonPayload.getBytes("UTF-8"));
}
return Base64.getUrlEncoder().withoutPadding().encodeToString(baos.toByteArray());
}
public static String decompressPayload(String compressedBase64) throws Exception {
byte[] compressedBytes = Base64.getUrlDecoder().decode(compressedBase64);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (java.util.zip.GZIPInputStream gzipIn =
new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(compressedBytes))) {
byte[] buffer = new byte[1024];
int len;
while ((len = gzipIn.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
}
return baos.toString("UTF-8");
}
}
"zip": "gzip"),告知接收方 Payload 已被压缩。接收方在解析时先检查该声明,再决定是否解压。否则会导致解析失败。
4.4 三种优化策略的对比与选择
| 优化策略 | 体积缩减效果 | 实现复杂度 | 适用场景 |
|---|---|---|---|
| 精简 Payload 字段 | 中等(20%~40%) | 低 | 所有场景,优先实施 |
| 使用短字段名 | 中等(10%~25%) | 低 | 字段名较长且重复次数多的场景 |
| gzip 压缩 Payload | 高(40%~60%) | 中 | 令牌体积 > 1KB 或带宽敏感场景 |
建议按以下优先级实施:先精简字段 → 再缩短字段名 → 最后考虑压缩。前两步零成本且无副作用,压缩则需额外 CPU 开销和解压逻辑。
4.5 综合案例:一个 1.5KB 令牌的优化过程
假设一个原始 JWT 的 Payload 包含以下字段:
{
"user_id": "1234567890",
"user_name": "zhangsan",
"role_code": "admin",
"permissions": ["read:user", "write:user", "delete:user", "read:order"],
"tenant_id": "t-001",
"issued_at": 1710000000,
"expiration_time": 1710086400
}
经过三步优化后:
- 精简字段: 移除
user_name和permissions(权限列表移至 Redis) - 短字段名:
user_id→uid,role_code→role,tenant_id→tid,issued_at→iat,expiration_time→exp - gzip 压缩: 对精简后的 JSON 进行压缩
| 阶段 | Payload 体积 | 整体 JWT 体积(含 Header + 签名) | 缩减比例 |
|---|---|---|---|
| 原始 | ~210 字节 | ~1.5 KB | — |
| 精简字段后 | ~130 字节 | ~1.1 KB | ~27% |
| 短字段名后 | ~95 字节 | ~0.9 KB | ~40% |
| gzip 压缩后 | ~45 字节 | ~0.6 KB | ~60% |