ASP.NET 身份更改密码

我需要能力改变用户的密码由管理员。因此,管理员不应该输入用户的当前密码,他应该有能力设置一个新的密码。我查看了 ChangePasswordAsync 方法,但是这个方法需要输入旧密码。所以,这个方法不适合这个任务。因此,我通过以下方式做到了这一点:

    [HttpPost]
public async Task<ActionResult> ChangePassword(ViewModels.Admin.ChangePasswordViewModel model)
{
var userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
var result = await userManager.RemovePasswordAsync(model.UserId);
if (result.Succeeded)
{
result = await userManager.AddPasswordAsync(model.UserId, model.Password);
if (result.Succeeded)
{
return RedirectToAction("UserList");
}
else
{
ModelState.AddModelError("", result.Errors.FirstOrDefault());
}
}
else
{
ModelState.AddModelError("", result.Errors.FirstOrDefault());
}
return View(model);
}

它可以工作,但是理论上我们可以在 AddPasswordAsync 方法上接收错误。因此,将删除旧密码,但不设置 new。情况不妙。“一次交易”有什么办法吗? 附言。我看到了带有重置令牌的 ResetPasswordAsync 方法,似乎,它更安全(因为不能与用户不稳定的情况下) ,但在任何情况下,它做了2个动作。

122612 次浏览

ApplicationUserManager is the class generated by the ASP.NET Template.

Which means, you can edit it and add any functionality it doesn't have yet. The UserManager class has a protected property named Store which stores a reference to the UserStore class (or any subclass of it, depending on how you configured your ASP.NET Identity or if you use custom user store implementations, i.e. if you use different database engine like MySQL).

public class AplicationUserManager : UserManager<....>
{
public async Task<IdentityResult> ChangePasswordAsync(TKey userId, string newPassword)
{
var store = this.Store as IUserPasswordStore;
if(store==null)
{
var errors = new string[]
{
"Current UserStore doesn't implement IUserPasswordStore"
};


return Task.FromResult<IdentityResult>(new IdentityResult(errors) { Succeeded = false });
}


if(PasswordValidator != null)
{
var passwordResult = await PasswordValidator.ValidateAsync(password);
if(!password.Result.Success)
return passwordResult;
}


var newPasswordHash = this.PasswordHasher.HashPassword(newPassword);


await store.SetPasswordHashAsync(userId, newPasswordHash);
return Task.FromResult<IdentityResult>(IdentityResult.Success);
}
}

The UserManager is nothing else than a wrapper to the underlying UserStore. Check out IUserPasswordStore interface documentation at MSDN on available Methods.

Edit: The PasswordHasher is also a public property of the UserManager class, see interface definition here.

Edit 2: Since some people naively believe, you can't do password validation this way, I've updated it. The PasswordValidator property is also a property of UserManager and its as simple as adding 2 lines of code to add password validation too (which wasn't an requirement of the original question though).

This method worked for me:

public async Task<IHttpActionResult> changePassword(UsercredentialsModel usermodel)
{
ApplicationUser user = await AppUserManager.FindByIdAsync(usermodel.Id);
if (user == null)
{
return NotFound();
}
user.PasswordHash = AppUserManager.PasswordHasher.HashPassword(usermodel.Password);
var result = await AppUserManager.UpdateAsync(user);
if (!result.Succeeded)
{
//throw exception......
}
return Ok();
}

This is just a refinement on the answer provided by @Tseng. (I had to tweak it to get it to work).

public class AppUserManager : UserManager<AppUser, int>
{
.
// standard methods...
.


public async Task<IdentityResult> ChangePasswordAsync(AppUser user, string newPassword)
{
if (user == null)
throw new ArgumentNullException(nameof(user));


var store = this.Store as IUserPasswordStore<AppUser, int>;
if (store == null)
{
var errors = new string[] { "Current UserStore doesn't implement IUserPasswordStore" };
return IdentityResult.Failed(errors);
}


var newPasswordHash = this.PasswordHasher.HashPassword(newPassword);
await store.SetPasswordHashAsync(user, newPasswordHash);
await store.UpdateAsync(user);
return IdentityResult.Success;
}
}

Note: this applies specifically to a modified setup that uses int as the primary keys for users and roles. I believe it would simply be a matter of removing the <AppUser, int> type args to get it to work with the default ASP.NET Identity setup.

Yes, you are correct. ResetPassword through token is a preferred approach. Sometime back, I created a complete wrapper over .NET Identity and code can be found here. It might be helpful for you. You can also find nuget here. I also explained the library in a blog here. This wrapper is easily consumable as nuget and create all required configs during installation.

EDIT: I know the OP requested an answer which performs the task in one transaction but I think the code is useful to people.

All the answers use the PasswordHasher directly which isn't a good idea as you will lose some baked in functionality (validation etc).

An alternative (and I would assume the recommended approach) is to create a password reset token and then use that to change the password. Example:

var user = await UserManager.FindByIdAsync(id);


var token = await UserManager.GeneratePasswordResetTokenAsync(user);


var result = await UserManager.ResetPasswordAsync(user, token, "MyN3wP@ssw0rd");
public async Task<IActionResult> ChangePassword(ChangePwdViewModel usermodel)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.ChangePasswordAsync(user, usermodel.oldPassword, usermodel.newPassword);
if (!result.Succeeded)
{
//throw exception......
}
return Ok();
}


