ASP.NET Identity重置密码


95

如何在新的ASP.NET Identity系统中获取用户密码?或者如何在不知道当前密码的情况下重置密码(用户忘记了密码)?

Answers:


102

在当前版本中

假设您已处理了有关重置忘记密码的请求的验证,请使用以下代码作为示例代码步骤。

ApplicationDbContext =new ApplicationDbContext()
String userId = "<YourLogicAssignsRequestedUserId>";
String newPassword = "<PasswordAsTypedByUser>";
ApplicationUser cUser = UserManager.FindById(userId);
String hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>();            
store.SetPasswordHashAsync(cUser, hashedNewPassword);

在AspNet每晚构建中

该框架已更新为可与令牌一起使用,以处理诸如ForgetPassword之类的请求。发行后,将需要简单的代码指导。

更新:

此更新只是为了提供更清晰的步骤。

ApplicationDbContext context = new ApplicationDbContext();
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(context);
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(store);
String userId = User.Identity.GetUserId();//"<YourLogicAssignsRequestedUserId>";
String newPassword = "test@123"; //"<PasswordAsTypedByUser>";
String hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);                    
ApplicationUser cUser = await store.FindByIdAsync(userId);
await store.SetPasswordHashAsync(cUser, hashedNewPassword);
await store.UpdateAsync(cUser);

您知道1.1版何时发布吗?
graycrow13年

它仍然处于alpha状态,并且1.0刚刚发布。因此,假设几个月。myget.org/gallery/aspnetwebstacknightnight
jd4u

11
奇怪的是,store.SetPasswordHashAsync(cUser,hashedNewPassword)方法调用对我不起作用,相反,我必须手动设置cUser.PasswordHash = hashedNewPassword,然后调用UserManager.UpdateAsync(user);。
安迪·梅哈利克

1
仅当用户检索上下文和商店上下文不同时,代码才能工作。该代码只是示例步骤,并不准确。很快就会更新答案,以免其他人遇到此问题。
jd4u

1
框架1不提供。但是Framework 2-alpha确实没有几个功能可以为处理密码重置请求提供简单的过程。aspnetidentity.codeplex.com
jd4u 2014年

138

或者如何在不知道当前密码的情况下重置密码(用户忘记了密码)?

如果要使用UserManager更改密码,但又不想提供用户的当前密码,则可以生成一个密码重置令牌,然后立即使用它。

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);

8
到目前为止,这是设置新密码的最佳和最干净的方法。可接受答案的问题在于,它通过直接访问密码散列器来绕过密码复杂性验证。
克里斯

6
仅供参考,您可能会收到错误消息“未注册IUserTokenProvider。” 如果您使用上面的逻辑。请参阅此stackoverflow.com/questions/22629936/…
Prasad Kanaparthi

1
我想这仅适用于版本2中的Microsoft.AspNet.Identity。您无法在版本1中找到GeneratePasswordResetTokenAsync方法
。– romanoza

谢谢您的回答。对我来说,它就像是一种魅力。
Thomas.Benz

4
如果您收到无效令牌,请确保SecurityStamp您的用户的不为null。从其他数据库迁移的用户,或者不是通过UserManager.CreateAsync()方法创建的用户,可能会发生这种情况。
Alisson

70

不推荐使用

这是原始答案。它确实有效,但是有问题。如果AddPassword失败怎么办?用户没有密码。

原始答案:我们可以使用三行代码:

UserManager<IdentityUser> userManager = 
    new UserManager<IdentityUser>(new UserStore<IdentityUser>());

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

另请参阅:http : //msdn.microsoft.com/en-us/library/dn457095(v=vs.111).aspx

现在推荐

最好使用EdwardBrey提出的答案,然后再使用DanielWright的代码示例进行详细说明


1
感谢上帝,我想我必须创建一个新的用户商店,直到我看到了!
路加福音

有什么办法可以直接在SQL中执行此操作吗?我很乐意将我的DBA传递给需要时调用的存储过程,而不是可执行文件。
Mark Richman

@MarkRichman这是一个新问题。但是,您可以做的一件事是检查在SQL Server上运行的生成的T-SQL。
Shaun Luttin 2015年

3
小心点,每当AddPassword失败(即密码复杂性不足)时,用户都将没有密码。
克里斯(Chris)

1
丹尼尔·赖特(Daniel Wright)提出了一种最干净的方法,而无需绕开任何业务规则(因为直接访问密码哈希器时,没有密码复杂性验证)。
克里斯

29

