用户(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; ... }
- Authorize和GetRoles在ASP.NET标识中不起作用
- 我应该将我的应用程序上下文与用于标识的ApplicationDbContext分开吗?
- ASP Identity 2.0:重新生成身份
- AspNetUsers(身份)与自定义表之间的一对多关系
- 在MVC 5 Dot Net Identity中扩展IdentityUserRole后,UserManager.GetRoles不起作用
- entity framework6和Asp.Net Identity UserManager:多个DbContext
- Asp.Net Identity和多种登录机制
- 使用Simple Injector注册IAuthenticationManager
- 多站点ASP.NET身份2的中间件