【问题标题】:How do I implement a password Reset Link如何实现密码重置链接
【发布时间】:2015-05-14 17:33:17
【问题描述】:

我目前有一个系统,如果用户忘记了密码,他们可以通过单击忘记密码链接来重置密码。他们将被带到他们输入用户名/电子邮件的页面,然后将向用户发送一封电子邮件,我想知道如何在电子邮件中实现密码重置链接,因此一旦用户单击该链接,他/她被带到一个允许他们重置密码的页面。

这是我的控制器中的代码

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

【问题讨论】:

  • 您生成一个长随机字符串并将其与用户 ID(和到期日期)一起存储,导航到 reset.aspx?id=longstring 并使用它来识别用户并显示重置页面,删除/成功时使字符串无效。
  • 您需要参数化所有查询。这里的第一个查询对 sql 注入是开放的。此外,你真的应该只指定你需要的列,而不是使用 *.

标签: c# sql-server visual-studio-2010 password-recovery


【解决方案1】:

创建一个具有类似结构的表

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来生成token,两个同时重置的​​用户会得到相同的token(我可以同时重置我的密码和你的密码,然后用我的token重置你的账户) .而是创建一个静态 RNGCryptoServiceProvider 并从中使用 GetBytes 方法,该类是线程安全的,因此您无需担心两个线程使用同一个实例。
  4. 一定要parameterize your queries在您当前的代码中,如果我输入用户ID '; delete dbo.[USERS] --,它将删除您数据库中的所有用户。有关如何修复它的更多信息,请参阅链接的 SO 帖子。
  5. 确保您对令牌进行哈希处理,您的passwordReset 页面只接受未经哈希处理的版本,并且您绝不会将未经哈希处理的版本存储在任何地方(包括发送给用户的邮件的电子邮件日志)。这可以防止对数据库具有读取权限的攻击者为其他用户创建令牌,读取电子邮件中发送的值,然后自己发送相同的值(并可能访问可以做更多事情的管理员用户不仅仅是读取值)。

【讨论】:

  • 还有一点,只存储令牌的 hash,对数据库具有读取访问权限(SQL 注入)的攻击者可能会重置他希望的任何帐户。跨度>
  • @ScottChamberlain 如果只有哈希存储在数据库中,如何将来自电子邮件的随机令牌与生成的原始令牌进行比较?我在假设“散列值不可能反转散列”时问这个问题
  • @Athul 您生成随机令牌,通过电子邮件发送该令牌。散列令牌并将其保存在数据库中。单击链接时,您使用与将其存储到数据库时相同的哈希方法对传递到重置链接的令牌进行哈希处理。然后您检查存储的哈希是否与您刚刚生成的哈希匹配。这与您检查密码是否正确时执行的过程相同,您从不比较密码,您只检查密码的两个哈希值是否相同。
  • @ScottChamberlain 如果我改用 JWT 令牌会怎样?你认为有必要对它进行哈希处理吗?
  • 我知道这是旧的但是.. 真的有必要将用户传递给重置端点吗?令牌应该不够吗?
【解决方案2】:

这里有 2 个使用 HMAC 或 JWT 的替代方案(我认为它们可以提供更好、更安全的电子邮件 URL)

【讨论】:

    猜你喜欢
    • 2012-09-16
    • 2013-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-26
    • 2021-01-27
    • 2016-10-08
    • 2010-11-13
    相关资源
    最近更新 更多