用户验证重置密码页

时间:2022-11-15 12:31:56

I am writing a password-reset page for my website. Here's my idea:

我正在为我的网站写一个密码重置页面。这是我的想法:

a. User click the "forgot password" link on the login page

a.用户点击登录页面上的“忘记密码”链接

b. Redirect to my password-reset page

重定向到我的密码重置页面。

c. User enter his email address

c.用户输入电子邮件地址

d. A email message sent to the email address with the link to reset his/her password. The link has security code like ?code="xxxx" in it.

d.发送到电子邮件地址的电子邮件,带有重置其密码的链接。这个链接的安全代码是?code="xxxx"。

e. User open the link and enter new password, and then click the submit button.

e.用户打开链接,输入新密码,点击提交按钮。

f. My page change user's password.

f.我的页面更改用户的密码。

My question is for step f. In step e, when user opened the link, I could verify his security code and then show the 'new password' and the 'confirm password' fields to user. But when the user clicked the submit button, how could I know this is a real request submited by the user instead of a hacker? Maybe I am wrong, but I think hacker can easily simulate such field data, since there is no validation fields.

我的问题是关于步骤f。在步骤e中,当用户打开链接时,我可以验证他的安全码,然后向用户显示“新密码”和“确认密码”字段。但是当用户点击提交按钮时,我怎么知道这是一个由用户而不是黑客提交的真实请求呢?也许我错了,但是我认为hacker可以很容易的模拟这样的字段数据,因为没有验证字段。

There are some idea I can think of to validate the request in step f, but I don't know whether they are right. 1. Add a encrypted cookie in step e and check it in step f? 2. Use a session variable in step e and check it in step f? 3. Add a hidden field in step e and check it in step f?

我可以在步骤f中考虑一些验证请求的方法,但是我不知道它们是否正确。1。在步骤e中添加加密cookie,并在步骤f中检查它?2。在步骤e中使用会话变量,在步骤f中检查它?3所示。在步骤e中添加一个隐藏字段,并在步骤f中检查它?

Are those approaches ok? Which one is better, or is there any better one?

这些方法还好吗?哪个更好,哪个更好?

Thanks in advance.

提前谢谢。

3 个解决方案

#1


3  

A user entering their username and reset code should log them into the site just as their username and password would. The difference is you then immediately force them to change their password. With this password reset method you're implicitly trusting that the user is the owner of the email account where the code was sent.

用户输入用户名和重置代码时,应该像登录用户名和密码一样登录网站。不同的是,你会立即强迫他们更改密码。使用这个密码重置方法,您可以隐式地信任用户是发送代码的电子邮件帐户的所有者。

Edit:

Ok, so I don't know the first thing about ASP.net.

好吧,我不知道关于ASP.net的第一件事。

However, I've handled this problem many times before. Here is a solution of mine in PHP:

然而,我以前已经处理过这个问题很多次了。下面是我的PHP解决方案:

<?php
class AuthController extends Zend_Controller_Action
{
    public function identifyAction()
    {
        if ($this->_request->isPost()) {
            $username = $this->_getParam('username');
            $password = $this->_getParam('password');

            if (empty($username) || empty($password)) {
                $this->_flashError('Username or password cannot be blank.');
            } else {
                $user = new User();
                $result = $user->login($username, $password);

                if ($result->isValid()) {
                    $user->fromArray((array) $this->_auth->getIdentity());

                    if ($this->_getParam('changepass') || $user->is_password_expired) {
                        $this->_redirect('auth/change-password');
                        return;
                    }
                    $this->_doRedirect($user);
                    return;
                } else {
                    $this->_doFailure($result->getIdentity());
                }
            }
        }
        $this->_redirect('/');
    }

