用户在 ASP.NET Core Identity 中打开设置页面时如何询问密码?

Posted

技术标签:

【中文标题】用户在 ASP.NET Core Identity 中打开设置页面时如何询问密码?【英文标题】:How to ask for password when user open settings page in ASP.NET Core Identity? 【发布时间】:2021-09-02 00:43:57 【问题描述】:

我在 ASP.NET Core 3.1 中配置了 Web 应用程序以将 ASP.NET Core Identity 与本地帐户一起使用。 一切正常。 我想强制用户在转到特定页面时再次插入密码(或使用外部提供程序),例如帐号设定。 我找不到这个案例的例子。 有人有这方面的经验吗? 也许有些文章我错过了。

到目前为止,唯一的想法是注销用户并打开登录页面,但这不符合逻辑,因为他应该能够不受限制地打开其他页面。

【问题讨论】:

【参考方案1】:

我没有尝试过,但我偶然发现了这个 blog post,听起来它对你有用,我已经把它缩短到几乎 TL;DR 帖子

设置所需的密码验证页面

RequirePasswordVerificationModel 类实现了 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 页面。此类是使用 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" />

登录 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 Core 数据库中。

public class ApplicationUser : IdentityUser

    public string LastLogin  get; set; 

当应用程序启动时,用户可以登录,并且需要验证密码才能访问实现需要此功能的 Razor 页面。

【讨论】:

以上是关于用户在 ASP.NET Core Identity 中打开设置页面时如何询问密码?的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.Net Core Identity 中刷新用户 cookie 票证

asp.net core系列 48 Identity 身份模型自定义

ASP.NET Core Identity 系列之四

使用 ASP.NET Core Identity 3 的用户角色权限

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

ASP.Net Core Identity - 管理其他用户