12

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

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

我正在做的一些事情可以帮助诊断:
app.UseOAuthBearerTokens使用 Asp.Net Identity 2 作为身份验证。这意味着app.UseCookieAuthentication(new CookieAuthenticationOptions())当您使用 Asp 创建新的 Web Api 2.1 项目时,我删除了默认启用的.Net 身份 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()User 被填充在构造函数中。

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

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

4

6 回答 6

5

问题确实出在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);
    });
}
于 2017-03-07T14:03:13.443 回答
4

不知道这是否仍然相关,但正如您在上面描述的那样,我遇到了完全相同的问题。我已经设法使用自定义 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

于 2015-06-14T10:18:03.503 回答
3

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

于 2014-05-22T16:45:07.593 回答
1

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

于 2014-08-01T21:47:44.337 回答
1

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

valueRepository.SetUser(User);

和访问

Thread.CurrentPrincipical

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

于 2016-02-12T08:56:54.923 回答
0

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

    public ActionResult GetFiles()
    {
        ...
        string domainID = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name;
        ...
    }
于 2017-09-28T17:12:45.570 回答