Asp.net 在mvc6中使用带有cookie auth的简单注入器

Asp.net 在mvc6中使用带有cookie auth的简单注入器,asp.net,authentication,cookies,asp.net-core-mvc,simple-injector,Asp.net,Authentication,Cookies,Asp.net Core Mvc,Simple Injector,我有一个MVC6项目,它使用简单的注入器和cookie中间件进行身份验证,而不使用ASP.NET身份验证(下面的教程) 我有一个自定义的登录管理器/用户管理器,它包装了PrincipalContext,以验证windows凭据(旁注:我不会将Azure AD与aspnet 5一起使用,因为[将来]我知道将混合使用windows和非windows用户名。此外,我无法在足够的时间内获得这样做的权限)。我最初的问题是将IHttpContextAccessor注入SignInManager和Cooki

我有一个MVC6项目,它使用简单的注入器和cookie中间件进行身份验证,而不使用ASP.NET身份验证(下面的教程)

我有一个自定义的
登录管理器
/
用户管理器
,它包装了
PrincipalContext
,以验证windows凭据(旁注:我不会将Azure AD与aspnet 5一起使用,因为[将来]我知道将混合使用windows和非windows用户名。此外,我无法在足够的时间内获得这样做的权限)。我最初的问题是将
IHttpContextAccessor
注入
SignInManager
CookieAuthenticationOptions
两个类中。我一直收到以下错误:

未配置任何身份验证处理程序来处理方案:ThisCompany.Identity

为了解决我的问题,我必须从asp.net服务获取
IHttpContextAccessor
,然后用simple injector注册它。这是可行的,但似乎是错误的,也许还有别的办法。那么,这是错的吗?如果是这样的话,我希望其他人也尝试过这一点,如果存在的话,可以提出另一种解决方案。以下是我的课程的缩写版本:

  public class Startup
  {
    public static IConfigurationRoot Configuration;
    private readonly Container container = new Container();
    private readonly AppSettings settings;
    private readonly CookieAuthenticationOptions cookieOptions;

    public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
    {
      // config builder here...

      cookieOptions = createCookieOptions();
    }

    public void ConfigureServices(IServiceCollection services)
    {
      // other stuff here...

      services.AddInstance<IControllerActivator>(new SimpleInjectorControllerActivator(container));
      services.AddInstance<IViewComponentInvokerFactory>(new SimpleInjectorViewComponentInvokerFactory(container));
      services.Add(ServiceDescriptor.Instance<IHttpContextAccessor>(new NeverNullHttpContextAccessor()));
    }

    public async void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env)
    {
      app.UseCookieAuthentication(cookieOptions);

      #region DI

      container.Options.DefaultScopedLifestyle = new AspNetRequestLifestyle();
      container.Options.LifestyleSelectionBehavior = new ScopeLifestyleSelectionBehavior();
      app.UseSimpleInjectorAspNetRequestScoping(container);
      InitializeContainer(app);

      // this is the part I am unsure about
      var accessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();                    
      container.Register(() => accessor, Lifestyle.Scoped);

      container.RegisterAspNetControllers(app);
      container.RegisterAspNetViewComponents(app);
      container.Verify();

      #endregion

      using (var scope = SimpleInjectorExecutionContextScopeExtensions.BeginExecutionContextScope(container))
      {
        // seed cache and dummy data
      }
    }

    private void InitializeContainer(IApplicationBuilder app)
    {
      var conn = new SqlConnection(Configuration["Data:AppMainConnection"]);

      // bunch of registrations...

      container.RegisterSingleton(() => cookieOptions);
    }

    private sealed class NeverNullHttpContextAccessor : IHttpContextAccessor
    {
      private readonly AsyncLocal<HttpContext> context = new AsyncLocal<HttpContext>();

      public HttpContext HttpContext
      {
        get { return context.Value ?? new DefaultHttpContext(); }
        set { context.Value = value; }
      }
    }

    private sealed class ScopeLifestyleSelectionBehavior : ILifestyleSelectionBehavior
    {
      public Lifestyle SelectLifestyle(Type serviceType, Type implementationType)
      {
        return Lifestyle.Scoped;
      }
    }
    private CookieAuthenticationOptions createCookieOptions()
    {
      return new CookieAuthenticationOptions()
      {
        AuthenticationScheme = "ThisCompany.Identity",
        AutomaticChallenge = true,
        AutomaticAuthenticate = true,
        LoginPath = new PathString("/Auth/Login/"),
        LogoutPath = new PathString("/Auth/Logout"),
        AccessDeniedPath = new PathString("/Auth/Forbidden/"), // TODO
        CookieName = "yumyum.net",
        SlidingExpiration = true,
        ExpireTimeSpan = TimeSpan.FromDays(1),
        Events = new CookieAuthenticationEvents()
        {
          OnRedirectToAccessDenied = ctx =>
          {
            if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
            {
              ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            }
            else
            {
              ctx.Response.Redirect(ctx.RedirectUri);
            }

            return Task.FromResult(0);
          }
        }
      };
    }
更新

下面是我添加
container.CrossWire(app)时的详细信息并删除

  var accessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();                    
  container.Register(() => accessor, Lifestyle.Scoped);
更新

