忘记密码与重置密码

「忘记密码」可能是你的产品里使用频率最高的功能。据统计,超过 30% 的用户在首次登录后 30 天内就会触发密码重置流程。如果这个环节设计得不够好——链接打不开、Token 过期太快、流程绕来绕去——用户不会反思自己记性差,只会对产品失去信任。

本文覆盖忘记密码与重置密码的完整链路:从入口设计到 Token 生成,从安全防护到用户体验,最后提供可直接落地的检查清单。


忘记密码流程设计

一个完整的忘记密码流程由四个阶段组成:入口发现、身份验证、重置链接发送、密码设置。每个阶段都有需要重点关注的产品决策。

入口发现

「忘记密码」入口的可发现性直接决定了用户是否能在第一时间找到帮助。这个入口应出现在以下位置:

  1. 登录页:在密码输入框下方或登录按钮下方,以文字链接形式展示,而非隐藏在三级菜单里
  2. 登录失败提示:当用户连续输入错误密码 2-3 次后,主动提示「忘记密码?」
  3. 账号管理页:已登录用户修改密码时,如果忘记当前密码,也应有重置入口

入口文案需要统一。常见的表达方式有「忘记密码?」「无法登录?」或「重置密码」。无论使用哪种,整个产品内应保持一致,避免用户在不同位置看到不同措辞而产生困惑。

身份验证

用户点击「忘记密码」后,系统需要验证其身份。最通用的方式是通过注册邮箱或手机号进行验证。这一步的核心设计原则是:

  • 只要求一个标识符:通常是邮箱地址。不要在这一步额外要求用户名、手机号或安全问题——每多一个输入框,流失率就上升一截
  • 输入框启用自动补全:设置 autocomplete="email" 属性,让浏览器和密码管理器能快速填入
  • 移动端使用正确的键盘类型:设置 inputmode="email",调出带 @ 符号的键盘布局

邮箱验证与重置链接发送

用户提交邮箱后,后端在数据库中查找该邮箱对应的账户。如果账户存在,生成重置 Token 并通过邮件发送。如果账户不存在,也必须显示相同的确认信息。这是防止账户枚举攻击的关键措施,后面会在安全章节详细展开。

邮件发送环节需要注意:

  • 发件人信息要可信:使用用户能识别的品牌名称和域名邮箱,避免被归入垃圾邮件
  • 邮件配置 SPF/DKIM/DMARC:防止钓鱼邮件冒用你的域名发送伪造重置链接
  • 正文不包含敏感数据:邮件内容只需包含重置按钮和有效期说明,不要附带用户名、账户 ID 等个人信息

重置链接生成

重置链接的安全性是整个流程的核心。链接中包含一个 Token,这个 Token 必须在不可预测性、有效期和使用约束三个维度上都做到位。

Token 生成

Token 必须由密码学安全的随机数生成器(CSPRNG)产生。Node.js 环境下使用 crypto.randomBytes(32),Python 环境下使用 secrets.token_urlsafe(32)。不要使用 Math.random()uuid() 或时间戳作为 Token 来源——这些方式的随机性不足,攻击者可以通过分析已知 Token 推测出后续 Token。

Token 的最低长度要求是 64 个字符(约 256 bit 熵)。使用 Base64 URL-safe 编码后作为链接参数传递。Token 中不应包含任何用户可识别信息(如邮箱、用户 ID),因为 URL 可能出现在浏览器历史记录、邮件客户端日志和 Referer 头中。

// Node.js 环境下的 Token 生成示例
import { randomBytes } from 'node:crypto'
 
function generateResetToken(): string {
  // 32 字节 = 256 bit 熵,Base64 URL-safe 编码后约 43 字符
  return randomBytes(32).toString('base64url')
}

Token 存储

数据库中存储的是 Token 的哈希值,而非明文。推荐使用 SHA-256 对 Token 进行哈希后存储。这样即使数据库被拖库,攻击者也无法直接使用泄露的 Token 完成密码重置。

-- 密码重置令牌表结构
CREATE TABLE password_reset_tokens (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id       UUID NOT NULL REFERENCES users(id),
  token_hash    VARCHAR(64) NOT NULL,  -- SHA-256 哈希后的 Token
  expires_at    TIMESTAMPTZ NOT NULL,
  used_at       TIMESTAMPTZ,           -- 使用后标记,NULL 表示未使用
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE(user_id)                       -- 每个用户同时只有一个有效 Token
);

有效期设计

Token 的有效期需要在安全性和用户体验之间找到平衡。有效期太短,用户还没来得及点击链接就过期了;有效期太长,Token 被泄露后的风险窗口就越大。

有效期适用场景说明
15 分钟高安全要求(金融、企业级)用户需要立即操作,适合对安全性要求极高的产品
30 分钟通用 SaaS 产品推荐默认值,覆盖大多数使用场景
1 小时国际化产品 / 跨时区场景考虑邮件投递延迟和用户时区差异
24 小时仅限内部工具不推荐面向公网的产品使用

OWASP 建议的上限是 1 小时。对于出海产品,考虑到邮件投递延迟(部分地区的 SMTP 链路可能较慢),30 分钟到 1 小时是合理的范围。

