Asp.net 在mvc6中使用带有cookie auth的简单注入器
我有一个MVC6项目,它使用简单的注入器和cookie中间件进行身份验证,而不使用ASP.NET身份验证(下面的教程) 我有一个自定义的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
登录管理器
/用户管理器
,它包装了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>());