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
也是UserManager
class的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
的需要(内部, 这正是 UserManager
在 SupportsUserPassword
getter).
中所做的
由于 UpdatePasswordHash
是 protected
,您仍然需要扩展基础 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
.
我认为解决方案更简单
- 生成passwordToken,
- 使用生成的令牌重置密码...
public async Task<IdentityResult> ResetPasswordAsync(ApplicationUser user, string password)
{
string token = await userManager.GeneratePasswordResetTokenAsync(user);
return await userManager.ResetPasswordAsync(user, token, 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
也是UserManager
class的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
的需要(内部, 这正是 UserManager
在 SupportsUserPassword
getter).
由于 UpdatePasswordHash
是 protected
,您仍然需要扩展基础 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
.
我认为解决方案更简单
- 生成passwordToken,
- 使用生成的令牌重置密码...
public async Task<IdentityResult> ResetPasswordAsync(ApplicationUser user, string password)
{
string token = await userManager.GeneratePasswordResetTokenAsync(user);
return await userManager.ResetPasswordAsync(user, token, password);
}