一次性使用

Token 必须在使用后立即失效。实现方式是在用户提交新密码时,检查 used_at 字段:如果为 NULL,则允许重置并更新 used_at 为当前时间;如果不为 NULL,则拒绝请求。

此外,每次用户发起新的重置请求时,应使该用户之前的所有 Token 失效。这可以通过在生成新 Token 前将旧记录的 used_at 标记为当前时间来实现,或者在表上对 user_id 加唯一约束。


重置密码页面设计

用户点击重置链接后,进入新密码设置页面。这个页面看似简单,却有不少细节影响转化率和安全性。

表单设计

重置密码页面需要两个输入字段:新密码和确认密码。两个字段应放在同一个页面上,不要拆成多步。

<form method="POST" action="/auth/reset-password">
  <input type="hidden" name="token" value="{{resetToken}}" />
 
  <label for="new-password">新密码</label>
  <input
    type="password"
    id="new-password"
    name="password"
    autocomplete="new-password"
    minlength="8"
    required
  />
 
  <label for="confirm-password">确认密码</label>
  <input
    type="password"
    id="confirm-password"
    name="confirmPassword"
    autocomplete="new-password"
    minlength="8"
    required
  />
 
  <button type="submit">设置新密码</button>
</form>

密码强度验证

密码强度规则直接影响账户安全。出海产品需要平衡安全要求和用户习惯——不同市场对密码复杂度的期望不同。

规则项最低要求推荐策略说明
最小长度8 字符10-12 字符NIST SP 800-63B 建议至少 8 字符
最大长度64 字符128 字符允许用户使用密码管理器生成的长密码
字符类型无强制建议混合使用NIST 不强制要求大小写/数字/特殊字符
黑名单检查必须必须拒绝常见弱密码(如 123456、password)
历史密码可选禁止重复最近 3 次防止用户反复使用相同密码

建议在输入框附近实时显示密码强度指示器(弱 / 中 / 强),让用户在输入过程中获得即时反馈。密码匹配验证应在确认字段失去焦点时(onblur)触发,而非等到表单提交后。

密码输入的可用性

  • 允许粘贴:不要设置 onpaste="return false"。用户使用密码管理器时,粘贴是主要输入方式
  • 提供「显示密码」开关:用一个眼睛图标切换 type="password"type="text",减少因输入错误导致的重试
  • 显示 Caps Lock 状态:在输入框内提示大写锁定是否开启,避免无意的大小写错误
  • 启用浏览器密码管理器:设置 autocomplete="new-password" 属性,让 Chrome、Safari 和 1Password 等工具能正确识别并保存新密码

安全考虑

密码重置流程是攻击者重点关注的入口。一个存在漏洞的重置流程等同于一个无需认证的登录通道。

Token 安全

安全措施实现方式风险等级
CSPRNG 生成crypto.randomBytes(32)不使用安全随机源会导致 Token 可预测
哈希存储SHA-256 哈希后存入数据库明文存储会在数据库泄露时暴露 Token
短有效期30 分钟到 1 小时过长有效期扩大泄露后的攻击窗口
一次性使用使用后标记 used_at重复使用会导致链接被他人截获后滥用
HTTPS 传输全站强制 HTTPSHTTP 环境下 Token 可能被中间人截获
固定重定向域名硬编码回调域名防止 Host 头注入将 Token 引向攻击者服务器

速率限制

速率限制是防止攻击者批量尝试重置密码或暴力破解 Token 的必要手段。

  • 按邮箱限制:同一邮箱每小时最多允许 3 次重置请求。超过限制后返回与正常请求相同的响应(不要返回「请求过于频繁」的错误,否则会暴露邮箱是否存在)
  • 按 IP 限制:同一 IP 每分钟最多允许 5 次重置请求。防止攻击者使用不同邮箱从同一 IP 发起批量攻击
  • Token 端点限制:对 /reset-password 的 POST 请求也做速率限制,防止攻击者暴力猜测 Token 值
  • 响应时间一致:无论邮箱是否存在于数据库中,接口的响应时间应保持一致。可以通过异步发送邮件或在所有路径上加入等量的处理延迟来实现,避免时序攻击

日志记录

密码重置流程的每一步都应记录审计日志,内容包括:

  • 重置请求发起时间、来源 IP、目标邮箱
  • Token 生成时间
  • Token 使用时间或失败原因(过期、已使用、无效)
  • 密码重置成功或失败的结果

日志中不应记录 Token 值本身,只记录 Token 的哈希前缀(如前 8 位),用于排查问题。

会话处理

密码重置成功后,应立即注销该用户的所有活跃会话。如果用户在多个设备上登录,旧会话不应继续有效。同时发送一封确认邮件,告知用户密码已更改,并提供「如果这不是你本人操作,请联系支持」的提示。


用户体验优化

安全性和用户体验不是对立关系。一个好的密码重置流程应该让用户感觉「简单但可靠」。

错误提示

