Как реализовать ссылку для сброса пароля

В настоящее время у меня есть система, в которой, если пользователь забыл свой пароль, он может сбросить его, щелкнув ссылку для забытого пароля. Они будут перенаправлены на страницу, где они введут свое имя пользователя / адрес электронной почты, а затем пользователю будет отправлено электронное письмо. Я хотел знать, как я могу реализовать ссылку для сброса пароля в электронном письме, поэтому, как только пользователь нажмет на ссылку /она попадает на страницу, которая позволит им сбросить свой пароль.

Это код в моем контроллере

public ActionResult ForgotPassword()
        {
           //verify user id

            string UserId = Request.Params ["txtUserName"];
            string msg = "";
            if (UserId == null) 
            {
                msg = "You Have Entered An Invalid UserId - Try Again";
                ViewData["ForgotPassword"] = msg;
                return View("ForgotPassword");
            }

            SqlConnection lsql = null;
            lsql = DBFactory.GetInstance().getMyConnection();

            String sqlstring = "SELECT * from dbo.[USERS] where USERID = '" + UserId.ToString() + "'";
            SqlCommand myCommand = new SqlCommand(sqlstring, lsql);
            lsql.Open();
            Boolean validUser;         
            using (SqlDataReader myReader = myCommand.ExecuteReader())
            {

                validUser = false;
                while (myReader.Read())
                {
                    validUser = true;

                }
                myReader.Close();
            }
            myCommand.Dispose();  


            if (!validUser) 
                  {
                msg = "You Have Entered An Invalid UserId - Try Again";
                ViewData["ForgotPassword"] = msg;
                lsql.Close();
                return View("ForgotPassword");
            }

            //run store procedure


            using (lsql)
            {
                SqlCommand cmd = new SqlCommand("Stock_Check_Test.dbo.RESET_PASSWORD", lsql);
                cmd.CommandType = CommandType.StoredProcedure;

                SqlParameter paramUsername = new SqlParameter("@var1", UserId);

                cmd.Parameters.Add(paramUsername);


                SqlDataReader rdr = cmd.ExecuteReader();
                while (rdr.Read())
                {
                    if (Convert.ToInt32(rdr["RC"]) == 99)
                    {
                        msg = "Unable to update password at this time";
                        ViewData["ForgotPassword"] = msg;
                        lsql.Close();
                        return View("ForgotPassword");  

                    }
                }
            }


            msg = "new password sent";
            ViewData["ForgotPassword"] = msg;
            lsql.Close();
            return View("ForgotPassword");
        }

Это моя текущая хранимая процедура, которая отправляет пользователю электронное письмо.

ALTER PROCEDURE [dbo].[A_SEND_MAIL]
    @var1 varchar (200), -- userid
    @var2 varchar (200) -- email address
AS
BEGIN
declare @bodytext varchar(200);
set @bodytext = 'Password Reset for user: ' +@var1 + ' @' + cast (getDate() as varchar) + ' ' ;
EXEC msdb.dbo.sp_send_dbmail 
@profile_name='Test',
@recipients=@var2,
@subject='Password Reset',
@body=@bodytext
END 

GO

person Karan Ramchandani    schedule 12.03.2015    source источник
comment
Вы создаете длинную случайную строку и сохраняете ее вместе с идентификатором пользователя (и датой истечения срока действия), переходите к reset.aspx?id=longstring и используете ее для идентификации пользователя и отображения страницы сброса, удаляя/аннулируя строку в случае успеха.   -  person Alex K.    schedule 12.03.2015
comment
Вам нужно параметризовать ВСЕ ваши запросы. Первый запрос здесь широко открыт для SQL-инъекций. Кроме того, вам действительно следует указывать только те столбцы, которые вам нужны, вместо использования *.   -  person Sean Lange    schedule 12.03.2015


Ответы (2)


Создайте таблицу со структурой вида

