【问题标题】:Is this a good way to implement "forgot your password?" feature?这是实现“忘记密码”的好方法吗?特征?
【发布时间】:2025-12-08 01:25:01
【问题描述】:

我目前正在设计一个小型网站,我想实现“忘记密码?”特征。这是我的想法:

忘记密码的网页会有这个:

Email: (input box)
(button: "Make temp password")

点击“制作临时密码”按钮:

  1. 为 SQLi/XSS 清理电子邮件输入
  2. 检查电子邮件地址是否存在于数据库中
    • 如果没有,告诉用户创建新帐户或检查电子邮件拼写
  3. 创建随机密码(即“xh5MvQe”)并将其放入数据库中的用户临时密码字段中。
    • 如果已经存在则覆盖。不要触摸主密码。在接下来的 48 小时内,用户将能够使用两个密码登录。
  4. 获取当前 UTC 时间加上 48 小时,并将其放入数据库中用户的临时密码到期日期字段。如果已经存在则覆盖。
  5. 使用临时密码向用户发送电子邮件

我的数据库有用户表的这些字段:

(primary key) user id
name
nickname
(non null, unique) email
(non null) password (encrypted)
temporary password
temporary password expiration

一旦用户获得密码,他们就可以登录并更改他们的主密码。

问题是:这是实现此功能的安全方式吗?我主要关心的是通过电子邮件发送纯文本密码。我已经看到了另一种使用电子邮件哈希/时间戳/随机 id 作为 GET 参数生成链接的方法,但我看不出这有多安全。如果我错了,请纠正我。

【问题讨论】:

  • 我同意这两种方法同样安全,并注意电子邮件中的链接是可点击的。
  • 您列出的步骤很好。您无法控制用户如何管理他们的电子邮件地址,但增加一些安全性的好方法是在重置密码之前提出安全问题。如果一些用户也忘记了这一点,这可能是不明智的,所以这取决于你想要它的方式以及更有用的东西。
  • 只是大声思考。但是,如果您担心通过电子邮件发送密码,那么使用 2 个恢复密码会不会太费力?您可以通过电子邮件发送一份,然后显示一份。这样您就知道电子邮件没有被截获,因为只有请求密码的用户才有第二个密码。几乎就像“我拥有的东西”+“我知道的东西”之类的东西。

标签: php forgot-password


【解决方案1】:

阅读:The definitive guide to form-based website authentication

向用户发送任何密码是不好的,因为您无法确定他的机器/电子邮件帐户是否/将是安全的。如果您生成并发送密码,用户可能不会更改它。不过,您无法避免发送某种标识令牌以进行恢复,因此到期日期是个好主意。

切勿在任何地方存储纯密码。使用盐渍哈希。攻击者访问您的数据库已经够糟糕的了,但对于为其他服务使用相同密码的用户来说更糟糕 - 他们总是这样做,愚蠢的用户。

因此,这是一种避免在用户机器上保存纯文本密码的方法:

  1. 为 SQLi/XSS 清理电子邮件输入
  2. 检查电子邮件地址是否存在于数据库中
    • 如果没有,告诉用户创建新帐户或检查电子邮件拼写
  3. 创建随机恢复登录令牌(即“xh5MvQe”)并将其哈希值放入数据库中的用户密码恢复哈希字段中。
    • 如果已经存在则覆盖。不要触摸主密码。用户将能够
      • 使用他的旧密码登录,删除恢复哈希。
      • 在接下来的 48 小时内使用恢复令牌密码登录一次,这会强制用户选择新密码。
  4. 获取当前 UTC 时间加上 48 小时 [...]
  5. 使用一次性恢复登录令牌链接向用户发送电子邮件。

【讨论】:

  • +1 只为“愚蠢的用户”(哦,还有有用的信息......)
【解决方案2】:

我认为您拥有的伪系统是安全的,我将添加一个步骤 6,即当用户更改其实际密码时清除临时密码。如果您担心未加密的电子邮件 - 您需要确保临时密码在用户成功更改密码后的 48 小时内不可用。

【讨论】:

    【解决方案3】:

    这是一种非常不寻常的方式。
    大多数时候发送的是超链接,而不是密码本身。

    允许用户输入另一个密码的超链接。
    由电子邮件和过期时间组成,用一些哈希密封。

    此外,我认为针对 XSS 清理电子邮件毫无意义。
    “清理 SQLi 的电子邮件输入”作为一个单独的步骤对我来说听起来很奇怪。必要的准备不是 SQL 查询创建的本地部分吗?为什么它恰好是全局算法的一部分?

    所以,对我来说,它看起来像两部分算法:

    1. 要求用户输入他们的电子邮件。
    2. 检查电子邮件地址是否存在于数据库中
    3. 如果是,请发送指向该电子邮件的超链接

    用这样的代码

    $salt  = "35onsoi2e=-7#%3%g03skl";
    $time  = time();
    $hash  = MD5($_POST['email'].$time.$salt);
    $email = urlencode($_POST['email']);
    $link  = "http://example.com/register.php?email=$email&time=$time&hash=$hash";
    

    点击链接后,用户登陆恢复密码脚本,

    1. 使用相同的哈希算法进行验证。
    2. 检查过期时间。
    3. 如果一切正常,提示用户输入新密码
    4. 重置数据库中的密码。

    【讨论】: