C# 如何实现密码重置链接

C# 如何实现密码重置链接,c#,sql-server,visual-studio-2010,password-recovery,C#,Sql Server,Visual Studio 2010,Password Recovery,我目前有一个系统,如果用户忘记了密码,他们可以通过点击忘记密码链接来重置密码。他们将被带到一个页面,在该页面中输入用户名/电子邮件,然后向用户发送一封电子邮件。我想知道如何在电子邮件中实现密码重置链接,因此,一旦用户单击链接,他/她将被带到一个允许他们重置密码的页面 这是我控制器中的代码 public ActionResult ForgotPassword() { //verify user id string UserId = R

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

这是我控制器中的代码

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

创建一个具有如下结构的表

create table ResetTickets(
    username varchar(200),
    tokenHash varbinary(16),
    expirationDate datetime,
    tokenUsed bit)
然后,在您的代码中,当用户单击重置密码按钮时,您将生成一个随机令牌,然后在该表中放入一个条目,该条目的哈希值为
令牌
,过期日期为
DATEADD(day,1,GETDATE())
并将该令牌值附加到您通过电子邮件发送给用户的url上,以用于密码重置页面

www.example.com/passwordReset?username=Karan&token=ZB71yObR
在密码重置页面上,您获取传入的用户名和令牌,再次散列令牌,然后将其与
ResetTickets
表进行比较,如果过期日期尚未过去,且令牌尚未使用,则将用户带到允许其输入新密码的页面

需要注意的事项

  • 确保令牌过期,不要让两年前的电子邮件重置密码
  • 确保将令牌标记为已使用,不要让计算机的其他用户使用浏览器的历史记录重置其他用户的密码
  • 确保安全生成随机令牌。不要使用
    Rand
    并使用它生成令牌,两个同时重置的用户将获得相同的令牌(我可以同时重置我的密码和你的密码,然后使用我的令牌重置你的帐户)。相反,创建一个static并使用
    GetBytes
    方法,因为该类是线程安全的,所以您不需要担心使用同一实例的两个线程
  • 如果我输入了用户ID
    ,请确保。在当前代码中;删除dbo。[用户]--
    它将删除数据库中的所有用户。有关如何修复的更多信息,请参阅链接的SO帖子
  • 确保对令牌进行散列,您的
    密码重置
    页面只接受未删除的版本,并且您从不将未删除的版本存储在任何地方
    (包括发送给用户的邮件日志)。这可以防止具有数据库读访问权限的攻击者为其他用户生成令牌,读取电子邮件中发送的值,然后自己发送相同的值(并且可能获得管理员用户的访问权限,该管理员用户可以执行比读取值更多的操作)

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


    生成一个长随机字符串,并将其与用户id(以及有效期)一起存储,导航到reset.aspx?id=longstring,并使用它来识别用户并显示重置页面,成功后删除/使字符串无效。您需要参数化所有查询。这里的第一个查询对sql注入非常开放。另外,您确实应该只指定所需的列,而不是使用*。还有一件事,只存储令牌的散列,即具有db读取权限的攻击者(SQL注入)否则可以重置他想要的任何帐户。@ScottChamberlain如果数据库中只存储哈希,那么如何将来自电子邮件的随机令牌与生成的原始令牌进行比较?我这样问是因为假设“哈希值不可能反向哈希”@Athul您生成随机令牌,然后通过电子邮件发送该令牌。散列令牌并将其保存在数据库中。单击链接时,使用与存储到数据库时相同的哈希方法对传递到重置链接的令牌进行哈希。然后检查存储的哈希是否与刚才生成的哈希匹配。这与您检查密码是否正确的过程相同,您从不比较密码,您只检查两个密码哈希值是否相同。@ScottChamberlain如果我使用JWT令牌怎么办?你认为有必要把它也散列出来吗?