MVC 5.2 Cookie 登录 OWIN 并注入经过身份验证的用户信息

MVC 5.2 Cookie Sign In OWIN and Injecting Authenticated User Information

由于我编写(当前)我的应用程序服务实现的方式以及它们为依赖注入配置的方式,我对我的 MVC 应用程序有点困惑。

我希望通过遵循 SOLID 原则来分离应用程序的层。

问题是在其中一些服务中,构造函数需要 IUserContext 的实例。 IUserContext 包含有关登录用户的各种信息,并将传递到几个不同的层。

public class ProjectDataLoader : DataLoaderBase, IProjectDataLoader
{
    public ProjectDataLoader(IMyDbContext dbContext, IUserContext userContext)
        : base (dbContext, userContext)
    {
    }
    ...

    public IEnumerable<ProjectViewModel> Find(string filter = "")
    {
        ...
    }
}

以及 IUserContext 的实现:

public class AspNetUserContext : IUserContext
{
    ...
}

我可以在每个方法调用上传递 IUserContext,但我觉得它属于构造函数。但这不是这里的问题。

当我通过 AccountController 从登录页面登录时,MyAppSignInManager.SignInOrTwoFactor 通过 OWIN 管道被调用。此时我正在会话中创建一个新的 AspNetUserContext 实例:

HttpContext.Current.Session["UserContext"] = aspNetUserContext;

现在我有自定义 SignInManager 实现:

public class MyAppSignInManager : SignInManager<MyAppUser, string>
{
    ...
}

我有一个自定义的 IUserStore 实现:

public class MyAppUserStore : IUserPasswordStore<MyAppUser>,
    IUserStore<MyAppUser>
{
    ...
}

以上所有内容都已连接到我选择的容器的简单注入器依赖注入。

public static class DependencyConfig
{
    public static Container Initialize(IAppBuilder app)
    {
        Container container = GetInitializeContainer(app);
        container.Verify();

        DependencyResolver.SetResolver(
            new SimpleInjectorDependencyResolver(container));

        return container;
    }

    private static Container GetInitializeContainer(IAppBuilder app)
    {
        var container = new Container();

        RegisterCommon(container);
        RegisterRepositories(container);
        RegisterDataLoaders(container);
        RegisterAppServices(container);
        RegisterMvc(app, container);

        return container;
    }

    private static void RegisterCommon(Container container)
    {
        container.Register<IUserContext>(() =>
        {
            IUserContext context = null;
            if (HttpContext.Current.Session == null)
                context = new AspNetUserContext(Guid.Empty, Guid.Empty);
            else
                context = (IUserContext)HttpContext.Current.Session["UserContext"];

            return context;

        }, Lifestyle.Transient);
    }

    private static void RegisterRepositories(Container container)
    {
        container.RegisterPerWebRequest<IUserRepository>(() =>
            new UserRepository(container.GetInstance<IMyApp4Context>()));

        container.Register<IMyApp4Context>(() => new MyApp4Context(),
            Lifestyle.Transient);
    }

    private static void RegisterDataLoaders(Container container)
    {
        container.Register<IProjectDataLoader, ProjectDataLoader>();
        container.Register<ContractDataLoader>();
        container.Register<DrawingDataLoader>();
        container.Register<WeldDataLoader>();
    }

    private static void RegisterAppServices(Container container)
    {
    }

    private static void RegisterMvc(IAppBuilder app, Container container)
    {
        container.RegisterSingle(app);
        container.RegisterPerWebRequest<MyAppUserManager>();
        container.RegisterPerWebRequest<SignInManager<MyAppUser, string>,
            MyAppAppSignInManager>();

        container.RegisterPerWebRequest(() =>
        {
            if (HttpContext.Current != null && 
                HttpContext.Current.Items["owin.Environment"] == null && 
                container.IsVerifying())
            {
                return new OwinContext().Authentication;
            }
            return HttpContext.Current.GetOwinContext().Authentication;
        });

        container.RegisterPerWebRequest<IUserStore<MyAppUser>>(() =>
            new MyAppUserStore(container.GetInstance<IUserRepository>()));

        app.UseOwinContextInjector(container);

        container.RegisterMvcControllers(
                Assembly.GetExecutingAssembly());

    }

    private static void InitializeUserManager(MyAppUserManager manager, IAppBuilder app)
    {
        manager.UserValidator =
         new UserValidator<MyAppUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        manager.PasswordValidator = new PasswordValidator()
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = false,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        IDataProtectionProvider dataProtectionProvider =
             app.GetDataProtectionProvider();

        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider =
             new DataProtectorTokenProvider<MyAppUser>(
              dataProtectionProvider.Create(purposes: new string[] { "ASP.NET Identity" }));
        }
    }

}

还有:

public partial class Startup
{   
    public void ConfigureAuth(IAppBuilder app, Container container)
    {
        app.CreatePerOwinContext(() => container.GetInstance<MyAppUserManager>());

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString(value: "/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<MyAppUserManager, MyAppUser>(
                    validateInterval: TimeSpan.FromMinutes(value: 30),
                    regenerateIdentity: (manager, user) =>
                    {
                        return user.GenerateUserIdentityAsync(manager);
                    })
            }
        });
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
    }
}

那么这些将在控制器中使用:

public class ProjectController : MyBaseContextController
{
    public ProjectController(IProjectDataLoader loader)
        : base(context)
    {
        ...
    }
}

我最初的问题是如何在 cookie 身份验证发生后获得 MyAppUser。也许问这个仍然有效。

更好的问题是问我想要完成什么。本质上,我想要的是将 IUserContext 注入到我的服务中。这需要注入到我的 DI 容器中注册的各种服务实现的构造函数中。但是,在用户登录 in/authenticated.

之前,此实例将不可用

注意: 所有用户信息都存储在 SQL 中,我使用 Entity Framework 访问所有这些信息。

因此,一旦用户通过登录页面通过 MyAppSignInManager.SignInOrTwoFactor 方法登录并通过 cookie 进行了身份验证,我如何才能使我的 AspNetUserContext (IUserContext) 实例可用于我的 DI 容器?

注意:我只想从数据库中获取一次用户信息——而不是每次调用需要它的控制器。

"I just want to get the user information from the database once."

您应该考虑在声明中存储所需的用户数据。

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString(value: "/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<MyAppUserManager, MyAppUser>(
            validateInterval: TimeSpan.FromMinutes(value: 30),
            regenerateIdentity: (manager, user) =>
            {
                return user.GenerateUserIdentityAsync(manager);
            })
    }
})

GenerateUserIdentityAsync 方法添加了核心身份声明,但您可以覆盖它并存储您的服务需要的自定义声明。然后,您可以传入 IClaimsIdentity.

而不是将 IUserContext 传递给您的服务

这意味着您不必一直查询数据库来获取所需的数据。声明将在代码中指定的 30 分钟间隔后自动更新。

希望对您有所帮助。