用户(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 的构造函数中不可用的主要内容,如果未能解决你的问题,请参考以下文章