在您的UserManager第一次调用GeneratePasswordResetTokenAsync。一旦用户验证了自己的身份(例如,通过在电子邮件中接收令牌),请将令牌传递给ResetPasswordAsync


1
试图弄清楚为什么ResetPasswordAsync需要用户ID,以及在用户使用令牌显示时从用户那里获取它的合理方法。GeneratePasswordReset使用的令牌超过150个字符...似乎足以以密码方式存储用户ID,因此我不必自己实现。:(
pettys

我假设它正在询问用户ID,以便可以针对该用户ID将重置令牌输入到身份数据库中。如果不这样做,框架将如何知道令牌是否有效。您应该能够使用User.Identity.GetUserId()或类似方法拉出用户ID。
Ryan Buddicom 2015年

1
在API方面,要求用户ID是一个愚蠢的选择,当调用ResetPassword(async)时,令牌已在数据库中,并且仅应根据输入对其进行验证就足够了。
菲利普(Filip)

@Filip,ResetPasswordAsync获取用户ID 的优点是身份提供者仅需要索引用户ID,而无需标记。如果有很多用户,这将使其更好地扩展。
爱德华·布雷

1
@Edward Brey好吧,您如何获取重置呼叫的用户ID?
Filip

2
string message = null;
//reset the password
var result = await IdentityManager.Passwords.ResetPasswordAsync(model.Token, model.Password);
if (result.Success)
{
    message = "The password has been reset.";
    return RedirectToAction("PasswordResetCompleted", new { message = message });
}
else
{
    AddErrors(result);
}

此代码段摘自github上的AspNetIdentitySample项目


2

在中创建方法 UserManager<TUser, TKey>

public Task<IdentityResult> ChangePassword(int userId, string newPassword)
{
     var user = Users.FirstOrDefault(u => u.Id == userId);
     if (user == null)
          return new Task<IdentityResult>(() => IdentityResult.Failed());

     var store = Store as IUserPasswordStore<User, int>;
     return base.UpdatePassword(store, user, newPassword);
}

1

如果要重设密码,建议通过向注册用户的电子邮件中发送密码重设令牌来重设密码,并要求用户提供新密码。如果已经通过Identity框架使用默认配置settins创建了易于使用的.NET库。您可以在博客链接中找到详细信息,并在github上找到源代码


1

我认为Microsoft ASP.NET Identity指南是一个好的开始。

https://docs.microsoft.com/zh-cn/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity

注意:

如果您不使用AccountController并且不想重置密码,请使用Request.GetOwinContext().GetUserManager<ApplicationUserManager>();。如果您没有相同的OwinContext,则需要DataProtectorTokenProviderOwinContext使用的一样创建一个新的。默认情况下,查看App_Start -> IdentityConfig.cs。应该看起来像new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));

可以这样创建:

没有Owin:

[HttpGet]
[AllowAnonymous]
[Route("testReset")]
public IHttpActionResult TestReset()
{
    var db = new ApplicationDbContext();
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
    var provider = new DpapiDataProtectionProvider("SampleAppName");
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
        provider.Create("SampleTokenName"));

    var email = "test@test.com";

    var user = new ApplicationUser() { UserName = email, Email = email };

    var identityUser = manager.FindByEmail(email);

    if (identityUser == null)
    {
        manager.Create(user);
        identityUser = manager.FindByEmail(email);
    }

    var token = manager.GeneratePasswordResetToken(identityUser.Id);
    return Ok(HttpUtility.UrlEncode(token));
}

[HttpGet]
[AllowAnonymous]
[Route("testReset")]
public IHttpActionResult TestReset(string token)
{
    var db = new ApplicationDbContext();
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
    var provider = new DpapiDataProtectionProvider("SampleAppName");
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
        provider.Create("SampleTokenName"));
    var email = "test@test.com";
    var identityUser = manager.FindByEmail(email);
    var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
    var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
    return Ok(result);
}

使用Owin:

[HttpGet]
[AllowAnonymous]
[Route("testResetWithOwin")]
public IHttpActionResult TestResetWithOwin()
{
    var manager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();

    var email = "test@test.com";

    var user = new ApplicationUser() { UserName = email, Email = email };

    var identityUser = manager.FindByEmail(email);

    if (identityUser == null)
    {
        manager.Create(user);
        identityUser = manager.FindByEmail(email);
    }

    var token = manager.GeneratePasswordResetToken(identityUser.Id);
    return Ok(HttpUtility.UrlEncode(token));
}

[HttpGet]
[AllowAnonymous]
[Route("testResetWithOwin")]
public IHttpActionResult TestResetWithOwin(string token)
{
    var manager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();

    var email = "test@test.com";
    var identityUser = manager.FindByEmail(email);
    var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
    var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
    return Ok(result);
}