场景推荐提示不推荐的提示
邮箱未注册「如果该邮箱已注册,你会收到一封重置邮件」「该邮箱不存在」
Token 已过期「重置链接已过期,请重新申请」附带返回链接「Token 无效」
Token 已被使用「此链接已被使用,请重新申请」「系统错误」
密码太弱具体说明不满足哪项规则「密码不符合要求」
两次密码不一致「两次输入的密码不一致」在确认字段下方实时显示提交后才提示

成功反馈

密码重置成功后,给用户一个明确的确认页面,而非直接跳转到登录页。确认页面应包含:

  1. 成功提示:「你的密码已成功重置」
  2. 自动跳转:3-5 秒后自动跳转到登录页,同时提供手动跳转链接
  3. 安全提示:「我们已向你的邮箱发送了密码变更通知」
  4. 异常联系方式:「如果这不是你本人操作,请立即联系我们」

自动登录的取舍

重置完成后是否自动登录用户,是一个需要权衡的设计决策。

  • 自动登录的好处:减少一步操作,用户可以直接进入产品
  • 自动登录的风险:如果重置链接被他人点击,攻击者会直接获得账户访问权限
  • OWASP 的建议:不推荐自动登录,让用户重新输入新密码登录,以确认其确实控制了邮箱账户

对于出海产品,建议采用折中方案:重置成功后跳转到登录页,但自动填入用户邮箱,用户只需输入新密码即可。


完整流程对比

阶段用户操作系统响应安全措施
1. 发现入口在登录页点击「忘记密码」展示邮箱输入表单
2. 提交邮箱输入注册邮箱并提交查找账户、生成 Token、发送邮件速率限制、响应时间一致
3. 检查邮箱收到重置邮件并点击链接验证 Token 有效性HTTPS、Token 校验
4. 设置新密码输入新密码和确认密码更新密码、注销旧会话Token 一次性使用、密码强度检查
5. 完成确认看到成功提示跳转登录页、发送确认邮件审计日志记录

案例分析

案例一:Notion 的密码重置流程

Notion 的密码重置流程以简洁著称。用户在登录页点击「Forgot password」后,只需输入邮箱地址,即可收到重置邮件。邮件中的链接有效期约为 30 分钟,点击后进入一个简洁的新密码设置页面。

Notion 的几个值得学习的细节:

  • 邮件发送后,页面显示「Check your email」,并明确告知「If you don't see the email, check your spam folder」
  • 重置页面只有一个密码输入框(不需要「确认密码」),而是在密码下方实时显示强度指示器
  • 重置完成后自动跳转到工作区,但对于开启了 2FA 的账户,会要求再次验证

案例二:Linear 的密码重置流程

Linear 在密码重置流程中融入了更多安全考量。用户提交邮箱后,Linear 不会立即发邮件,而是先展示一个验证码输入页面——用户会收到一封包含 6 位数字验证码的邮件,输入验证码后才会进入密码重置页面。

这种两步验证的好处是:即使攻击者获得了用户的邮箱访问权限,也需要能够通过邮箱接收验证码。这在一定程度上防止了邮箱泄露后的连锁攻击。缺点是增加了一步操作,但对于面向开发者的工具产品,用户对安全性的接受度更高。


完整流程

流程图画布 · 115%
Mermaid 流程图加载中...

检查清单

在设计或评审密码重置功能时,逐项确认以下内容:

  • 「忘记密码」入口在登录页清晰可见
  • 邮箱输入框设置了 autocomplete="email"inputmode="email"
  • 密码输入框设置了 autocomplete="new-password"
  • Token 使用 CSPRNG 生成(如 crypto.randomBytes(32)
  • Token 在数据库中以哈希形式存储,不是明文
  • Token 有效期不超过 1 小时(推荐 30 分钟)
  • Token 只能使用一次,使用后自动失效
  • 同一用户发起新重置请求时,旧 Token 自动失效
  • 接口对邮箱和 IP 维度都做了速率限制
  • 邮箱不存在时返回与存在时相同的提示信息和响应时间
  • 重置链接通过 HTTPS 传输,回调域名硬编码
  • 密码重置成功后注销所有活跃会话
  • 密码重置成功后发送确认邮件通知用户
  • 重置流程的每一步都有审计日志
  • 密码强度规则在输入时实时反馈,而非提交后才提示
  • 允许用户粘贴密码,不阻止密码管理器
  • 提供「显示密码」开关
  • 所有错误提示不泄露账户是否存在

参考资料

  1. OWASP Forgot Password Cheat Sheet — OWASP 官方密码重置安全指南
  2. Authgear: Password Reset Best Practices — 密码重置最佳实践与常见陷阱
  3. UX Patterns: Password Reset — 密码重置交互模式参考
  4. SuperTokens: Forgot Password Flow Implementation Guide — 忘记密码流程实现指南
  5. Clerk: Don't Underestimate the Value of a Secure Forgot Password Flow — 安全忘记密码流程的价值分析
  6. NIST SP 800-63B: Digital Identity Guidelines — 美国国家标准与技术研究院数字身份指南,密码长度与复杂度建议
  7. Logto: 重置密码用户流程 — 中文开发者文档参考