注册状态机:用户创建、邮件码投递、验证要分开审计
在注册体系里,状态机在"用户创建插入了一行"时并不算完成:邮件码投递与验证必须是独立、可审计的状态转换。运营约束是可审计性——从未验证地址到活跃账号的每次变化事后都要能解释,同时攻击者获得的信息越少越好。
短答案:把用户创建、邮件码投递、验证建模为独立的、服务端校验的状态转换,验证成功后才推进业务账号。这条规则同样适用忘记密码流程——密码重置也是被审计的转换,不是注册设计的例外。
事故教训:用户行不等于证据
作者曾把"用户已创建"当作 onboarding 管道里有用的里程碑。API 返回成功,下一个 worker 就配置了调度员账号。审计时,证据比运营状态弱得多:日志里有请求和用户 ID,但无法证明邮件码是否发出、尝试了几次、哪个事件授权了激活。缺口不是戏剧性的事故,而是一个缺失的状态边界。
不变式
每个认证动作需要:持久化状态、有界转换、不含密钥的审计事件。投递事件可以说 email_code_sent,验证事件可以说 email_verified——任何事件都不该携带验证码本身。存储哈希或等价值,而不是明文码。
实践建议
- 把创建/投递/验证/激活拆成独立状态转换,别在"插入行"就宣布完成;
- 审计事件不含密钥:事件记录状态变化,密钥只存哈希;
- 忘记密码走同一条被审计的转换路径,别当特例绕开。