簡體   English   中英

用戶(IPrincipal)在ApiController的構造函數上不可用,使用Web Api 2.1和Owin

[英]User (IPrincipal) not available on ApiController's constructor using Web Api 2.1 and Owin

我正在使用帶有Asp.Net Identity 2的Web Api 2.1。我試圖在我的ApiController的構造函數上獲取經過身份驗證的用戶(我使用AutoFac來注入我的依賴項),但是當調用構造函數時,User顯示為未經過身份驗證。

我試圖獲取用戶,以便我可以為任何數據庫寫操作生成審計信息。

我正在做的一些事情可以幫助診斷:
使用app.UseOAuthBearerTokens作為Asp.Net Identity 2的身份驗證。這意味着我刪除了在使用Asp.Net創建新的Web Api 2.1項目時默認啟用的app.UseCookieAuthentication(new CookieAuthenticationOptions())身份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方法,但它仍然無法使用,給我的圖像中描述的內容相同。

問題確實在於config.SuppressDefaultHostAuthentication()

Brock Allen撰寫的這篇文章很好地解釋了為什么會這樣。 該方法故意將主體設置為null,以便像cookie這樣的默認身份驗證不起作用。 而是, Web API身份驗證篩選器負責身份驗證部分。

如果您沒有cookie身份驗證,則可以選擇刪除此配置。

這里提到的一個簡潔的解決方案是范圍應用程序的Web API部分,以便您可以僅將此配置分離到特定路徑:

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

不知道這是否仍然相關,但我有完全相同的問題,如上所述。 我設法使用自定義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,我在Useup.Auth.cs中的Bearer token的Use *語句后面放了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<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類型的輸入參數)的地方調用此方法。

這樣,無論用戶如何通過身份驗證(通過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()允許我更早地在構造函數中獲取Identity。 但是,如果您使用令牌身份驗證,我不建議這樣做。

Thread.CurrentPrincipical在整個管道中都可用,您可以跳過下面的用戶注冊:

valueRepository.SetUser(User);

和訪問

Thread.CurrentPrincipical

而是在存儲庫中,使存儲庫上下文可識別。 此外,您可以添加上下文層。

如果上述解決方案沒有任何工作,請試試這個:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM