Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
493 views
in Technique[技术] by (71.8m points)

asp.net mvc - ExpireTimeSpan ignored after regenerateIdentity / validateInterval duration in MVC Identity (2.0.1)

Been scratching my head all day on this one. I'm trying to set up "very long" login sessions in MVC Identity 2.0.1. (30 days).

I use the following cookie startup:

      app.UseCookieAuthentication(new CookieAuthenticationOptions
        {

            SlidingExpiration = true,
            ExpireTimeSpan = System.TimeSpan.FromDays(30),
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/My/Login"),
            CookieName = "MyLoginCookie",
            Provider = new CookieAuthenticationProvider
            {                           
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),

                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });

Which on the whole, works fine. The cookie is set 30 days hence, all looks good.

If I close browser and come back after "validateInterval" duration has passed (30mins here) I'm still logged in, however the cookie is now re-issued as "session" only (correct cookie name still)! The 30 day expiration is gone.

If I now close browser/reopen again I'm no longer logged in.

I have tested removing the "Provider" and all works as expected then, I can come back several hours later and I'm still logged in fine. I read that it is best practice to use the stamp revalidation though, so am unsure how to proceed.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

When the SecurityStampValidator fires the regenerateIdentity callback, the currently authenticated user gets re-signed in with a non-persistent login. This is hard-coded, and I don't believe there is any way to directly control it. As such, the login session will continue only to the end of the browser session you are running at the point the identity is regenerated.

Here is an approach to make the login persistent, even across identity regeneration operations. This description is based on using Visual Studio MVC ASP.NET web project templates.

First we need to have a way to track the fact that a login session is persistent across separate HTTP requests. This can be done by adding an "IsPersistent" claim to the user's identity. The following extension methods show a way to do this.

public static class ClaimsIdentityExtensions
{
    private const string PersistentLoginClaimType = "PersistentLogin";

    public static bool GetIsPersistent(this System.Security.Claims.ClaimsIdentity identity)
    {
        return identity.Claims.FirstOrDefault(c => c.Type == PersistentLoginClaimType) != null;
    }

    public static void SetIsPersistent(this System.Security.Claims.ClaimsIdentity identity, bool isPersistent)
    {
        var claim = identity.Claims.FirstOrDefault(c => c.Type == PersistentLoginClaimType);
        if (isPersistent)
        {
            if (claim == null)
            {
                identity.AddClaim(new System.Security.Claims.Claim(PersistentLoginClaimType, Boolean.TrueString));
            }
        }
        else if (claim != null)
        {
            identity.RemoveClaim(claim);
        }
    }
}

Next we need to make the "IsPersistent" claim when the user signs in requesting a persistent session. For example, your ApplicationUser class may have a GenerateUserIdentityAsync method which can be updated to take an isPersistent flag parameter as follows to make such a claim when needed:

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, bool isPersistent)
{
    var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
    userIdentity.SetIsPersistent(isPersistent);
    return userIdentity;
}

Any callers of ApplicationUser.GenerateUserIdentityAsync will now need to pass in the isPersistent flag. For example, the call to GenerateUserIdentityAsync in AccountController.SignInAsync would change from

AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, 
    await user.GenerateUserIdentityAsync(UserManager));

to

AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent },
    await user.GenerateUserIdentityAsync(UserManager, isPersistent));

Lastly, the CookieAuthenticationProvider.OnValidateIdentity delegate used in the Startup.ConfigureAuth method needs some attention to preserve the persistence details across identity regeneration operations. The default delegate looks like:

OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
    validateInterval: TimeSpan.FromMinutes(20),
    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))

This can be changed to:

OnValidateIdentity = async (context) =>
{
    await SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
        validateInterval: TimeSpan.FromMinutes(20),
        // Note that if identity is regenerated in the same HTTP request as a logoff attempt,
        // the logoff attempt will have no effect and the user will remain logged in.
        // See https://aspnetidentity.codeplex.com/workitem/1962
        regenerateIdentity: (manager, user) =>
            user.GenerateUserIdentityAsync(manager, context.Identity.GetIsPersistent())
    )(context);

    // If identity was regenerated by the stamp validator,
    // AuthenticationResponseGrant.Properties.IsPersistent will default to false, leading
    // to a non-persistent login session. If the validated identity made a claim of being
    // persistent, set the IsPersistent flag to true so the application cookie won't expire
    // at the end of the browser session.
    var newResponseGrant = context.OwinContext.Authentication.AuthenticationResponseGrant;
    if (newResponseGrant != null)
    {
        newResponseGrant.Properties.IsPersistent = context.Identity.GetIsPersistent();
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...