ASP.NET Identity 2 Invalidate Identity Difficultie

2019-04-11 00:16发布

问题:

I have been updating my implementation of ASP.NET Identity all day today and I feel I'm on the last step, but just can't make it work. All I want to happen is to have the user's current session (if any) invalidated when something about them changes and to send them back to the login page. From the dozens of Identity related articles I've been reading today, I've settled that I have to override the OnValidateIdentity delegate, but it's just not working. Below is my code, I would really appreciate it if someone could tell me what I'm missing because I surely am not seeing it...

OwinConfiguration.cs

public static class OwinConfiguration {
    public static void Configuration(
        IAppBuilder app) {
        if (app == null) {
            return;
        }

        // SOLUTION: the line below is needed so that OWIN can
        // instance the UserManager<User, short>
        app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<UserManager<User, short>>());

        // SOLUTION: which is then used here to invalidate
        app.UseCookieAuthentication(new CookieAuthenticationOptions {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/"),
            ExpireTimeSpan = new TimeSpan(24, 0, 0),
            Provider = new CookieAuthenticationProvider {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager<User, short>, User, short>(
                    // SOLUTION: make sure this is set to 0 or it will take
                    // however long you've set it to before the session is
                    // invalidated which to me seems like a major security
                    // hole. I've seen examples set it to 30 minutes, in
                    // which time a disgruntled employee (say, after being
                    // fired) has plenty of opportunity to do damage in the
                    // system simply because their session wasn't expired
                    // even though they were disabled...
                    validateInterval: TimeSpan.FromMinutes(0),
                    regenerateIdentityCallback: (m, u) => u.GenerateUserIdentityAsync(m),
                    getUserIdCallback: (id) => short.Parse(id.GetUserId())
                )
            },
            SlidingExpiration = true
        });
    }
}

The GenerateUserIdentityAsync method looked like it needed to be a part of the entity, which I didn't like, so I made an extesion method for it that's internal to the assembly with the OWIN configuration:

UserExtensions.cs

internal static class UserExtensions {
    public static async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        this User user,
        UserManager<User, short> manager) {
        var userIdentity = await manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

        return userIdentity;
    }
}

I have a feeling that it has something to do with insancing the UserManager<User, short>, but I can't seem to resolve it. I think the OWIN app has to create a singleton of it for the request, but it's not happening and thus the validation override isn't working? The thing is, I'm using Ninject, and I'm not sure how to make it cooperate with OWIN since OWIN is much earlier in the pipeline... Here's the Ninject configuration:

NinjectConfiguration.cs

namespace X.Dependencies {
    using System;
    using System.Linq;
    using System.Web;
    using Data;
    using Data.Models;
    using Identity;
    using Microsoft.AspNet.Identity;
    using Microsoft.Owin.Security;
    using Microsoft.Web.Infrastructure.DynamicModuleHelper;
    using Ninject;
    using Ninject.Modules;
    using Ninject.Web.Common;
    using Services;

    public static class NinjectConfiguration {
        private static readonly Bootstrapper Bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start() {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));

            Bootstrapper.Initialize(CreateKernel);
        }

        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop() {
            Bootstrapper.ShutDown();
        }

        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel() {
            var kernel = new StandardKernel();

            try {
                kernel.Bind<Func<IKernel>>().ToMethod(
                    c => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

                RegisterServices(kernel);

                return kernel;
            } catch {
                kernel.Dispose();

                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(
            IKernel kernel) {
            if (kernel == null) {
                return;
            }

            kernel.Bind<XContext>().ToSelf().InRequestScope();

            kernel.Bind<IUserStore<User, short>>().To<UserStore>().InRequestScope();

            kernel.Bind<IAuthenticationManager>().ToMethod(
                c =>
                    HttpContext.Current.GetOwinContext().Authentication).InRequestScope();

            RegisterModules(kernel);
        }

        private static void RegisterModules(
            IKernel kernel) {
            var modules = AssemblyHelper.GetTypesInheriting<NinjectModule>().Select(Activator.CreateInstance).Cast<NinjectModule>();

            kernel.Load(modules);
        }
    }
}

A lot of the OWIN and Identity portions were put together by copy/pasting/adjusting from what I've found online... I would really appreciate some help. Thanks in advance!

回答1:

Most likely you are missing UserManager registration with OWIN.

The MVC template given with latest VS has these lines of code:

app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

This is run very early in the application lifecycle and effectively registers the delegate on how to create ApplicationUserManager. This code usually sits just before your line app.UseCookieAuthentication. And it is required to provide OWIN with delegate on how to create ApplicationUserManager because it is used in the routine of cookie invalidation when SecurityStamp is changed in the database.

Now the tricky part is to give OWIN correct delegate to work with. A lot of the times your DI container is created after this code is run. So you need to be careful about this. And usually you need to register your DI as a ServiceProvider for MVC to get your controllers resolved. And if this works, you'll get your ApplicationUserManager from MVC service provider:

app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());

Here is the full sample of the code. Or you keep the static method that creates an instance of the ApplicationUserManager.

I've blogged about using DI with Identity. And there is a GitHub repository with working code sample of DI container working with Indentity. I hope this can give you some ideas.



回答2:

Your cookie is set to expire in 30 minutes but with SlidingExpiration set to true. What it means is, the cookie will expire in 30 minutes if a user has not used the web site for more than 30 minutes. But if a user has stop using the web site for 28 mins and then comes to click on a button at the 29 mins or within the 30 minute expiration duration, the cookie expiration will be reset to another 30 mins. In effect that cookie will not expire. And you will not see the effect of the cookie expiration. The cookie will only be invalid after 30 minutes and session will no longer be valid. The session is valid if the cookie is valid and vice versa.

This may be what you are experiencing.

Set to false.

 SlidingExpiration = false


回答3:

Some code will explain better. This method is only accessible after you have logged in

[Authorise]
public ActionResult Dashboard()
{
   return View();
}

Your authentication cookie settings

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider
        {
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                validateInterval: TimeSpan.FromMinutes(2),
                regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
        },
        SlidingExpiration = false                
    });