【问题标题】:ASP.NET Identity change passwordASP.NET Identity 更改密码
【发布时间】:2015-05-31 05:32:54
【问题描述】:

我需要管理员更改用户密码的能力。所以,管理员不应该输入用户的当前密码,他应该有能力设置新密码。我看一下 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 方法上收到错误。因此,旧密码将被删除,但未设置新密码。这样不好。有什么办法可以在“一次交易”中做到这一点? PS。我看到了带有重置令牌的 ResetPasswordAsync 方法,似乎它更安全(因为用户的情况不会不稳定),但无论如何,它通过 2 个动作来完成。

【问题讨论】:

  • 这个问题的症结在于一次交易。你会满足于在两次交易中完成并继续尝试直到第二次成功吗?如果没有,您可能必须编写自己的更改密码的实现。

标签: c# asp.net-mvc-5 asp.net-identity-2 reset-password


【解决方案1】:

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

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

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

var user = await UserManager.FindByIdAsync(id);

var token = await UserManager.GeneratePasswordResetTokenAsync(user);

var result = await UserManager.ResetPasswordAsync(user, token, "MyN3wP@ssw0rd");

【讨论】:

  • 这似乎是正确的方法,并开启了许多新的可能性。谢谢!
  • 我更喜欢这个答案
  • 您当然也可以使用 UserManager 中定义的PasswordValidator 属性在设置之前对其进行验证。但这不是原始问题的要求
  • 这似乎是更好的方法,谢谢 - 仍想使用内置的安全检查和 IdentityResult。似乎很难直接更新值
  • 优秀的答案。需要注意的一件事:如果您没有注册令牌提供程序,它将引发异常。我必须在 Program.cs 中将 .AddDefaultTokenProviders() 添加到 IdentityBuilder 流利的链中(例如链接在 builder.Services.AddIdentityCore 之后)
【解决方案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();
}

【讨论】:

  • 虽然这段代码可能运行良好,但一个好的答案还应该解释它是如何工作的以及为什么它是一个好的解决方案。
  • UserManager 类中的默认更改密码方法首先尝试验证密码并进行安全检查,但我像普通字段一样直接更新密码值,对其没有特殊限制。
  • 什么是 UsercredentialsModel?
  • UsercredentialsModel 是一个简单的自定义类作为用户模型。在我的情况下,它类似于' public class UsercredentialsModel { public string Id { get;放; } 公共字符串电话号码 { 获取;放; } 公共字符串验证码 { 获取;放; } 公共字符串密码 { 获取;放; } }' @Dov Miller
  • 虽然我觉得这不是更改密码的正确方法,但它在无法生成 ResetToken 和验证令牌的不同情况下帮助了我。
【解决方案3】:

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

这意味着,您可以对其进行编辑并添加它还没有的任何功能。 UserManager 类有一个名为 Store 的受保护属性,它存储对 UserStore 类(或其任何子类,取决于您如何配置 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 也是 UserManager 类的公共属性,请参阅 interface definition here

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

【讨论】:

  • 这是一个有趣的方法。我想知道我们可以实现什么,以便在一次事务中更改密码,即使管理员不知道原始密码。
  • 这种方法是一笔交易。你不需要旧密码,因为新实现的没有使用旧密码的任何功能,如果有的话,直接从UserStore的实现中调用SetPasswordHashAsync。您只需要知道userIdnewPassword(可以随机生成)
  • 我使用了这个答案,但密码没有更新到数据库表,除非我在执行SetPasswordHashAsync 方法后添加await store.UpdateAsync(user, cancellation);。附言我对此做了一些调整,userclass ApplicationUser: IdentityUser 的对象。为什么会发生这种情况?
【解决方案4】:

在 .net core 3.0 中

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

【讨论】:

    【解决方案5】:

    这只是对@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 作为用户和角色的主键的修改设置。我相信只需删除 &lt;AppUser, int&gt; 类型参数即可使其与默认的 ASP.NET 身份设置一起使用。

    【讨论】:

      【解决方案6】:

      我认为解决方案要容易得多

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

      【讨论】:

        【解决方案7】:
        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 我是从当前登录的用户中检索的。

        【讨论】:

          【解决方案8】:

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

          await UserManager.RemovePasswordAsync(user);
          await UserManager.AddPasswordAsync(user, model.Password);
          

          【讨论】:

          • 这个问题是如果新密码验证失败,您已经删除了现有密码。您可以在删除旧密码之前尝试验证密码,但仍然存在添加密码失败的风险,导致用户没有密码并且可能无法登录
          【解决方案9】:

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

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

          UserManager 还有一个公共属性bool SupportsUserPassword,它取代了测试Store 是否实现IUserPasswordStore 的需要(在内部,这正是UserManagerSupportsUserPassword getter 中所做的事情)。

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

          protected Task&lt;IdentityResult&gt; 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

          【讨论】:

            【解决方案10】:
            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 });
            }
            

            【讨论】:

              【解决方案11】:

              是的,你是对的。通过令牌重置密码是首选方法。 前一段时间,我在 .NET Identity 上创建了一个完整的包装器,代码可以在 here 找到。它可能对你有帮助。您还可以找到 nuget here。我还在博客here 中解释了该库。这个包装器很容易作为 nuget 使用,并在安装过程中创建所有必需的配置。

              【讨论】:

              • 更喜欢包含内容的答案,而不是外部链接。如果这些链接因任何原因而死亡,那么这个答案将完全没有用。此外,您正在回答一个超过两年的问题,两年后的答案已被接受,而没有提供任何额外的价值。
              猜你喜欢
              • 2019-05-01
              • 1970-01-01
              • 2017-06-17
              • 2014-09-07
              • 2013-11-26
              • 2013-10-31
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多