    public function forgotPasswordAction()
    {
        if ($this->_request->isPost()) {
            // Pseudo-random uppercase 6 digit hex value
            $resetCode = strtoupper(substr(sha1(uniqid(rand(),true)),0,6));

            Doctrine_Query::create()
                ->update('dUser u')
                ->set('u.reset_code', '?', array($resetCode))
                ->where('u.username = ?', array($this->_getParam('username')))
                ->execute();

            $mail = new Zend_Mail();
            $mail->setBodyText($this->_resetEmailBody($this->_getParam('username'), $resetCode));
            $mail->setFrom('no-reply@example.com', 'Example');
            $mail->addTo($this->_getParam('username'));
            $mail->setSubject('Forgotten Password Request');
            $mail->send();


            $this->_flashNotice("Password reset request received.");
            $this->_flashNotice("An email with further instructions, including your <em>Reset Code</em>, has been sent to {$this->_getParam('username')}.");
            $this->_redirect("auth/reset-password/username/{$this->_getParam('username')}");
        }
    }

    public function resetPasswordAction()
    {
        $this->view->username = $this->_getParam('username');
        $this->view->reset_code = $this->_getParam('reset_code');

        if ($this->_request->isPost()) {
            $formData = $this->_request->getParams();
            if (empty($formData['username']) || empty($formData['reset_code'])) {
                $this->_flashError('Username or reset code cannot be blank.');
                $this->_redirect('auth/reset-password');
            } elseif ($formData['new_password'] !== $formData['confirm_password']) {
                $this->_flashError('Password and confirmation do not match.');
                $this->_redirect('auth/reset-password');
            } else {
                $user = new User();
                $result = $user->loginWithResetCode($formData['username'], $formData['reset_code']);

                if ($result->isValid()) {
                    $user->updatePassword($result->getIdentity(), $formData['new_password']);

                    $user->fromArray((array) $this->_auth->getIdentity());
                    $this->_setLegacySessionData($user);

                    $this->_flashNotice('Password updated successfully!');
                    $this->_doRedirect($user);
                } else {
                    $this->_doFailure($result->getIdentity());
                    $this->_redirect('auth/reset-password');
                }
            }
        }
    }

    protected function _doFailure($username)
    {
        $user = Query::create()
            ->from('User u')
            ->select('u.is_locked')
            ->where('u.username = ?', array($username))
            ->fetchOne();

        if ($user->is_locked) {
            $lockedMessage = Config::get('auth.lock_message');
            if (!$lockedMessage) {
                $lockedMessage = 'This account has been locked.';
            }
            $this->_flashError($lockedMessage);
        } else {
            $this->_flashError('Invalid username or password');
        }
    }
}

If you can follow this, it should give you a good idea of what to do. I'll try to summarize:

如果你能做到这一点,它会让你知道该怎么做。我将试着总结:

identifyAction

This is the regular "login" using username and password. It logs the user in and stores their identity in the session.

这是使用用户名和密码的常规“登录”。它记录用户并将其标识存储在会话中。

forgotPasswordAction

This presents the user with a form requesting their username. After entering their username a reset code is generated, stored in their entry in the user table, and they are emailed as well as redirected to the reset password page. This page is unauthenticated, the user is not logged in.

这将向用户提供一个请求用户名的表单。输入用户名后,将生成一个重置代码,存储在用户表的条目中,并通过电子邮件将其发送到重置密码页面。此页面未经身份验证,用户未登录。

resetPasswordAction

This is where the user is presented with the "resetPassword" form. They must provide their username and the reset code they received via email. This authenticates the user with the given username and reset code, just as if the reset code were a password. If the credentials are valid the user is then redirected to the changePassword action where they are permitted to change their password. The changePasswordAction (not shown) requires the user be authenticated (logged in) either via username/password or username/resetCode

在这里,用户将看到“resetPassword”表单。他们必须提供他们的用户名和他们通过电子邮件收到的重置代码。这将使用给定的用户名和重置代码对用户进行身份验证,就像重置代码是密码一样。如果凭证是有效的,那么用户将被重定向到changePassword操作,在那里他们可以修改密码。changePasswordAction(未显示)要求用户通过用户名/密码或用户名/resetCode进行身份验证(登录)

