密码正确只是开始。用户登录之后的每个环节——要不要再验一次、令牌怎么续、密码改了之后旧会话怎么办——都在安全与体验之间拉扯。我们把自建账号体系的规则写成了一份可以逐条解释的清单,本文是其中与安全直接相关的部分。
先分清四类凭证
混乱往往来自"所有凭证混为一谈"。我们的模型里有四种东西,各自独立管理:
| 凭证 | 作用 | 生命周期 | 撤销方式 |
|---|---|---|---|
| 访问令牌 | 携带请求身份 | 短时效 | 过期即失效 / 版本号失效 |
| 刷新令牌 | 换取新访问令牌 | 较长,一次有效 | 用后即换,旧令牌立即作废 |
| 设备令牌 | 标记"受信设备",跳过验证码 | 长期,可单独撤销 | 账号页手动移除 |
| 用户版本号 | 全量失效的总开关 | 随用户存在 | 改密 / 安全事件时 +1 |
把这张表当作设计契约:每类凭证管什么、活多久、怎么撤,三个问题都要有明确答案。
设备信任:跳过验证要有边界
陌生设备登录时要求邮箱验证码(第二因素);验证通过后可以勾选"记住此设备",之后这台设备登录直接放行。规则的关键全在边界上:
- 设备令牌是独立凭证——撤销设备不等于注销当前会话,注销会话也不影响设备信任,两者分开管理;
- 用户能在账号页看到所有受信设备并逐一移除(撤销路径必须对用户可见,否则"信任"就成了黑盒);
- "记住设备"只影响验证码环节,不改变密码校验与限流规则。
还有一个实现细节值得一提:设备令牌要绑定签发时使用的浏览器特征摘要,而不是完整指纹——变化频繁时宁可再验一次,不要因为指纹漂移把用户锁在门外。
令牌轮换:旧令牌必须死得干脆
访问令牌短时效、刷新令牌负责续期是常规做法。容易出错的是旧令牌的失效时点:
- 刷新成功后,旧刷新令牌立即作废——一次刷新只有一次续期,旧令牌不可复用;
- 如果有人拿着已作废的旧刷新令牌再来刷新,说明它可能已泄漏——此时应连带撤销整条会话链(该设备/该会话的所有令牌),而不是只拒绝这一次;
- 这条规则要进验收:刷新一次后,重放旧刷新令牌必须失败。
改密即全下线:用版本号一刀切
修改密码后,所有旧令牌立即失效——包括其他设备上的登录。实现上不靠枚举会话逐个撤销(可能漏、可能慢),而是一个用户级版本号:
令牌里带上签发时的版本号
校验时:令牌版本 == 当前版本,否则拒绝
修改密码:当前版本 += 1 → 全部旧令牌一次失效
这条规则牺牲了一点体验(改密后所有设备要重新登录),换来确定的语义:用户对"账号可能被盗、我改了密码"有立即、可解释的效果。同类事件(怀疑泄漏、管理员冻结)也走同一个开关,语义一致。
验证码环节的风控
邮箱验证码本身也要有规则:单码短时效、错误次数有限、重发有倒计时、按邮箱与 IP 双向限流。四个数字各自独立配置——拉长任一维度都会给爆破留下空间,缩短过多则误伤正常用户,我们的取值原则是"让自动化攻击不划算,让手滑的人能重来"。
小结
账号安全的工程质量,在于每条规则都能被解释:哪个凭证管什么、何时失效、用户如何自行撤销。解释不清的"安全设计",不是安全,是运气;而可以逐条解释的清单,才配得上"可以交付"。
