在哪里可以找到在 ASP .NET MVC2 中实现密码恢复的 C# 示例代码
Posted
技术标签:
【中文标题】在哪里可以找到在 ASP .NET MVC2 中实现密码恢复的 C# 示例代码【英文标题】:Where to find C# sample code to implement password recovery in ASP .NET MVC2 【发布时间】:2011-12-09 05:40:59 【问题描述】:如何在MVC2应用中实现密码重置?
使用 ASP .NET 成员资格提供程序对密码进行哈希处理。未使用密码恢复问题。使用带有标准 AccountController 类的标准 ASP .NET MVC2 项目模板。
如果用户忘记密码,应将带有临时链接或新密码的电子邮件发送到用户电子邮件地址。
在哪里可以找到在 MVC 2 C# 中实现此功能的代码?
*** 包含两个答案,它们讨论了实现这一点的方法。没有示例代码。 我搜索了“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
【问题讨论】:
【参考方案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
【讨论】:
以上是关于在哪里可以找到在 ASP .NET MVC2 中实现密码恢复的 C# 示例代码的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET MVC2 AsyncController 可以访问 HttpContext.Current 吗?
Wordpress 类似于 ASP.NET MVC2/3 或 ASP.NET 4.0 中的动态永久链接
asp.net mvc2 可以有两个相同的action。但参数不一样.controller怎样调用