Hope this helps.

希望这个有帮助。

#2


0  

If your code that you're emailing is a GUID or some such ID, there is a statistically low chance that someone can guess that code. If you additionally had the link include a hashed version of their email or some other way of linking the code to the user, I think you'd be pretty well safe from malicious input.

如果你正在发送电子邮件的代码是GUID或类似的ID,那么从统计学上讲,人们猜测代码的可能性很小。如果你有另外的链接,包括一个哈希版本的他们的电子邮件或一些其他方式连接代码到用户,我认为你是相当安全的恶意输入。

I'd be more worried about people being spammed from step c/d, unless you're doing some sort of verification of the email existing currently in your database.

我更担心的是人们会被来自步骤c/d的垃圾邮件所困扰,除非您正在对数据库中现有的电子邮件进行某种验证。

#3


0  

Search for other questions on * with the forgot-password tag. There are already several well written answers on good algorithms and techniques to use.

使用忘记密码标签搜索*上的其他问题。关于好的算法和使用的技术,已经有了几个很好的答案。

#1


3  

A user entering their username and reset code should log them into the site just as their username and password would. The difference is you then immediately force them to change their password. With this password reset method you're implicitly trusting that the user is the owner of the email account where the code was sent.

用户输入用户名和重置代码时,应该像登录用户名和密码一样登录网站。不同的是,你会立即强迫他们更改密码。使用这个密码重置方法,您可以隐式地信任用户是发送代码的电子邮件帐户的所有者。

Edit:

Ok, so I don't know the first thing about ASP.net.

好吧,我不知道关于ASP.net的第一件事。

However, I've handled this problem many times before. Here is a solution of mine in PHP:

然而,我以前已经处理过这个问题很多次了。下面是我的PHP解决方案:

<?php
class AuthController extends Zend_Controller_Action
{
    public function identifyAction()
    {
        if ($this->_request->isPost()) {
            $username = $this->_getParam('username');
            $password = $this->_getParam('password');

            if (empty($username) || empty($password)) {
                $this->_flashError('Username or password cannot be blank.');
            } else {
                $user = new User();
                $result = $user->login($username, $password);

                if ($result->isValid()) {
                    $user->fromArray((array) $this->_auth->getIdentity());

                    if ($this->_getParam('changepass') || $user->is_password_expired) {
                        $this->_redirect('auth/change-password');
                        return;
                    }
                    $this->_doRedirect($user);
                    return;
                } else {
                    $this->_doFailure($result->getIdentity());
                }
            }
        }
        $this->_redirect('/');
    }

    public function forgotPasswordAction()
    {
        if ($this->_request->isPost()) {
            // Pseudo-random uppercase 6 digit hex value
            $resetCode = strtoupper(substr(sha1(uniqid(rand(),true)),0,6));

            Doctrine_Query::create()
                ->update('dUser u')
                ->set('u.reset_code', '?', array($resetCode))
                ->where('u.username = ?', array($this->_getParam('username')))
                ->execute();

            $mail = new Zend_Mail();
            $mail->setBodyText($this->_resetEmailBody($this->_getParam('username'), $resetCode));
            $mail->setFrom('no-reply@example.com', 'Example');
            $mail->addTo($this->_getParam('username'));
            $mail->setSubject('Forgotten Password Request');
            $mail->send();


            $this->_flashNotice("Password reset request received.");
            $this->_flashNotice("An email with further instructions, including your <em>Reset Code</em>, has been sent to {$this->_getParam('username')}.");
            $this->_redirect("auth/reset-password/username/{$this->_getParam('username')}");
        }
    }

