令牌里的用户主键不要明文
访问令牌的 sub(主体)若直接是数据库自增 ID 或雪花 ID,泄露后攻击者可以枚举用户。签发时用客户端标识派生密钥、把用户 ID 做对称加密再放进 sub,能把令牌可携带的标识和库里的主键形态切开。
背景与问题
JWT 常被复制到日志、前端存储、第三方回调里。sub 一旦是连续或可猜测的主键,攻击面就不只是令牌被盗用,还包括按 ID 探测用户是否存在、批量撞库侧写。把主键明文塞进令牌,等于把内部标识体系暴露给所有能看见令牌的人。
即使令牌签名完好,明文主键仍然可以被离线阅读。签名保护的是完整性,保护不了「这个数字不该被外人看见」。
做法
签发时加密,校验时先解密再走授权。解密失败视为未授权,不要把密文当主键查库。密钥与客户端绑定,不同客户端即使截获同一密文也无法互解。
csharp
string EncryptSubject(long userId, string clientId)
{
var key = DeriveKey(clientId); // 例如 HKDF:配置主密钥 + clientId
return AesEncrypt(userId.ToString(), key);
}
bool TryResolveUserId(string subject, string clientId, out long userId)
{
userId = 0;
try
{
var plain = AesDecrypt(subject, DeriveKey(clientId));
return long.TryParse(plain, out userId);
}
catch
{
return false;
}
}
授权策略解析声明时,明文用户 ID 等于 AES_Decrypt(subject, clientId)。解析失败直接拒绝,避免密文被当成另一个用户的主键命中错误数据。
取舍与坑点
加密增加签发和校验成本,换来的是标识形态隔离。轮换密钥时要兼容旧令牌,或强制全员重新登录。可逆加密意味着持有密钥的一方仍能还原主键,密钥保管比算法名称更重要。
若只需不可关联性、业务上不需要还原用户 ID,应改用单向派生或成对映射表。对称解密适合「对外不可读、对内还要查库」的场景。
结语
令牌里的用户主键应加密或不可逆派生后再放入 sub,库内 ID 不应直接当成对外主体标识。