如果这不正确,我相信我会被纠正,但我选择了@Steven的答案作为适配器。我想这更像是一堂我不太熟悉的设计模式课。下面是我的新类和注册,我将在自定义SignInManager中使用它们:

  public class DefaultAuthenticationManager : IAuthenticationManager
  {
    private readonly HttpContext context;

    public DefaultAuthenticationManager(IHttpContextAccessor accessor)
    {
      if (accessor == null || accessor.HttpContext == null) throw new ArgumentNullException(nameof(accessor));

      context = accessor.HttpContext;
    }

    public Task SignInAsync(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties)
    {
      return context.Authentication.SignInAsync(authenticationScheme, principal, properties);
    }

    public Task SignOutAsync(string authenticationScheme)
    {
     return  context.Authentication.SignOutAsync(authenticationScheme);
    }
  }


private void InitializeContainer(IApplicationBuilder app)
{
     var accessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();

     container.Register<IAuthenticationManager>(() => new DefaultAuthenticationManager(accessor), Lifestyle.Scoped);
}
公共类DefaultAuthenticationManager:IAAuthenticationManager
{
私有只读HttpContext上下文;
public DefaultAuthenticationManager(IHttpContextAccessor访问器)
{
if(accessor==null | | accessor.HttpContext==null)抛出新的ArgumentNullException(nameof(accessor));
context=accessor.HttpContext;
}
公共任务签名同步(字符串authenticationScheme、ClaimsPrincipal主体、AuthenticationProperties)
{
返回context.Authentication.SignInAsync(authenticationScheme、主体、属性);
}
公共任务SignOutAsync(字符串身份验证方案)
{
返回context.Authentication.SignOutAsync(authenticationScheme);
}
}
私有void InitializeContainer(IApplicationBuilder应用程序)
{
var accessor=app.ApplicationServices.GetRequiredService();
container.Register(()=>新的DefaultAuthenticationManager(访问器),lifesture.Scoped);
}

为了向simpleinjector注册
识别选项
IDataProtectionProvider
,我不得不做一些类似的事情,以使一些身份验证工作正常进行。我不认为这是“错误的”,但我可能是,我相信史蒂文会与他的权威观点相呼应,让我们都走上正确的道路


一个微小的区别是,我没有为我的
InitializeContainer
方法提供
IAApplicationBuilder
实例,只有
IServiceProvider
(也可以通过
IAApplicationBuilder.ApplicationServices
属性获得)。您真的需要整个
iaapplicationbuilder
来初始化容器吗?

我必须做一些类似的事情,以便使用simpleinjector注册
IdentityOptions
IDataProtectionProvider
,以实现一些身份验证功能。我不认为这是“错误的”,但我可能是,我相信史蒂文会与他的权威观点相呼应,让我们都走上正确的道路

一个微小的区别是,我没有为我的
InitializeContainer
方法提供
IAApplicationBuilder
实例,只有
IServiceProvider
(也可以通过
IAApplicationBuilder.ApplicationServices
属性获得)。您真的需要整个
iaapplicationbuilder
来初始化容器吗?

更新

在这种情况下,您应该能够抑制诊断警告(很抱歉,今晚我无法使代码工作到足以确认它)

更新

在这种情况下,您应该能够抑制诊断警告(很抱歉,今晚我无法使代码工作到足以确认它)


集成包中的
CrossWire
扩展方法在Simple Injector中进行代理注册,允许Simple Injector“了解”服务,而ASP.NET 5配置系统仍在控制构建该服务。您也可以按如下方式进行操作:

container.Register(() => app.ApplicationServices.GetRequiredService<ISomeService>());
第三个选项是完全阻止注册此IHttpContextAccessor。它本身已经违反了应用程序代码的依赖项反转原则。这是DIP冲突,因为IHttpContextAccessor不是由应用程序定义的,而是由框架定义的。因此,它永远不会以完全适合您的应用程序需要的方式定义。应用程序代码几乎不需要获取HttpContext对象。相反,它感兴趣的是一些特定的值,如UserId、TenantId或其他上下文值。因此,当应用程序依赖于IUserContext、ITenantContext或其他特定抽象时,它会更好。是否从HttpContext提取该值是一个实现细节

这种实现(适配器)可以在运行时解析IHttpContextAccessor并从中获取HttpContext。当然,这种适配器的实现在大多数情况下都非常简单,但这很好;我们的目标只是保护应用程序不受这些知识的影响。由于适配器了解ASP.NET抽象,因此可以从it配置中解析服务。适配器只是一个反损坏层

这些基本上是您的选择。

交叉线
  public class DefaultAuthenticationManager : IAuthenticationManager
  {
    private readonly HttpContext context;

    public DefaultAuthenticationManager(IHttpContextAccessor accessor)
    {
      if (accessor == null || accessor.HttpContext == null) throw new ArgumentNullException(nameof(accessor));

      context = accessor.HttpContext;
    }

    public Task SignInAsync(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties)
    {
      return context.Authentication.SignInAsync(authenticationScheme, principal, properties);
    }

    public Task SignOutAsync(string authenticationScheme)
    {
     return  context.Authentication.SignOutAsync(authenticationScheme);
    }
  }


private void InitializeContainer(IApplicationBuilder app)
{
     var accessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();

     container.Register<IAuthenticationManager>(() => new DefaultAuthenticationManager(accessor), Lifestyle.Scoped);
}
container.CrossWire<IHttpContextAccessor>(app);
var registration = container.GetRegistration(
    typeof(IHttpContextAccessor)).Registration;
registration.SuppressDiagnosticWarning(
    DiagnosticType.LifestyleMismatch, "Owned by ASP.NET");
container.CrossWire<IHttpContextAccessor>(app);
container.Register(() => app.ApplicationServices.GetRequiredService<ISomeService>());
container.RegisterSingleton(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());