create table ResetTickets(
    username varchar(200),
    tokenHash varbinary(16),
    expirationDate datetime,
    tokenUsed bit)

Затем в вашем коде, когда пользователь нажимает кнопку сброса пароля, вы создаете случайный токен, затем помещаете запись в эту таблицу с хешированным значением этого token и датой истечения срока действия, например DATEADD(day, 1, GETDATE()), и добавляете это значение токена к URL-адресу, который вы отправляете по электронной почте. пользователю для страницы сброса пароля.

www.example.com/passwordReset?username=Karan&token=ZB71yObR

На странице сброса пароля вы берете переданное имя пользователя и токен, снова хешируете токен, затем сравниваете его с таблицей ResetTickets, и, если срок действия еще не прошел, а токен еще не использовался, затем переводите пользователя на страницу что позволяет им ввести новый пароль.

На что следует обратить внимание:

  1. Убедитесь, что срок действия токена истек, не позволяйте электронному письму двухлетней давности сбросить пароль.
  2. Обязательно пометьте токен как используемый, не позволяйте другим пользователям компьютера использовать историю браузера для сброса паролей других пользователей.
  3. Убедитесь, что вы сгенерировали случайный токен безопасно. Не используйте Rand и используйте его для создания токена, два пользователя, которые сбросятся в одно и то же время, получат один и тот же токен (я могу сбросить свой пароль и ваш пароль одновременно, а затем использовать свой токен для сброса вашей учетной записи). Вместо этого создайте статический RNGCryptoServiceProvider< /a> и используйте из него метод GetBytes, класс является потокобезопасным, поэтому вам не нужно беспокоиться о двух потоках, использующих один и тот же экземпляр.
  4. Обязательно параметризируйте свои запросы. В текущем коде если я введу идентификатор пользователя '; delete dbo.[USERS] --, он удалит всех пользователей в вашей базе данных. См. связанный пост SO для получения дополнительной информации о том, как это исправить.
  5. Убедитесь, что вы хешируете токен, ваша passwordReset страница принимает только нехешированную версию, и вы никогда не храните нехешированную версию где-либо (включая журналы электронной почты исходящих сообщений пользователям). Это не позволяет злоумышленнику, имеющему доступ на чтение к базе данных, создать токен для другого пользователя, прочитать значение, отправленное в электронном письме, а затем отправить то же самое значение самому (и, возможно, получить доступ к пользователю-администратору, который может делать больше вещей). чем просто читать значения).
person Scott Chamberlain    schedule 12.03.2015
comment
Еще одна вещь: храните только хэш токена, злоумышленник с доступом для чтения к базе данных (SQL-инъекция) может в противном случае сбросить любую учетную запись, которую он пожелает. - person martinstoeckli; 12.03.2015
comment
@ScottChamberlain, если в БД хранится только хэш, как можно сравнить случайный токен, полученный из электронной почты, с исходным сгенерированным токеном? Я спрашиваю об этом в предположении, что хешированные значения невозможно изменить хэш - person None; 07.11.2016
comment
@Athul Вы генерируете случайный токен, отправляете этот токен по электронной почте. Хэшируем токен и сохраняем его в БД. При нажатии на ссылку вы хешируете токен, который был передан по ссылке сброса, используя тот же метод хэширования, что и при сохранении его в базе данных. Затем вы проверяете, соответствует ли сохраненный хэш только что сгенерированному хешу. Это тот же процесс, который вы делаете, когда проверяете правильность пароля, вы никогда не сравниваете пароли, вы только проверяете, имеют ли два хэша паролей одно и то же значение. - person Scott Chamberlain; 07.11.2016
comment
@ScottChamberlain, а если вместо этого я использую токен JWT? как вы думаете, нужно ли его также хешировать? - person DanielV; 05.08.2017

вот 2 альтернативы, использующие HMAC или JWT (которые, я думаю, обеспечивают лучшие, более безопасные URL-адреса электронной почты)

person Yarix    schedule 17.05.2020