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

Posted

技术标签:

【中文标题】用户(IPrincipal)在使用 Web Api 2.1 和 Owin 的 ApiController 的构造函数中不可用【英文标题】:User (IPrincipal) not available on ApiController's constructor using Web Api 2.1 and Owin 【发布时间】:2014-05-21 14:02:37 【问题描述】:

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

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

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

WebApiConfig 内部,我正在注入我的存储库:

builder.RegisterType<ValueRepository>().As<IValueRepository>().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 对象,我会得到:

身份验证正在运行,如果我没有通过我的令牌,它将以Unauthorized 响应。如果我传递令牌并尝试从任何方法访问用户,它就会被正确地验证和填充。它只是在调用时不会显示在构造函数中。

在我的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(),构造函数中会填充用户。

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

编辑: 正如 Rikard 建议的那样,我尝试让用户使用 Initialize 方法,但它仍然不可用,给了我图片中描述的相同内容。

【问题讨论】:

非常相似的问题here。原来是中间件注册的顺序,尽管目前答案仍在进行中。 【参考方案1】:

问题确实出在config.SuppressDefaultHostAuthentication()

This article by Brock Allen 很好地解释了为什么会这样。该方法有意将主体设置为 null,以便像 cookie 这样的默认身份验证不起作用。相反,Web API Authentication filter 然后负责身份验证部分。

可以选择在您没有 cookie 身份验证时删除此配置。

一个巧妙的解决方案as mentioned here,是限定应用程序的 Web API 部分,以便您可以将此配置仅分离到特定路径:

public void Configuration(IAppBuilder app)

    var configuration = WebApiConfiguration.HttpConfiguration;
    app.Map("/api", inner =>
    
        inner.SuppressDefaultHostAuthentication();
        // ...
        inner.UseWebApi(configuration);
    );

【讨论】:

我花了几个小时来研究为什么我的 ClaimsPrincipal.Current.Claims 是空的。然后我删除了 config.SuppressDefaultHostAuthentication()。阅读此答案后。现在我可以访问索赔。你救了我的命。谢谢!。【参考方案2】:

不知道这是否仍然相关,但我遇到了与您在上面描述的完全相同的问题。我已经设法使用自定义 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<int>();
            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,我在我的 Startup.Auth.cs 中将 Use* 语句放在了 Bearer 令牌的 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 应用程序启动时在一些引导代码上调用;在我的情况下,这是在 Startup 类 (Startup.cs) 中,Configuration 方法中)用于我的 ICurrentContext 实现是:

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<int>();

        // 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<ICurrentContext>().InstancePerLifetimeScope();

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

通过这种方式,我得到了 CurrentContext 的单一实现,无论用户如何进行身份验证(通过 MVC Web 应用程序或 Web API)。在我的例子中,Web API 调用是从一些桌面应用程序进行的,但是 MVC 应用程序和 Web API 的数据库和大部分代码库是相同的。

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

注意:无论在何处使用 GetUserId,都可以将其替换为原始 GetUserId(返回字符串)实现。我使用 GetUserId 的原因是因为我在某种程度上重写了 ASP.NET,以便在 TKey 中使用整数而不是字符串。我是根据以下文章完成的:http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity

【讨论】:

【参考方案3】:

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

【讨论】:

感谢您的回复 Rikard。我实际上尝试过这个,但我仍然无法在 Initialize 方法中访问用户数据。 (我正在使用该附加信息编辑原始问题)。 好吧,也许这个asp.net/aspnet/overview/owin-and-katana/…可以帮到你。【参考方案4】:

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

【讨论】:

没错。经过更多研究后,我认为我正在尝试做的事情使用过滤器更有意义。【参考方案5】:

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

valueRepository.SetUser(User);

和访问

Thread.CurrentPrincipical

改为在存储库中,使存储库上下文感知。此外,您可以添加上下文层。

【讨论】:

【参考方案6】:

如果上述解决方案均无效,请尝试以下解决方案:

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

【讨论】:

以上是关于用户(IPrincipal)在使用 Web Api 2.1 和 Owin 的 ApiController 的构造函数中不可用的主要内容,如果未能解决你的问题,请参考以下文章

如何通过依赖注入传递 Web API 请求?

来自 WCF 请求的 IPrincipal

在 ASP.NET Core 中模拟 IPrincipal

如何使用 Web API 来对 MVC 应用程序进行身份验证

IPrincipalIIdentity介绍

在 MVC3 中使用自定义 IPrincipal 和 IIdentity