How to setup password expiration using ASP.NET Ide

2019-02-02 15:01发布

问题:

I have a ASP.NET project using Identity. For Identity Configuration regarding passwords, the PasswordValidator is being used. How do I expand the enforcement of password beyond what PasswordValidator has currently (RequiredLength, RequiredDigit, etc.) to satisfy a requirement that asks for password expiration after N days?

回答1:

There is no such functionality builtin ASP.NET Identity 2. Easiest is to add a field on the user like LastPasswordChangedDate. And then check this field during each Authorization.

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var user = await GetUser(context.UserName, context.Password);
        if(user.LastPasswordChangedDate.AddDays(20) < DateTime.Now)
           // user needs to change password

    }
}


回答2:

Adding on to @Rikard's answer...

I added LastPasswordChangedDate to my ApplicationUser model, like so:

    public class ApplicationUser : IdentityUser
    {
        public DateTime LastPasswordChangedDate { get; set; }

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }

Add a static configuration setting to AccountController (you'll need this later on in Login():

private static readonly int PasswordExpireDays = Convert.ToInt32(ConfigurationManager.AppSettings["PasswordExpireDays"]);

Then, during Login, check to see if the user should reset password. This only checks after a successful login, as to not bug user too much.

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, change to shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            switch (result)
            {
                case SignInStatus.Success:
                    var user = await UserManager.FindByNameAsync(model.Email);
                    if (user.LastPasswordChangedDate.AddDays(PasswordExpireDays) < DateTime.UtcNow)
                    {
                        return RedirectToAction("ChangePassword", "Manage");
                    }
                    else
                    {
                        return RedirectToLocal(returnUrl);
                    }
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Error: Invalid username or password");
                    return View(model);
            }
        }

Make sure to update the LastPasswordChangedDate when the user successfully updates password, in ManageController, ChangePassword action:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> ChangePassword(ChangePasswordViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }
        var result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword);
        if (result.Succeeded)
        {
            var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
            if (user != null)
            {
                user.LastPasswordChangedDate = DateTime.UtcNow;
                await UserManager.UpdateAsync(user);

                await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
            }
            return RedirectToAction("Index", new { Message = ManageMessageId.ChangePasswordSuccess });
        }
        AddErrors(result);
        return View(model);
    }