密码重置token必须带服务端强制过期时间,用crypto/rand生成32字节随机数据并存expires_at字段,校验时原子更新used状态且仅允许使用一次。

密码重置流程中 token 生成必须带过期时间
Go 里直接拼接随机字符串当重置 token 是危险的——没过期机制,一旦泄露就永久有效。用 crypto/rand 生成安全随机字节,再转成 base64 或 hex,同时务必存入数据库时绑定 expires_at 字段。
常见错误:用 time.Now().Unix() 拼在 token 后当“过期标识”,这不防篡改,后端必须独立校验时间。
- 推荐长度:32 字节随机数据(base64 后约 44 字符)
- 存储时写死过期时间,比如
time.Now().Add(15 * time.Minute) - 校验 token 前先查库,立刻判断
expires_at是否已过期,过期则直接拒绝,不进后续逻辑
发送重置邮件时避免暴露用户是否存在
前端点“忘记密码”后,无论邮箱是否存在,后端都返回成功响应(如 HTTP 200 + “邮件已发送”),但实际只对已注册邮箱发信。这是基础安全要求,防止邮箱枚举。
实现上别写 if user == nil { return } 就完事——得统一走异步发信流程,哪怕用户不存在也模拟一次“发送成功”。否则响应时间差异可能被用来探测账号。
- 用
goroutine异步调用邮件发送函数,主流程不等结果 - 日志里记录“重置请求收到”,但不要记“用户不存在”这类敏感信息
- 邮件模板里别出现用户名,用“尊敬的用户”代替,避免泄露注册状态
重置接口必须验证 token 有效性且仅允许使用一次
POST /api/reset-password 这类接口,不能只校验 token 格式和过期时间,还要查数据库确认该 token 的 used 字段为 false,且更新时用原子操作标记为已用。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
典型翻车点:先 SELECT 再 UPDATE,并发请求下可能两个请求都通过校验,导致同一 token 重置两次密码。
- 用 SQL 实现原子校验+标记:
UPDATE password_resets SET used = true WHERE token = ? AND expires_at > ? AND used = false - 检查
sql.Result.RowsAffected()是否为 1,不是 1 就说明 token 已用或过期 - 重置成功后立即删掉该 token 记录(或保留归档,但不再参与任何校验)
新密码设置前必须再次校验旧密码?不,这里容易搞错
密码重置场景下,用户已经丢失原密码,所以 reset-password 接口**不该要求提供旧密码**。但很多人误以为要“二次确认”,反而加了无意义且破坏体验的字段。
真正该做的是:在设置新密码前,确保当前请求来自合法的、未过期且未使用的 token,并对新密码做强度校验(比如长度 ≥8、含大小写字母+数字)。
- 别在重置接口里加
old_password字段——那是“修改密码”功能的事 - 新密码要走
bcrypt.GenerateFromPassword,别用md5或明文存 - 如果业务要求更高安全等级(如金融类),可在此步加短信/邮箱二次确认,但那是额外流程,不属于基础重置链路
重置功能里最易被忽略的其实是 token 存储方式:别存在客户端 cookie 里传回来,要用 HTTP POST body 显式提交;数据库里 token 字段必须加索引,否则高并发查 token 会拖慢整个流程。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










