美文网首页Asp.net core mvc
使用扩展方法更改ASP.NET Core Identity用户密

使用扩展方法更改ASP.NET Core Identity用户密

作者: firechun | 来源:发表于2019-01-18 16:13 被阅读6次

ASP.NET Core Identity提供的UserManager包括了一个修改密码的方法:

public virtual System.Threading.Tasks.Task<Microsoft.AspNetCore.Identity.IdentityResult> ChangePasswordAsync (TUser user, string currentPassword, string newPassword);

方法的参数分别是要修改密码的用户对象、当前密码和修改后的密码。很显然,这个方法能够满足常见的用户修改自己密码的场景:用户在UI中输入当前密码和新的密码,业务逻辑验证当前密码正确,然后用新的密码替换数据库中的当前密码。

但是这个方法无法满足这种场景:我是系统管理员,我不知道用户的当前密码,但我需要重置或修改这个用户的密码。因为我无法提供用户的当前密码,就没法使用上面的ChangePasswordAsync方法。

为了实现这个需求,查看UserManager的文档,ChangePasswordAsync方法并没有我们需要的重载,必须得提供当前密码才行,所以只能放弃这个方法,另僻蹊径。

浏览了整个UserManager类之后,没有找到想要的方法,但是看到这两个方法:AddPasswordAsync和RemovePasswordAsync,想到了一种解决方式:先移除旧密码,再添加新密码,效果和更改密码是一样的。

                    if (changedPassword)
                    {
                        //如果用户修改了密码,先移除该用户的密码,再为该用户添加新的密码
                        await userManager.RemovePasswordAsync(updateUser);
                        await userManager.AddPasswordAsync(updateUser, newPassword);
                    }

测试之后,效果和修改密码相同,而且不用提供用户的当前密码。

看起来没有任何问题了,但是等等!如果我们的代码运行完RemovePasswordAsync之后,系统因为各种原因意外崩溃了,那么用户的密码已经被删除,但新的密码却没有添加……也就是说,除非我们能保证这两个方法要么全部执行,要么全部不执行,就象数据库中的事务一样,否则这个修改密码的业务逻辑就存在一个很大的漏洞。

UserManager封装了对数据库的操作,显然我们没有办法为这两个方法添加“事务”,这种方式也得放弃。

下面我们继续查看UserManager文档,找到了属性Store,一个用于持久化存储的管理器,这个属性的类型接口IUserStore<TUser>,接口中定义了UpdateAsync方法用于更新用户,而且更新的是持久化存储,也就是数据库。但是这个接口中并没有提供和密码有关的方法。但是IUserStore派生出众多的子接口,其中有一个IUserPasswordStore<TUser>包含了三个方法:GetPasswordAsync,HasPasswordAsync和SetPasswordAsync。根据子类可以替换父类的原则,Store属性也可以是IUserPasswordStore的实现类对象。因为文档中无法体现这一点,我们需要用代码验证一下,简单地说,就是在代码中测试一下Store属性是否是IUserPasswordStore的实例:检查userManager.Store is IUserPasswordStore<TUser>返回值。这个很简单,我们在任意一个使用了UserManager对象的代码段中加一行代码测试一下就行了。不过问题又来了,Store是一个受保护的属性,无法直接访问,需要使用反射。

当然最终的结果肯定是符合我们期望的,不然就不会有这篇文章了,最后我们为UserManager添加一个扩展方法,实现我们的目的,上代码:

    public static class UserManagerExtensions
    {
        /// <summary>
        /// 修改用户密码,无需提供用户当前密码
        /// </summary>
        /// <param name="userManager"></param>
        /// <param name="user">要修改密码的用户对象</param>
        /// <param name="password">修改后的密码</param>
        /// <returns></returns>
        public static async Task<IdentityResult> ChangePasswordAsync(this UserManager<User> userManager, User user, string password)
        {
            var type = userManager.GetType();
            var storeProperty = type.GetProperty("Store", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            var store = storeProperty.GetValue(userManager) as IUserPasswordStore<User>;
            if (store == null)
            {
                return IdentityResult.Failed(new IdentityError { Code = "NotImplements", Description = "持久化存储器没有实现IUserPasswordStore接口" });
            }
            var passwordHash = userManager.PasswordHasher.HashPassword(user, password);
            await store.SetPasswordHashAsync(user, passwordHash, System.Threading.CancellationToken.None);
            await store.UpdateAsync(user, System.Threading.CancellationToken.None);
            return IdentityResult.Success;
        }
    }

代码中的User类是自定义的用户类,继承自IdentityUser类。
调用:

                    if (changedPassword)
                    {
                        await userManager.ChangePasswordAsync(updateUser, newPassword);
                    }

相关文章

网友评论

    本文标题:使用扩展方法更改ASP.NET Core Identity用户密

    本文链接:https://www.haomeiwen.com/subject/vlrndqtx.html