DpapiDataProtectionProviderDataProtectorTokenProvider需要与密码重置工作相同的名称创建。使用Owin创建密码重置令牌,然后DpapiDataProtectionProvider使用其他名称创建新令牌将不起作用。

我用于ASP.NET标识的代码:

Web.Config:

<add key="AllowedHosts" value="example.com,example2.com" />

AccountController.cs:

[Route("RequestResetPasswordToken/{email}/")]
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var user = await UserManager.FindByEmailAsync(email);
    if (user == null)
    {
        Logger.Warn("Password reset token requested for non existing email");
        // Don't reveal that the user does not exist
        return NoContent();
    }

    //Prevent Host Header Attack -> Password Reset Poisoning. 
    //If the IIS has a binding to accept connections on 80/443 the host parameter can be changed.
    //See https://security.stackexchange.com/a/170759/67046
    if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) {
            Logger.Warn($"Non allowed host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}");
            return BadRequest();
    }

    Logger.Info("Creating password reset token for user id {0}", user.Id);

    var host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}";
    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
    var callbackUrl = $"{host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}";

    var subject = "Client - Password reset.";
    var body = "<html><body>" +
               "<h2>Password reset</h2>" +
               $"<p>Hi {user.FullName}, <a href=\"{callbackUrl}\"> please click this link to reset your password </a></p>" +
               "</body></html>";

    var message = new IdentityMessage
    {
        Body = body,
        Destination = user.Email,
        Subject = subject
    };

    await UserManager.EmailService.SendAsync(message);

    return NoContent();
}

[HttpPost]
[Route("ResetPassword/")]
[AllowAnonymous]
public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model)
{
    if (!ModelState.IsValid)
        return NoContent();

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        Logger.Warn("Reset password request for non existing email");
        return NoContent();
    }            

    if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
    {
        Logger.Warn("Reset password requested with wrong token");
        return NoContent();
    }

    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);

    if (result.Succeeded)
    {
        Logger.Info("Creating password reset token for user id {0}", user.Id);

        const string subject = "Client - Password reset success.";
        var body = "<html><body>" +
                   "<h1>Your password for Client was reset</h1>" +
                   $"<p>Hi {user.FullName}!</p>" +
                   "<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" +
                   "</body></html>";

        var message = new IdentityMessage
        {
            Body = body,
            Destination = user.Email,
            Subject = subject
        };

        await UserManager.EmailService.SendAsync(message);
    }

    return NoContent();
}

public class ResetPasswordRequestModel
{
    [Required]
    [Display(Name = "Token")]
    public string Token { get; set; }

    [Required]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

1

我做了一些调查,发现对我有用的解决方案是本文中建立的一些解决方案的组合。

我基本上是在编译此解决方案,并发布了适用于我的方法。就我而言,我不想使用.net核心中的任何令牌。

public async Task ResetPassword(string userId, string password)
{
    var user = await _userManager.FindByIdAsync(userId);
    var hashPassword= _userManager.PasswordHasher.HashPassword(user, password);
    user.PasswordHash = passwordHash;
    await _userManager.UpdateAsync(user);

}

1

在用于Web API的Asp.Net Core Identity中重置密码的最佳方法。

注意 * :创建Error()和Result()供内部使用。您可以返回想要的。

        [HttpPost]
        [Route("reset-password")]
        public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
            try
            {
                if (model is null)
                    return Error("No data found!");


                var user = await _userManager.FindByIdAsync(AppCommon.ToString(GetUserId()));
                if (user == null)
                    return Error("No user found!");

                Microsoft.AspNetCore.Identity.SignInResult checkOldPassword =
                    await _signInManager.PasswordSignInAsync(user.UserName, model.OldPassword, false, false);

                if (!checkOldPassword.Succeeded)
                    return Error("Old password does not matched.");

                string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
                if (string.IsNullOrEmpty(resetToken))
                    return Error("Error while generating reset token.");

                var result = await _userManager.ResetPasswordAsync(user, resetToken, model.Password);

                if (result.Succeeded)
                    return Result();
                else
                    return Error();
            }
            catch (Exception ex)
            {
                return Error(ex);
            }
        }

这也适用于Fx v 4.5。其他解决方案无效。从根本上讲,这也要简单得多。您甚至根本不需要获取用户,因为所有方法都将接受id。我只需要它即可在管理界面中进行一次一次性重置,因此我不需要所有错误检查。
史蒂夫·辛纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.