public class ChangePwdViewModel
{
[DataType(DataType.Password), Required(ErrorMessage ="Old Password Required")]
public string oldPassword { get; set; }


[DataType(DataType.Password), Required(ErrorMessage ="New Password Required")]
public string newPassword { get; set; }
}

Note : here UserId i am retrieving from Current logged User.

If you don't have user's current password and still want to change the password. What you could do instead remove user's password first and then add the new password. This way you will be able to change user's password without needing current password of that user.

await UserManager.RemovePasswordAsync(user);
await UserManager.AddPasswordAsync(user, model.Password);
public async Task<ActionResult> ResetUserPassword(string id, string Password)
{
//     Find User
var user = await context.Users.Where(x => x.Id == id).SingleOrDefaultAsync();
if (user == null)
{
return RedirectToAction("UserList");
}
await UserManager.RemovePasswordAsync(id);
//     Add a user password only if one does not already exist
await UserManager.AddPasswordAsync(id, Password);
return RedirectToAction("UserDetail", new { id = id });
}

In .net core 3.0

var token = await UserManager.GeneratePasswordResetTokenAsync(user);
var result = await UserManager.ResetPasswordAsync(user, token, password);

For ASP.NET Core 3.1 users, this is a modernized iteration of the excellent answers provided by @Tseng and @BCA.

PasswordValidator is no longer a property on UserManager - instead, the property is an IList PasswordValidators. Furthermore, Identity now has a protected UpdatePasswordHash method that changes the password for you without needing to directly access the UserStore, which eliminates the need to manually hash and save the password anyway.

UserManager also has a public property, bool SupportsUserPassword, which replaces the need to test if Store implements IUserPasswordStore (internally, this is exactly what UserManager does in the SupportsUserPassword getter).

Since UpdatePasswordHash is protected, you do still need to extend the base UserManager. Its signature is:

protected Task<IdentityResult> UpdatePasswordHash(TUser user, string newPassword, bool validatePassword)

where validatePassword represents whether or not to run password validation. This does not default to true, unfortunately, so it needs to be supplied. The implementation looks like this:

public async Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, string newPassword)
{
if (!SupportsUserPassword)
{
return IdentityResult.Failed(new IdentityError
{
Description = "Current UserStore doesn't implement IUserPasswordStore"
});
}
            

var result = await UpdatePasswordHash(user, newPassword, true);
if (result.Succeeded)
await UpdateAsync(user);


return result;
}

As before, the first order of business is to ensure the current UserStore supports passwords.

Then, simply call UpdatePasswordHash with the ApplicationUser, the new password, and true to update that user's password with validation. If the update was successful, you still have to save the user so call UpdateAsync.

I think that the solution is much easier

  1. Generate the passwordToken,
  2. Reset the password with the generated Token...
public async Task<IdentityResult> ResetPasswordAsync(ApplicationUser user, string password)
{
string token = await userManager.GeneratePasswordResetTokenAsync(user);
return await userManager.ResetPasswordAsync(user, token, password);
}