【问题标题】:Where to find C# sample code to implement password recovery in ASP .NET MVC2在哪里可以找到在 ASP .NET MVC2 中实现密码恢复的 C# 示例代码
【发布时间】:2011-12-09 05:40:59
【问题描述】:

如何在MVC2应用中实现密码重置?

使用 ASP .NET 成员资格提供程序对密码进行哈希处理。未使用密码恢复问题。使用带有标准 AccountController 类的标准 ASP .NET MVC2 项目模板。

如果用户忘记密码,应将带有临时链接或新密码的电子邮件发送到用户电子邮件地址。

在哪里可以找到在 MVC 2 C# 中实现此功能的代码?

stackoverflow 包含两个答案,它们讨论了实现这一点的方法。没有示例代码。 我搜索了“asp .net mvc 密码重置 c# 示例代码下载”,但没有找到示例代码。

我是 MVC 的新手。在哪里可以找到密码恢复的示例代码?这是 VS2010 生成的项目模板中缺少的。

更新

我在 Mono 2.10 中尝试过这段代码,但出现异常:

Mono 不支持 CspParameters

一行

        des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);

如何在 Mono 中运行它?

堆栈跟踪:

System.NotSupportedException: CspParameters not supported by Mono
at System.Security.Cryptography.PasswordDeriveBytes.CryptDeriveKey (string,string,int,byte[]) [0x0001b] in /usr/src/redhat/BUILD/mono-2.10.2/mcs/class/corlib/System.Security.Cryptography/PasswordDeriveBytes.cs:197
at store2.Helpers.Password.EncodeMessageWithPassword (string,string) <IL 0x00055, 0x000f3>
at store2.Helpers.AccountHelper.GetTokenForValidation (string) <IL 0x00033, 0x00089>
at MvcMusicStore.Controllers.AccountController.PasswordReminder (MvcMusicStore.Models.PasswordReminderModel) <IL 0x001ac, 0x00495>
at (wrapper dynamic-method) System.Runtime.CompilerServices.ExecutionScope.lambda_method (System.Runtime.CompilerServices.ExecutionScope,System.Web.Mvc.ControllerBase,object[]) <IL 0x00020, 0x0005b>
at System.Web.Mvc.ActionMethodDispatcher.Execute (System.Web.Mvc.ControllerBase,object[]) <IL 0x00008, 0x0001b>
at System.Web.Mvc.ReflectedActionDescriptor.Execute (System.Web.Mvc.ControllerContext,System.Collections.Generic.IDictionary`2<string, object>) <IL 0x00072, 0x00103>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod (System.Web.Mvc.ControllerContext,System.Web.Mvc.ActionDescriptor,System.Collections.Generic.IDictionary`2<string, object>) <IL 0x00003, 0x00019>
at System.Web.Mvc.ControllerActionInvoker/<>c__DisplayClassd.<InvokeActionMethodWithFilters>b__a () <IL 0x0002d, 0x00068>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter (System.Web.Mvc.IActionFilter,System.Web.Mvc.ActionExecutingContext,System.Func`1<System.Web.Mvc.ActionExecutedContext>) <IL 0x00031, 0x000b6>


--------------------------------------------------------------------------------
Version information: Mono Runtime Version: 2.10.2 (tarball Mon Apr 18 18:57:39 UTC 2011); ASP.NET Version: 2.0.50727.1433

【问题讨论】:

    标签: asp.net-mvc mono password-recovery


    【解决方案1】:

    这是我的方法。在 MVC 中,您将有一个名为 RetrievePassword 的操作,您将在其中询问用户的电子邮件地址并在帖子中传递它

        [HttpGet]
        public ActionResult RetrievePassword()
        {
            return View();
        }
    
        [HttpPost]
        public ActionResult RetrievePassword(PasswordRetrievalModel model)
        {
            if (ModelState.IsValid)
            {
                string username = Membership.GetUserNameByEmail(model.Email);
    
                if (!String.IsNullOrEmpty(username))
                {
                    // This is a helper function that sends an email with a token (an MD5).
                    NotificationsHelper.SendPasswordRetrieval(model.Email, this.ControllerContext);
                }
                else
                {
                    Trace.WriteLine(String.Format("*** WARNING:  A user tried to retrieve their password but the email address used '{0}' does not exist in the database.", model.Email));
                 }
    
    
                return RedirectToAction("Index", "Home");
            }
    
            return View(model);
        }
    

    将发送一封电子邮件,其中包含重定向到http://example.com/Account/Validate?email=xxxxxxxx&token=xxxxxxxx 的网址

    如果令牌对电子邮件有效,您可能会显示密码重置表单,以便他们选择新密码。

    所以你需要一个验证操作:

    [HttpGet]
        [CompressFilter]
        public ActionResult Validate(string email, string token)
        {
            bool isValid = false;
    
            if (AccountHelper.IsTokenValid(token, email))
            {
                string username = Membership.GetUserNameByEmail(email);
                if (!String.IsNullOrEmpty(username))
                {
                    // Get the user and approve it.
                    MembershipUser user = Membership.GetUser(username);
                    user.IsApproved = true;
                    Membership.UpdateUser(user);
    
                    isValid = true;
    
                    // Since it was a successful validation, authenticate the user.
                    FormsAuthentication.SetAuthCookie(username, false);
                }
                else
                {
                    isValid = false;
                }
            }
    
            return View(isValid);
        }
    

    以下是您在此代码中看到的一些帮助器:

    帐户助手

    /// <summary>
        /// Gets the token for invitation.
        /// </summary>
        /// <param name="email">The email.</param>
        /// <returns></returns>
        public static string GetTokenForInvitation(string email)
        {
            if (String.IsNullOrEmpty(email))
                throw new ArgumentException("The email cannot be null");
    
            string token = Password.EncodeMessageWithPassword(String.Format("{0}#{1}", email, DateTime.Now), SEED);
    
            return token;
        }
    
    
        /// <summary>
        /// Gets the email from token.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="email">The email.</param>
        /// <returns></returns>
        public static bool GetEmailFromToken(string token, out string email)
        {
            email = String.Empty;
    
    
            string message = Password.DecodeMessageWithPassword(token, SEED);
            string[] messageParts = message.Split('#');
    
            if (messageParts.Count() != 2)
            {
                return false;
                // the token was not generated correctly.
            }
            else
            {
                email = messageParts[0];
                return true;
            }
        }
    
    
    
        /// <summary>
        /// Helper function used to generate a token to be used in the message sent to users when registered the first time to confirm their email address.
        /// </summary>
        /// <param name="email">The email address to encode.</param>
        /// <returns>The token generated from the email address, timestamp, and SEED value.</returns>
        public static string GetTokenForValidation(string email)
        {
            if (String.IsNullOrEmpty(email))
                throw new ArgumentException("The email cannot be null");
    
            string token = Password.EncodeMessageWithPassword(String.Format("{0}#{1}", email, DateTime.Now), SEED);
    
            return token;
        }
    
    
        /// <summary>
        /// Validates whether a given token is valid for a determined email address.
        /// </summary>
        /// <param name="token">The token to validate.</param>
        /// <param name="email">The email address to use in the validation.</param>
        /// <returns><c>true</c> if the token is valid, <c>false</c> otherwise.</returns>
        public static bool IsTokenValid(string token, string email)
        {
            return IsTokenValid(token, email, DateTime.Now);
        }
    
    
        /// <summary>
        /// Core method to validate a token that also offers a timestamp for testing.  In production mode should always be DateTime.Now.
        /// </summary>
        /// <param name="token">The token to validate.</param>
        /// <param name="email">the email address to use in the validation.</param>
        /// <param name="timestamp">The timestamp representing the time in which the validation is performed.</param>
        /// <returns><c>true</c> if the token is valid, <c>false</c> otherwise.</returns>
        public static bool IsTokenValid(string token, string email, DateTime timestamp)
        {
            if (String.IsNullOrEmpty(token))
                throw new ArgumentException("The token cannot be null");
    
            try
            {
                string message = Password.DecodeMessageWithPassword(token, SEED);
                string[] messageParts = message.Split('#');
    
                if (messageParts.Count() != 2)
                {
                    return false;
                    // the token was not generated correctly.
                }
                else
                {
                    string messageEmail = messageParts[0];
                    string messageDate = messageParts[1];
    
                    // If the emails are the same and the date in which the token was created is no longer than 5 days, then it is valid. Otherwise, it is not. 
                    return (String.Compare(email, messageEmail, true) == 0 && timestamp.Subtract(DateTime.Parse(messageDate)).Days < 5);
                }
            }
            catch (Exception)
            {
                // could not decrypt the message. The token has been tampered with.
                return false;
            }
        }
    

    最后这里有一些代码来加密,解密一个令牌......

    我把它放在一个 Password 类中,该类旨在成为一个助手。

    /// 编辑: 删除了我之前引用的两个函数并显示了完整的辅助类。

    这是包含所有辅助函数的 Password 静态类。

    using System;
    using System.Text;
    using System.IO;
    using System.Security.Cryptography;
    using System.Data;
    using System.Resources;
    
    namespace MySolution.Common.Util
    {
        /// <summary>
        /// Implements some functions to support password manipulation or generation
        /// </summary>
        public class Password
        {
            /// <summary>
            /// Takes a string and generates a hash value of 16 bytes.
            /// </summary>
            /// <param name="str">The string to be hashed</param>
            /// <param name="passwordFormat">Selects the hashing algorithm used. Accepted values are "sha1" and "md5".</param>
            /// <returns>A hex string of the hashed password.</returns>
            public static string EncodeString(string str, string passwordFormat)
            {
                if (str == null)
                    return null;
    
                ASCIIEncoding AE = new ASCIIEncoding();
                byte[] result;
                switch (passwordFormat)
                {
                    case "sha1":                    
                        SHA1 sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
                        result = sha1.ComputeHash(AE.GetBytes(str));
                        break;
                    case "md5":
                        MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                        result = md5.ComputeHash(AE.GetBytes(str));
                        break;
                    default:
                        throw new ArgumentException("Invalid format value. Accepted values are 'sha1' and 'md5'.", "passwordFormat");
                }
    
                // Loop through each byte of the hashed data 
                // and format each one as a hexadecimal string.
                StringBuilder sb = new StringBuilder(16);
                for (int i = 0; i < result.Length; i++)
                {
                    sb.Append(result[i].ToString("x2"));
                }
    
    
                return sb.ToString();
            }
    
            /// <summary>
            /// Takes a string and generates a hash value of 16 bytes.  Uses "md5" by default.
            /// </summary>
            /// <param name="str">The string to be hashed</param>
            /// <returns>A hex string of the hashed password.</returns>
            public static string EncodeString(string str)
            {
                return EncodeString(str, "md5");
            }
    
    
    
            /// <summary>
            /// Takes a string and generates a hash value of 16 bytes.
            /// </summary>
            /// <param name="str">The string to be hashed</param>
            /// <param name="passwordFormat">Selects the hashing algorithm used. Accepted values are "sha1" and "md5".</param>
            /// <returns>A string of the hashed password.</returns>
            public static string EncodeBinary(byte[] buffer, string passwordFormat)
            {
                if (buffer == null)
                    return null;
    
                byte[] result;
                switch (passwordFormat)
                {
                    case "sha1":
                        SHA1 sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
                        result = sha1.ComputeHash(buffer);
                        break;
                    case "md5":
                        MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                        result = md5.ComputeHash(buffer);
                        break;
                    default:
                        throw new ArgumentException("Invalid format value. Accepted values are 'sha1' and 'md5'.", "passwordFormat");
                }
    
    
                // Loop through each byte of the hashed data 
                // and format each one as a hexadecimal string.
                StringBuilder sb = new StringBuilder(16);
                for (int i = 0; i < result.Length; i++)
                {
                    sb.Append(result[i].ToString("x2"));
                }
    
    
                return sb.ToString();
            }
    
            /// <summary>
            /// Encodes the buffer using the default cryptographic provider.
            /// </summary>
            /// <param name="buffer">The buffer.</param>
            /// <returns></returns>
            public static string EncodeBinary(byte[] buffer)
            {
                return EncodeBinary(buffer, "md5");
            }
    
    
    
    
    
            /// <summary>
            /// Creates a random alphanumeric password.
            /// </summary>
            /// <returns>A default length character string with the new password.</returns>
            /// <remarks>The default length of the password is eight (8) characters.</remarks>
            public static string CreateRandomPassword()
            {
                //Default length is 8 characters
                return CreateRandomPassword(8);
            }
    
            /// <summary>
            /// Creates a random alphanumeric password on dimension (Length).
            /// </summary>
            /// <param name="Length">The number of characters in the password</param>
            /// <returns>The generated password</returns>
            public static string CreateRandomPassword(int Length)
            {
                Random rnd = new Random(Convert.ToInt32(DateTime.Now.Millisecond));  //Creates the seed from the time
                string Password="";
                while (Password.Length < Length ) 
                {
                    char newChar = Convert.ToChar((int)((122 - 48 + 1) * rnd.NextDouble() + 48));
                    if ((((int) newChar) >= ((int) 'A')) & (((int) newChar) <= ((int) 'Z')) | (((int) newChar) >= ((int) 'a')) & (((int) newChar) <= ((int) 'z')) | (((int) newChar) >= ((int) '0')) & (((int) newChar) <= ((int) '9')))
                        Password += newChar;
                }
                return Password;
            }
    
            /// <summary>
            /// Takes a text message and encrypts it using a password as a key.
            /// </summary>
            /// <param name="plainMessage">A text to encrypt.</param>
            /// <param name="password">The password to encrypt the message with.</param>
            /// <returns>Encrypted string.</returns>
            /// <remarks>This method uses TripleDES symmmectric encryption.</remarks>
            public static string EncodeMessageWithPassword(string plainMessage, string password)
            {
                if (plainMessage == null)
                    throw new ArgumentNullException("encryptedMessage", "The message cannot be null");
    
                TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
                des.IV = new byte[8];
    
                //Creates the key based on the password and stores it in a byte array.
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[0]);
                des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
    
                MemoryStream ms = new MemoryStream(plainMessage.Length * 2);
                CryptoStream encStream = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
                byte[] plainBytes = Encoding.UTF8.GetBytes(plainMessage);
                encStream.Write(plainBytes, 0, plainBytes.Length);
                encStream.FlushFinalBlock();
                byte[] encryptedBytes = new byte[ms.Length];
                ms.Position = 0;
                ms.Read(encryptedBytes, 0, (int)ms.Length);
                encStream.Close();
    
                return Convert.ToBase64String(encryptedBytes);
            }
    
            /// <summary>
            /// Takes an encrypted message using TripleDES and a password as a key and converts it to the original text message.
            /// </summary>
            /// <param name="encryptedMessage">The encrypted message to decode.</param>
            /// <param name="password">The password to decode the message.</param>
            /// <returns>The Decrypted message</returns>
            /// <remarks>This method uses TripleDES symmmectric encryption.</remarks>
            public static string DecodeMessageWithPassword(string encryptedMessage, string password)
            {
                if (encryptedMessage == null)
                    throw new ArgumentNullException("encryptedMessage", "The encrypted message cannot be null");
    
                TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
                des.IV = new byte[8];
    
                //Creates the key based on the password and stores it in a byte array.
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[0]);
                des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
    
                //This line protects the + signs that get replaced by spaces when the parameter is not urlencoded when sent.
                encryptedMessage = encryptedMessage.Replace(" ", "+");
                MemoryStream ms = new MemoryStream(encryptedMessage.Length * 2);
                CryptoStream decStream = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
    
                byte[] plainBytes; 
                try 
                {
                    byte[] encBytes = Convert.FromBase64String(Convert.ToString(encryptedMessage));
                    decStream.Write(encBytes, 0, encBytes.Length);
                    decStream.FlushFinalBlock();                
                    plainBytes = new byte[ms.Length];
                    ms.Position = 0;                
                    ms.Read(plainBytes, 0, (int)ms.Length);
                    decStream.Close();
                }
                catch(CryptographicException e)
                {
                    throw new ApplicationException("Cannot decrypt message.  Possibly, the password is wrong", e);
                }
    
                return Encoding.UTF8.GetString(plainBytes);
            }
        }
    }
    

    【讨论】:

    • 谢谢。优秀。我将其标记为答案。验证后,应强制用户更改密码。不使用问答。 MembershipUser 类方法ChangePassword(string oldPassword, string newPassword) 需要旧密码,但用户忘记了这个。验证后如何更改密码?应该使用使用user.ResetPassword() 创建的临时密码还是有更好的方法。你能提供这个代码吗?
    • 完全正确。您需要重置密码,您将获得一个临时密码。使用它来更改密码,以便您可以分配用户提供的密码。这只是对重置密码的额外调用。
    • 我想它增加了一点安全性...我不喜欢通过电子邮件发送密码的想法,所以将 url 发回给用户以强制他们更改密码是我的首选选项。我想这是可选的......
    • 添加了整个 Password 类,其中包含与消息加密或解密相关的所有帮助函数。
    • @keyCrumbs 这是 NotificationsHelper。自从我编写此代码以来,它可能已经改变,并且可能有一些不相关的代码,但我希望它有所帮助。 gist.github.com/agarcian/5828445
    【解决方案2】:

    在用户表中设置重置密码 GUID。您也可以使用过期时间。如果用户尝试重置密码,请使用新的 GUID 和过期日期时间更新该字段。

    发送一个链接,其中包含使用 GUID 重置密码的链接。

    可以为此创建一个这样的示例函数

    GUID res = objPasswordResetService.resetPassword(Convert.ToInt64(objUserViewModel.UserID), restpasswordGuid, resetPasswordExpiryDateTime);
    

    res 中的值可以是 DB 中更新的 GUID。发送带有此 GUID 的链接。您也可以检查到期时间。这只是一个想法

    【讨论】:

      【解决方案3】:

      我的博客中有一个如何在标准 ASP.NET MVC 应用程序中实现密码恢复的示例。

      这篇博文假设您已经有登录过程(数据库和所有),并且您只需要连接密码恢复过程。

      http://hectorcorrea.com/Blog/Password-Recovery-in-an-ASP.NET-MVC-Project

      【讨论】:

      • 赫克托谢谢。您的文章涵盖了使用密码提醒问题。我在问题中表示不使用密码提醒问题。因此,答案可能与使用您的精彩文章中描述的密码提醒有很大不同。它可能涉及通过电子邮件发送临时 url 或自动生成的新密码。
      【解决方案4】:

      在MVC2应用中实现密码重置的答案

      public string ResetPassword(string userName)
          {
              MembershipUser user = _provider.GetUser(userName, false);
      
              if (user.IsLockedOut)
                  user.UnlockUser();
      
              user.Comment = null;
              _provider.UpdateUser(user);
      
              string newPassword = user.ResetPassword();
              string friendlyPassword = GenerateNewPassword();
              _provider.ChangePassword(userName, newPassword, friendlyPassword);
              return friendlyPassword;
          }
      
      
      private string GenerateNewPassword()
          {
              string strPwdchar = "abcdefghijklmnopqrstuvwxyz0123456789#@$ABCDEFGHIJKLMNOPQRSTUVWXYZ";
              string strPwd = "";
              Random rnd = new Random();
              for (int i = 0; i <= 8; i++)
              {
                  int iRandom = rnd.Next(0, strPwdchar.Length - 1);
                  strPwd += strPwdchar.Substring(iRandom, 1);
              }
              return strPwd;
          }
      

      【讨论】:

      • 这太糟糕了,让我的眼睛流血
      【解决方案5】:

      这里是俄文版password recovery

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-18
        • 2011-01-17
        • 1970-01-01
        相关资源
        最近更新 更多