ASP.NET 身份更改密码

ASP.NET Identity change password

我需要能够由管理员更改用户密码。因此,管理员不应该输入用户的当前密码,他应该能够设置新密码。我查看 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 方法上收到错误。因此,旧密码将被删除,但未设置新密码。这不好。 "one transaction" 有什么办法吗? PS。我看到了带有重置令牌的 ResetPasswordAsync 方法,似乎它更安全(因为用户不会出现不稳定的情况)但无论如何,它通过 2 个操作来完成。

ApplicationUserManager 是由 ASP.NET 模板生成的 class。

这意味着,您可以编辑它并添加它尚不具备的任何功能。 UserManager class 有一个名为 Store 的受保护 属性,它存储对 UserStore class(或其任何子 class 的引用,具体取决于关于你如何配置你的 ASP.NET 身份,或者你是否使用自定义用户存储实现,即如果你使用不同的数据库引擎,如 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);
    }
}

UserManager 只不过是底层 UserStore 的包装器。在 MSDN 查看可用方法的 IUserPasswordStore 接口文档。

编辑: PasswordHasher也是UserManagerclass的public属性,见interface definition here.

编辑 2: 由于某些人天真地 认为,您不能以这种方式进行密码验证,因此我对其进行了更新。 PasswordValidator 属性 也是 UserManager 的 属性 并且它就像添加 2 行代码一样简单以添加密码验证(这不是原始要求问题。

这个方法对我有用:

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();
}

这只是对@Tseng 提供的答案的改进。 (我必须对其进行调整才能使其正常工作)。

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;
    }
}

注意:这特别适用于使用 int 作为用户和角色的主键的修改设置。我相信只需删除 <AppUser, int> 类型参数即可使其与默认的 ASP.NET 身份设置一起使用。

是的,你是对的。通过令牌重置密码是首选方法。 前段时间,我在 .NET Identity 上创建了一个完整的包装器,可以找到代码 here. It might be helpful for you. You can also find nuget here. I also explained the library in a blog here。这个包装器很容易作为 nuget 使用,并在安装过程中创建所有必需的配置。

编辑:我知道 OP 要求一个在一次交易中执行任务的答案,但我认为该代码对人们有用。

所有答案都直接使用 PasswordHasher,这不是一个好主意,因为您会失去一些内置功能(验证等)。

另一种方法(我认为是推荐的方法)是创建一个密码重置令牌,然后使用它来更改密码。示例:

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; }
    }

注意:这里的 UserId 我是从当前登录的用户中检索的。

如果您没有用户的当前密码但仍想更改密码。您可以做的是先删除用户密码,然后添加新密码。这样您就可以更改用户的密码而无需该用户的当前密码。

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 });
}

在 .net 核心 3.0 中

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

对于 ASP.NET Core 3.1 用户,这是对@Tseng 和@BCA 提供的优秀答案的现代化迭代。

PasswordValidator 不再是 UserManager 上的 属性 - 相反,属性 是 IList PasswordValidators。此外,Identity 现在有一个 protected UpdatePasswordHash 方法可以为您更改密码,而无需直接访问 UserStore,这样就无需手动散列并保存密码。

UserManager 也有一个 public 属性, bool SupportsUserPassword,它取代了测试 Store 是否实现 IUserPasswordStore 的需要(内部, 这正是 UserManagerSupportsUserPassword getter).

中所做的

由于 UpdatePasswordHashprotected,您仍然需要扩展基础 UserManager。它的签名是:

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

其中validatePassword表示是否运行密码验证。不幸的是,这 而不是 默认为 true,因此需要提供它。实现如下所示:

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;
}

和以前一样,首要任务是确保当前 UserStore 支持密码。

然后,只需使用 ApplicationUser、新密码和 true 调用 UpdatePasswordHash 即可通过验证更新该用户的密码。如果更新成功,你仍然需要保存用户所以调用 UpdateAsync.

我认为解决方案更简单

  1. 生成passwordToken,
  2. 使用生成的令牌重置密码...
public async Task<IdentityResult> ResetPasswordAsync(ApplicationUser user, string password)
{
    string token = await userManager.GeneratePasswordResetTokenAsync(user);
    return await userManager.ResetPasswordAsync(user, token, password);
}