用户(IPrincipal)在ApiController的构造函数上不可用,使用Web Api 2.1和Owin

我正在使用带有Asp.Net Identity 2的Web Api 2.1。我试图在我的ApiController的构造函数上获取经过身份validation的用户(我使用AutoFac来注入我的依赖项),但是当调用构造函数时,User显示为未经过身份validation。

我正在尝试获取用户,以便我可以为任何数据库写操作生成审核信息。

我正在做的一些事情可以帮助诊断:
使用app.UseOAuthBearerTokens作为Asp.Net Identity 2的身份validation。这意味着我删除了在使用Asp.Net创建新的Web Api 2.1项目时默认启用的app.UseCookieAuthentication(new CookieAuthenticationOptions())身份2。

WebApiConfig里面我正在注入我的存储库:

 builder.RegisterType().As().InstancePerRequest(); 

这是我的控制器:

 [RoutePrefix("api/values")] public class ValuesController : ApiController { private IValueRepository valueRepository; public ValuesController(IValueRepository repo) { valueRepository = repo; // I would need the User information here to pass it to my repository // something like this: valueRepository.SetUser(User); } protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext) { base.Initialize(controllerContext); // User is not avaliable here either... } } 

但是,如果我检查构造函数上的User对象,这就是我得到的: 用户

validation工作正常,如果我没有通过我的令牌,它将以Unauthorized响应。 如果我传递令牌并尝试从任何方法访问用户,则会对其进行身份validation并正确填充。 它只是在调用时没有显示在构造函数上。

在我的WebApiConfig我正在使用:

 public static void Register(HttpConfiguration config) { config.SuppressDefaultHostAuthentication(); config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // ... other unrelated injections using AutoFac } 

我注意到如果我删除这一行: config.SuppressDefaultHostAuthentication()用户将填充在构造函数上。

这是预期的吗? 如何在构造函数上获取经过身份validation的用户?

编辑:正如Rikard建议我试图让用户使用Initialize方法,但它仍然不可用,给我与图像中描述的相同的东西。

不知道这是否仍然相关,但我遇到了完全相同的问题,如上所述。 我已经设法使用自定义OWIN中间件组件解决它。

有关我的应用程序结构的一些信

  • 在同一个项目中使用MVC WebApp和WebAPI(可能不是最佳选择,但我没有时间更改它,因为截止日期临近;))
  • 使用AutoFac作为IoC容器
  • 实现自定义ICurrentContext以保存有关当前登录用户的信息(使用MVC中的CookieAuth和WebAPI中的Bearer Token Auth),在需要时注入(控制器,BAL对象等)
  • 使用EntityFramework 6进行Db访问
  • 转换的ASP.NET标识使用int键而不是字符串( http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity

所以关于代码。 这是我的ICurrentContext接口:

 public interface ICurrentContext { User CurrentUser { get; set; } // User is my User class which holds some user properties int? CurrentUserId { get; } } 

和实施:

 public class DefaultCurrentContext : ICurrentContext { public User CurrentUser { get; set; } public int? CurrentUserId { get { return User != null ? CurrentUser.Id : (int?)null; } } } 

我还创建了一个OWIN中间件组件:

 using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Microsoft.Owin; namespace MyWebApp.Web.AppCode.MiddlewareOwin { public class WebApiAuthInfoMiddleware : OwinMiddleware { public WebApiAuthInfoMiddleware(OwinMiddleware next) : base(next) { } public override Task Invoke(IOwinContext context) { var userId = context.Request.User.Identity.GetUserId(); context.Environment[MyWebApp.Constants.Constant.WebApiCurrentUserId] = userId; return Next.Invoke(context); } } } 

有关此组件的一些信息:MyWebApp.Constants.Constant.WebApiCurrentUserId是一些字符串常量(您可以使用自己的),我曾经用它来避免拼写错误,因为它在多个地方使用。 基本上这个中间件的作用是,它将当前的UserId添加到OWIN环境字典中,然后调用管道中的下一个动作。

然后我创建了Use *扩展语句,将OMC(OWIN中间件组件)包含到OWIN管道中:

 using System; using Owin; namespace MyWebApp.Web.AppCode.MiddlewareOwin { public static class OwinAppBuilderExtensions { public static IAppBuilder UseWebApiAuthInfo(this IAppBuilder @this) { if (@this == null) { throw new ArgumentNullException("app"); } @this.Use(typeof(WebApiAuthInfoMiddleware)); return @this; } } } 

为了使用这个OMC,我将Use *语句放在Startup.Auth.cs中的Bearer token的Use *语句之后:

 // Enable the application to use bearer tokens to authenticate users app.UseOAuthBearerTokens(OAuthOptions); // This was here before // Register AuthInfo to retrieve UserId before executing of Api controllers app.UseWebApiAuthInfo(); // Use newly created OMC 

现在,这个原则的实际用法是在AutoFac的Register方法中(在Web应用程序启动时调用了一些引导代码;在我的情况下,这是在我的ICurrentContext实现的Startup类(Startup.cs),Configuration方法中),它是:

 private static void RegisterCurrentContext(ContainerBuilder builder) { // Register current context builder.Register(c => { // Try to get User's Id first from Identity of HttpContext.Current var appUserId = HttpContext.Current.User.Identity.GetUserId(); // If appUserId is still zero, try to get it from Owin.Enviroment where WebApiAuthInfo middleware components puts it. if (appUserId <= 0) { object appUserIdObj; var env = HttpContext.Current.GetOwinContext().Environment; if (env.TryGetValue(MyWebApp.Constants.Constant.WebApiCurrentUserId, out appUserIdObj)) { appUserId = (int)appUserIdObj; } } // WORK: Read user from database based on appUserId and create appUser object. return new DefaultCurrentContext { CurrentUser = appUser, }; }).As().InstancePerLifetimeScope(); } 

在我构建AutoFac的容器(因此是ContainerBuilder类型的输入参数)的地方调用此方法。

这样,无论用户如何通过身份validation(通过MVC Web应用程序或Web API),我都可以单独实现CurrentContext。 在我的案例中,Web API调用是从某些桌面应用程序进行的,但数据库和大多数代码库对于MVC App和Web API都是相同的。

不知道它是否是正确的方法,但它对我有用。 虽然我仍然有点担心这会如何表现线程,因为我不确切知道如何在API调用中使用HttpContext.Current。 我已经读过某个地方,OWIN字典是按请求使用的,所以我认为这是安全的方法。 而且我也认为这不是那么简洁的代码,而是一个讨厌UserId的讨厌的黑客。 ;)如果使用此approcah有任何问题,我将不胜感激任何评论。 我已经用这两个星期了,这是我最接近将UserId放在一个地方(当从AutoFac通过lambda解析ICurrentContext时)。

注意:只要有GetUserId的使用,就可以用原始的GetUserId(返回字符串)实现替换它。 我使用GetUserId的原因是因为我在某种程度上重写了ASP.NET,因为使用int而不是TKey的字符串。 我是根据以下文章完成的: http : //www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity

在调用构造函数之后调用Initialize方法之前,不会填充控制器的User属性,这就是为什么尚未使用授权用户数据填充Identity的原因。

问题确实在于config.SuppressDefaultHostAuthentication()

Brock Allen的这篇文章很好地解释了为什么会这样。 该方法故意将主体设置为null,以便像cookie这样的默认身份validation不起作用。 而是, Web API身份validation筛选器负责身份validation部分。

如果您没有cookie身份validation,则可以选择删除此配置。

这里提到的一个简洁的解决方案是范围应用程序的Web API部分,以便您可以仅将此配置分离到特定路径:

 public void Configuration(IAppBuilder app) { var configuration = WebApiConfiguration.HttpConfiguration; app.Map("/api", inner => { inner.SuppressDefaultHostAuthentication(); // ... inner.UseWebApi(configuration); }); } 

我意识到删除config.SuppressDefaultHostAuthentication()允许我更早地在构造函数中获取Identity。 但是,如果您使用令牌身份validation,我不建议这样做。

Thread.CurrentPrincipical在整个管道中都可用,您可以跳过下面的用户注册:

 valueRepository.SetUser(User); 

和访问

 Thread.CurrentPrincipical 

而是在存储库中,使存储库上下文可识别。 此外,您可以添加上下文层。

如果上述解决方案没有任何效果,请试试这个:

  public ActionResult GetFiles() { ... string domainID = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name; ... }