当用户在 ASP.NET Core Identity 中打开设置页面时如何询问密码?
How to ask for password when user open settings page in ASP.NET Core Identity?
我在 ASP.NET Core 3.1 中配置 Web 应用程序以使用 ASP.NET Core Identity 和本地帐户。
一切正常。
我想强制用户在转到特定页面时再次输入密码(或使用外部提供商),例如帐号设定。
我找不到这个案例的例子。
有人有这方面的经验吗?
可能是我漏掉了一些文章。
到目前为止唯一的想法是注销用户并打开登录页面,但这不符合逻辑,因为他应该能够不受限制地打开其他页面。
我没试过这个,但我只是偶然发现了这个 blog post 听起来它对你有用,我把它缩短到几乎 TL;DR post
设置需要的密码验证页面
RequirePasswordVerificationModel class 实现要求用户在过去十分钟内验证身份用户密码的 Razor 页面。
public class RequirePasswordVerificationModel : PasswordVerificationBase
{
public RequirePasswordVerificationModel(UserManager<ApplicationUser> userManager) : base(userManager)
{
}
public async Task<IActionResult> OnGetAsync()
{
var passwordVerificationOk = await ValidatePasswordVerification();
if (!passwordVerificationOk)
{
return RedirectToPage("/PasswordVerification",
new { ReturnUrl = "/DoUserChecks/RequirePasswordVerification" });
}
return Page();
}
}
PasswordVerificationBase Razor 页面实现了 PageModel。 ValidatePasswordVerification 方法检查用户是否已通过身份验证
public class PasswordVerificationBase : PageModel
{
public static string PasswordCheckedClaimType = "passwordChecked";
private readonly UserManager<ApplicationUser> _userManager;
public PasswordVerificationBase(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<bool> ValidatePasswordVerification()
{
if (User.Identity.IsAuthenticated)
{
if (User.HasClaim(c => c.Type == PasswordCheckedClaimType))
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
var lastLogin = DateTime.FromFileTimeUtc(
Convert.ToInt64(user.LastLogin));
var lastPasswordVerificationClaim
= User.FindFirst(PasswordCheckedClaimType);
var lastPasswordVerification = DateTime.FromFileTimeUtc(
Convert.ToInt64(lastPasswordVerificationClaim.Value));
if (lastLogin > lastPasswordVerification)
{
return false;
}
else if (DateTime.UtcNow.AddMinutes(-10.0) > lastPasswordVerification)
{
return false;
}
return true;
}
}
return false;
}
}
如果用户需要重新输入凭据,可以使用 PasswordVerificationModel Razor 页面。此 class 是使用来自 ASP.NET Core Identity 的身份脚手架登录 Razor 页面构建的。使用 UserManager 服务删除旧密码验证声明。如果用户成功重新输入密码并使用新的 ClaimIdentity 实例刷新登录,则会创建新的密码验证声明。
public class PasswordVerificationModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<PasswordVerificationModel> _logger;
public PasswordVerificationModel(SignInManager<ApplicationUser> signInManager,
ILogger<PasswordVerificationModel> logger,
UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public CheckModel Input { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string ErrorMessage { get; set; }
public class CheckModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return NotFound($"User has no password'{_userManager.GetUserId(User)}'.");
}
returnUrl ??= Url.Content("~/");
ReturnUrl = returnUrl;
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(user.Email, Input.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User password re-entered");
await RemovePasswordCheck(user);
var claim = new Claim(PasswordVerificationBase.PasswordCheckedClaimType,
DateTime.UtcNow.ToFileTimeUtc().ToString());
await _userManager.AddClaimAsync(user, claim);
await _signInManager.RefreshSignInAsync(user);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
private async Task RemovePasswordCheck(ApplicationUser user)
{
if (User.HasClaim(c => c.Type == PasswordVerificationBase.PasswordCheckedClaimType))
{
var claims = User.FindAll(PasswordVerificationBase.PasswordCheckedClaimType);
foreach (Claim c in claims)
{
await _userManager.RemoveClaimAsync(user, c);
}
}
}
}
PasswordVerificationModel Razor 页面 html 模板显示带有密码字段的用户输入表单。
@page
@model PasswordVerificationModel
@{
ViewData["Title"] = "Password Verification";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<section>
<form id="account" method="post">
<h4>Verify account using your password</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password"
class="text-danger"></span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">
Re-enter password
</button>
</div>
</form>
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Login Razor 页面需要更新以在登录成功时添加 DateTime.UtcNow 的登录文件时间值。此值在基本 Razor 页面中用于验证密码检查。为此添加了 LastLogin 属性。
var result = await _signInManager.PasswordSignInAsync(
Input.Email, Input.Password,
Input.RememberMe,
lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
return NotFound("help....");
}
user.LastLogin = DateTime.UtcNow.ToFileTimeUtc().ToString();
var lastLoginResult = await _userManager.UpdateAsync(user);
return LocalRedirect(returnUrl);
}
LastLogin 属性 已添加到实现 IdentityUser 的 ApplicationUser。此值保存到 Entity Framework 核心数据库。
public class ApplicationUser : IdentityUser
{
public string LastLogin { get; set; }
}
当应用程序启动时,用户可以登录并需要验证密码才能访问为需要此功能而实现的 Razor 页面。
我在 ASP.NET Core 3.1 中配置 Web 应用程序以使用 ASP.NET Core Identity 和本地帐户。 一切正常。 我想强制用户在转到特定页面时再次输入密码(或使用外部提供商),例如帐号设定。 我找不到这个案例的例子。 有人有这方面的经验吗? 可能是我漏掉了一些文章。
到目前为止唯一的想法是注销用户并打开登录页面,但这不符合逻辑,因为他应该能够不受限制地打开其他页面。
我没试过这个,但我只是偶然发现了这个 blog post 听起来它对你有用,我把它缩短到几乎 TL;DR post
设置需要的密码验证页面
RequirePasswordVerificationModel class 实现要求用户在过去十分钟内验证身份用户密码的 Razor 页面。
public class RequirePasswordVerificationModel : PasswordVerificationBase
{
public RequirePasswordVerificationModel(UserManager<ApplicationUser> userManager) : base(userManager)
{
}
public async Task<IActionResult> OnGetAsync()
{
var passwordVerificationOk = await ValidatePasswordVerification();
if (!passwordVerificationOk)
{
return RedirectToPage("/PasswordVerification",
new { ReturnUrl = "/DoUserChecks/RequirePasswordVerification" });
}
return Page();
}
}
PasswordVerificationBase Razor 页面实现了 PageModel。 ValidatePasswordVerification 方法检查用户是否已通过身份验证
public class PasswordVerificationBase : PageModel
{
public static string PasswordCheckedClaimType = "passwordChecked";
private readonly UserManager<ApplicationUser> _userManager;
public PasswordVerificationBase(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<bool> ValidatePasswordVerification()
{
if (User.Identity.IsAuthenticated)
{
if (User.HasClaim(c => c.Type == PasswordCheckedClaimType))
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
var lastLogin = DateTime.FromFileTimeUtc(
Convert.ToInt64(user.LastLogin));
var lastPasswordVerificationClaim
= User.FindFirst(PasswordCheckedClaimType);
var lastPasswordVerification = DateTime.FromFileTimeUtc(
Convert.ToInt64(lastPasswordVerificationClaim.Value));
if (lastLogin > lastPasswordVerification)
{
return false;
}
else if (DateTime.UtcNow.AddMinutes(-10.0) > lastPasswordVerification)
{
return false;
}
return true;
}
}
return false;
}
}
如果用户需要重新输入凭据,可以使用 PasswordVerificationModel Razor 页面。此 class 是使用来自 ASP.NET Core Identity 的身份脚手架登录 Razor 页面构建的。使用 UserManager 服务删除旧密码验证声明。如果用户成功重新输入密码并使用新的 ClaimIdentity 实例刷新登录,则会创建新的密码验证声明。
public class PasswordVerificationModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<PasswordVerificationModel> _logger;
public PasswordVerificationModel(SignInManager<ApplicationUser> signInManager,
ILogger<PasswordVerificationModel> logger,
UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public CheckModel Input { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string ErrorMessage { get; set; }
public class CheckModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return NotFound($"User has no password'{_userManager.GetUserId(User)}'.");
}
returnUrl ??= Url.Content("~/");
ReturnUrl = returnUrl;
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(user.Email, Input.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User password re-entered");
await RemovePasswordCheck(user);
var claim = new Claim(PasswordVerificationBase.PasswordCheckedClaimType,
DateTime.UtcNow.ToFileTimeUtc().ToString());
await _userManager.AddClaimAsync(user, claim);
await _signInManager.RefreshSignInAsync(user);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
private async Task RemovePasswordCheck(ApplicationUser user)
{
if (User.HasClaim(c => c.Type == PasswordVerificationBase.PasswordCheckedClaimType))
{
var claims = User.FindAll(PasswordVerificationBase.PasswordCheckedClaimType);
foreach (Claim c in claims)
{
await _userManager.RemoveClaimAsync(user, c);
}
}
}
}
PasswordVerificationModel Razor 页面 html 模板显示带有密码字段的用户输入表单。
@page
@model PasswordVerificationModel
@{
ViewData["Title"] = "Password Verification";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<section>
<form id="account" method="post">
<h4>Verify account using your password</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password"
class="text-danger"></span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">
Re-enter password
</button>
</div>
</form>
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Login Razor 页面需要更新以在登录成功时添加 DateTime.UtcNow 的登录文件时间值。此值在基本 Razor 页面中用于验证密码检查。为此添加了 LastLogin 属性。
var result = await _signInManager.PasswordSignInAsync(
Input.Email, Input.Password,
Input.RememberMe,
lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
return NotFound("help....");
}
user.LastLogin = DateTime.UtcNow.ToFileTimeUtc().ToString();
var lastLoginResult = await _userManager.UpdateAsync(user);
return LocalRedirect(returnUrl);
}
LastLogin 属性 已添加到实现 IdentityUser 的 ApplicationUser。此值保存到 Entity Framework 核心数据库。
public class ApplicationUser : IdentityUser
{
public string LastLogin { get; set; }
}
当应用程序启动时,用户可以登录并需要验证密码才能访问为需要此功能而实现的 Razor 页面。