Token生成策略:Session ID、Access Token 和 Refresh Token 到底用什么生成
很多教程只讲 Token 怎么传、怎么校验,却没有说明它到底是怎么生成的。
先记住一个结论:Token 没有统一的生成算法。 不同 Token 的职责不同,生成方式也不同。

一、先分清两类 Token
Token 大致分为两类:
因此,不能简单地说“Access Token 和 Refresh Token 都是随机数”。
Opaque Access Token 通常是安全随机数。
JWT Access Token 是声明数据加数字签名。
Refresh Token 通常采用安全随机字符串,但标准并未限制它只能使用这种格式。
OAuth 2.0 允许 Access Token 是服务端查询用的标识,也允许它自包含授权信息和签名;Refresh Token 对客户端通常是不透明字符串。
二、Session ID:使用密码学安全随机数
Session ID 本质上是一张临时登录凭证:谁拿到它,谁就可能以当前用户身份访问系统。
推荐生成流程:
操作系统随机源 → CSPRNG → 随机字节 → Base64URL / Hex → Session ID
这里的 CSPRNG 是“密码学安全伪随机数生成器”,常见实现包括:
Java:
SecureRandomNode.js:
crypto.randomBytes()浏览器:
crypto.getRandomValues()
OWASP 要求 Session ID 至少具有 64 位熵;自行实现时,建议直接生成至少 128 位随机数据。实际项目使用 16~32 字节较常见。
Java 示例
import java.security.SecureRandom;
import java.util.Base64;
public final class SecureTokenGenerator {
private static final SecureRandom RANDOM = new SecureRandom();
private SecureTokenGenerator() {
}
public static String generate(int byteLength) {
byte[] bytes = new byte[byteLength];
RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(bytes);
}
public static void main(String[] args) {
// 32 字节 = 256 位随机数据
System.out.println(generate(32));
}
}
Spring Boot 使用容器 Session 时,通常不需要自己生成 JSESSIONID,Tomcat 等 Web 容器会负责生成和管理。
三、Opaque Access Token:也是安全随机字符串
Opaque Access Token 不携带可读取的用户信息,只充当查询键:
Access Token → Redis / 数据库 / 认证中心 → 用户、权限、过期时间
生成方式可以和 Session ID 相同:
import { randomBytes } from "node:crypto";
const accessToken = randomBytes(32).toString("base64url");
优点是容易立即吊销,权限变化也能及时生效;代价是资源服务器通常需要查询服务端状态。
四、JWT Access Token:不是随机数
JWT 的结构是:
Base64URL(Header) + Base64URL(Payload) + Signature
Payload 可能包含:
{
"sub": "10001",
"scope": "order:read",
"exp": 1784000000,
"jti": "随机唯一值"
}
JWT 的安全性主要来自:
签名密钥没有泄露;
服务端正确校验签名算法、签发者、受众和过期时间;
Token 生命周期足够短。
JWT 的 Payload 默认只是编码,不是加密。不要在里面放密码、身份证号等敏感数据。
jti 可以使用安全随机数或 UUID,但这不代表整个 JWT 是随机生成的。
五、Refresh Token:通常使用更长的安全随机值
Refresh Token 生命周期较长,只用于向认证服务器换取新的 Access Token,不应发送给资源服务器。
常见做法:
Refresh Token = CSPRNG 生成 32~64 字节随机数据
Node.js 示例:
import { createHash, randomBytes } from "node:crypto";
const refreshToken = randomBytes(32).toString("base64url");
// 数据库保存哈希,而不是保存明文 Token
const refreshTokenHash = createHash("sha256")
.update(refreshToken)
.digest("hex");
为什么可以直接使用 SHA-256 保存 Refresh Token 哈希,而密码通常要使用 Argon2、bcrypt?
因为安全随机 Token 本身已经具有很高熵,攻击者无法像猜常见密码一样进行字典攻击。前提是 Token 必须由 CSPRNG 生成,并且长度足够。

更稳妥的策略是 Refresh Token Rotation:
客户端使用 Refresh Token A 刷新;
服务端签发新的 Access Token 和 Refresh Token B;
Refresh Token A 立即失效;
如果 A 再次出现,说明可能发生了复制或泄露,服务端撤销整条登录会话。
RFC 9700 要求公共客户端采用 Refresh Token Rotation 或发送方约束 Token 等机制,以检测刷新令牌重放。
六、UUID 能不能作为 Token?
UUID v4
UUID v4 的主体来自随机数,正确实现时大约具有 122 位随机性。普通系统用它作为 Session ID 通常不会轻易被猜中。
但职责上更建议这样区分:
UUID:用于标识一条记录
CSPRNG 随机串:用于生成秘密凭证
自行设计 Token 时,SecureRandom 或 randomBytes() 更直接,随机长度也更容易控制。
UUID v1、UUID v7、ULID 和雪花 ID
这些方案主要解决时间排序、分布式唯一性或数据库索引问题。它们可能暴露时间或结构信息,不适合直接作为秘密 Token。
它们可以用来生成:
用户 ID
订单 ID
Session 记录主键
请求追踪 ID
不建议直接用来生成:
登录 Token
Refresh Token
密码重置 Token
邮箱验证 Token

七、常见错误
1. 使用普通随机数
new java.util.Random().nextLong();
Math.random().toString(36);
这些随机数主要用于模拟、抽样或普通业务,不适合生成安全凭证。
2. 使用用户 ID 加时间戳
userId + timestamp
用户 ID 和登录时间通常都可以估算,攻击者能够缩小枚举范围。
3. 对可预测数据做 MD5 或 SHA-256
SHA-256(userId + timestamp)
哈希只能改变数据表现形式,不能凭空增加随机性。输入可预测,输出仍然可以被批量计算。
4. 只关注字符串长度
下面的字符串很长,但完全不安全:
user-10001-login-2026-07-13-12-00-00
安全性取决于有效随机熵,而不是肉眼看到的字符数量。
八、实际项目怎么选
普通前后端分离系统可以采用:
Access Token:短期 JWT,约 10~30 分钟
Refresh Token:32 字节安全随机串,约 7~30 天
Session ID:UUID 或内部数据库主键,仅用于服务端标识会话记录
有效期没有统一答案,需要根据账号价值、使用频率、设备安全能力和风险控制策略调整。
九、最后总结
一句话记忆:
业务 ID 用 UUID;秘密凭证用 CSPRNG;需要自包含授权信息时才使用 JWT。
更准确地说:
Session ID:通常是密码学安全随机数;
Opaque Access Token:通常是密码学安全随机数;
JWT Access Token:声明数据加数字签名;
Refresh Token:通常是密码学安全随机数,并配合哈希存储和轮换机制。
参考资料
Token生成策略:Session ID、Access Token 和 Refresh Token 到底用什么生成
https://lautung.com/archives/session-id%E3%80%81access-token-%E5%92%8C-refresh-token-%E5%88%B0%E5%BA%95%E7%94%A8%E4%BB%80%E4%B9%88%E7%94%9F%E6%88%90
评论