ASP.NET Identity 更改密码

Posted

技术标签:

【中文标题】ASP.NET Identity 更改密码【英文标题】:ASP.NET Identity change password 【发布时间】: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 个动作来完成。

【问题讨论】:

这个问题的症结在于一次交易。你会满足于在两次交易中完成并继续尝试直到第二次成功吗?如果没有,您可能必须编写自己的更改密码的实现。 【参考方案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】:

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

    生成密码令牌, 用生成的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 使用,并在安装过程中创建所有必需的配置。

【讨论】:

更喜欢包含内容的答案,而不是外部链接。如果这些链接因任何原因而死亡,那么这个答案将完全没有用。此外,您正在回答一个超过两年的问题,两年后的答案已被接受,而没有提供任何额外的价值。

以上是关于ASP.NET Identity 更改密码的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET Core 中使用基于本地存储的 JWT-Token 更改用户密码(ASP.Identity)

如何在不影响密码更改行为的情况下正确防止 ASP.NET Identity 2.2.1 中的多个活动会话?

ASP.NET Identity 重置密码

如何覆盖 ASP.NET Core Identity 的密码策略

如何在 ASP.NET 5 MVC 6 (vNext) 中定义 Identity 的密码规则?

在 asp.net 会员中更改密码后旧密码仍然有效