    public function resetPasswordAction()
    {
        $this->view->username = $this->_getParam('username');
        $this->view->reset_code = $this->_getParam('reset_code');

        if ($this->_request->isPost()) {
            $formData = $this->_request->getParams();
            if (empty($formData['username']) || empty($formData['reset_code'])) {
                $this->_flashError('Username or reset code cannot be blank.');
                $this->_redirect('auth/reset-password');
            } elseif ($formData['new_password'] !== $formData['confirm_password']) {
                $this->_flashError('Password and confirmation do not match.');
                $this->_redirect('auth/reset-password');
            } else {
                $user = new User();
                $result = $user->loginWithResetCode($formData['username'], $formData['reset_code']);

                if ($result->isValid()) {
                    $user->updatePassword($result->getIdentity(), $formData['new_password']);

                    $user->fromArray((array) $this->_auth->getIdentity());
                    $this->_setLegacySessionData($user);

                    $this->_flashNotice('Password updated successfully!');
                    $this->_doRedirect($user);
                } else {
                    $this->_doFailure($result->getIdentity());
                    $this->_redirect('auth/reset-password');
                }
            }
        }
    }

    protected function _doFailure($username)
    {
        $user = Query::create()
            ->from('User u')
            ->select('u.is_locked')
            ->where('u.username = ?', array($username))
            ->fetchOne();

        if ($user->is_locked) {
            $lockedMessage = Config::get('auth.lock_message');
            if (!$lockedMessage) {
                $lockedMessage = 'This account has been locked.';
            }
            $this->_flashError($lockedMessage);
        } else {
            $this->_flashError('Invalid username or password');
        }
    }
}

If you can follow this, it should give you a good idea of what to do. I'll try to summarize:

如果你能做到这一点,它会让你知道该怎么做。我将试着总结:

identifyAction

This is the regular "login" using username and password. It logs the user in and stores their identity in the session.

这是使用用户名和密码的常规“登录”。它记录用户并将其标识存储在会话中。

forgotPasswordAction

This presents the user with a form requesting their username. After entering their username a reset code is generated, stored in their entry in the user table, and they are emailed as well as redirected to the reset password page. This page is unauthenticated, the user is not logged in.

这将向用户提供一个请求用户名的表单。输入用户名后,将生成一个重置代码,存储在用户表的条目中,并通过电子邮件将其发送到重置密码页面。此页面未经身份验证,用户未登录。

resetPasswordAction

This is where the user is presented with the "resetPassword" form. They must provide their username and the reset code they received via email. This authenticates the user with the given username and reset code, just as if the reset code were a password. If the credentials are valid the user is then redirected to the changePassword action where they are permitted to change their password. The changePasswordAction (not shown) requires the user be authenticated (logged in) either via username/password or username/resetCode

在这里,用户将看到“resetPassword”表单。他们必须提供他们的用户名和他们通过电子邮件收到的重置代码。这将使用给定的用户名和重置代码对用户进行身份验证,就像重置代码是密码一样。如果凭证是有效的,那么用户将被重定向到changePassword操作,在那里他们可以修改密码。changePasswordAction(未显示)要求用户通过用户名/密码或用户名/resetCode进行身份验证(登录)

Hope this helps.

希望这个有帮助。

#2


0  

If your code that you're emailing is a GUID or some such ID, there is a statistically low chance that someone can guess that code. If you additionally had the link include a hashed version of their email or some other way of linking the code to the user, I think you'd be pretty well safe from malicious input.

如果你正在发送电子邮件的代码是GUID或类似的ID,那么从统计学上讲,人们猜测代码的可能性很小。如果你有另外的链接,包括一个哈希版本的他们的电子邮件或一些其他方式连接代码到用户,我认为你是相当安全的恶意输入。

I'd be more worried about people being spammed from step c/d, unless you're doing some sort of verification of the email existing currently in your database.

我更担心的是人们会被来自步骤c/d的垃圾邮件所困扰,除非您正在对数据库中现有的电子邮件进行某种验证。

#3


0  

Search for other questions on * with the forgot-password tag. There are already several well written answers on good algorithms and techniques to use.

使用忘记密码标签搜索*上的其他问题。关于好的算法和使用的技术,已经有了几个很好的答案。