账号模型与生命周期
账号需要一个稳定的内部 ID。邮箱、手机号、第三方登录方式和企业归属都可能变化,把其中任何一项直接作为业务主键,会让换绑、合并与注销变成跨表迁移。
以下是业务建模建议,不是某个认证协议规定的数据库结构。
分开保存账号、标识与成员关系
| 实体 | 核心字段 | 约束 |
|---|---|---|
users | id、status、security_version | ID 稳定,账号状态独立于凭据 |
login_identifiers | user_id、type、normalized_value、verified_at | 按产品的全局或租户范围唯一 |
password_credentials | user_id、password_hash、changed_at | 不保存明文或可逆密码 |
external_identities | user_id、issuer、subject | 联合身份键唯一 |
authenticators | user_id、type、status、认证器数据 | 一人可以拥有多个认证器 |
memberships | tenant_id、user_id、status | 一人可以加入多个租户 |
sessions | user_id、会话凭据摘要、时间、撤销状态 | 账号与设备会话分开管理 |
OIDC 的主体键使用 (issuer, subject),不要只保存昵称或邮箱;sub 的语义与约束见 OIDC Core §2。同一个自然人在不同身份提供方、不同 pairwise subject 范围内可能对应不同标识,应用需要显式关联。
认证器数据必须按类型分开处理:密码存慢哈希,TOTP 秘钥需要受保护的可恢复存储,WebAuthn 保存公钥。把所有凭据统一塞进一个不加约束的 secret 字段,会隐藏完全不同的保护要求。
标识规范化需要业务规则
邮箱的域名部分可以按既定规则规范化;不要擅自删除邮箱本地部分的点号或 +tag,这些行为并非所有邮件服务都相同。是否大小写折叠也应成为产品规则,在注册、查询、换绑与唯一索引中一致执行。
手机号需要国家或地区信息,使用可维护的号码解析库统一格式。号码合法、当前可接收验证码和长期归属某个人,是三个不同结论。号码回收后,依靠旧手机号自动恢复账号可能把资产交给新持有人。
登录标识的唯一性最终由数据库保证。先查“未占用”再插入只是用户体验优化,不能阻止两个并发注册同时成功。
注册与激活
以下是一种可选的账号状态模型。图中的解除限制与撤回注销都需要满足产品策略,不能由一次普通登录自动触发。
临时登录限流与账号状态分别记录,避免一次验证码失败把整个账号置为不可用。未验证的预注册请求可以到期清理,不必全部创建为正式账号。
注册事务至少处理以下边界:
- 创建短期注册请求,绑定目标标识、用途和有效期。
- 完成邮箱或手机验证,原子消费验证凭据。
- 在事务内创建或激活账号、写入已验证标识,由唯一约束处理并发冲突。
- 提交成功后发送欢迎消息等非关键通知,通知失败不回滚已完成的注册。
不应让未验证注册无限占用某个邮箱。可以使用短期预注册记录和清理策略,也可以在验证成功时才创建正式账号。公开返回信息与内部错误码分开,减少账号枚举,具体见密码与恢复。
绑定、登录与合并是三种操作
| 操作 | 已有证明 | 结果 |
|---|---|---|
| 使用外部身份登录 | 该提供方的有效认证结果 | 找到已绑定的本地账号 |
| 给现有账号绑定身份 | 当前账号近期认证 + 新身份认证 | 新增一条外部身份关联 |
| 合并两个本地账号 | 对两个账号均有足够控制证明 | 按业务规则迁移或关联资产 |
默认不因外部提供方返回同一邮箱而自动绑定已有账号。邮箱声明、提供方验证规则与本地账号控制权不完全等价;即使 email_verified=true,仍需符合本地信任与关联策略。OWASP 的联合账号关联建议要求对关联过程单独防护。
绑定事务需要唯一约束:同一外部身份不能同时属于两个用户。若已被其他用户绑定,返回受控冲突,不做“最后写入覆盖”。解绑最后一种可用登录方式前,应先建立并验证替代方式。
账号合并还要处理订阅、余额、订单归属、团队角色和审计历史。通常更适合单独的业务流程,而不是在登录回调中执行。原账号可保留不可登录的映射记录,避免历史外键被随意改写。
换绑与恢复
已登录用户换绑邮箱时,既要证明能控制新邮箱,也要证明此次操作来自当前账号控制者。可组合近期密码或强认证、新邮箱验证、旧渠道通知;高风险账号需要延迟生效或人工复核。
所有换绑请求绑定 user_id、新标识、用途与安全版本。发起后密码被重置、账号被封禁或版本变化,原请求应失效,避免旧链接在账号恢复后继续改变控制权。
恢复过程详见密码、验证码与账号恢复,认证器丢失见多因素认证与 Passkey。
企业成员离职与账号注销
离开某个企业通常只应禁用相应 membership,不应自动删除个人账号及其他企业成员关系。租户角色也不能保存在全局 users.role 中。
全局封禁需要拒绝新登录、撤销刷新资格,并让现有会话按确定的时限失效。删除成员则至少影响该租户内的访问、令牌刷新和后台任务;其他租户是否受影响取决于账号风险。
账号注销还涉及资产结清、数据保留与匿名化。产品应定义可撤销等待期、最终删除范围和标识复用规则。保留审计记录的必要性、期限与访问控制需由业务和适用规则确定,不能用“软删除”无限期保留所有个人数据。
参考资料
- OpenID Foundation:OpenID Connect Core §2 — ID Token
- OWASP:Secure Federated Account Linking