我怎样才能安全地在自定义的WebAPI HttpMessageHandler中设置用户主体?

对于基本身份validation,我已经实现了一个自定义的HttpMessageHandler基于Darin Dimitrov的答案中显示的示例: https : HttpMessageHandler

该代码创build一个具有用户名和angular色的GenericPrincipaltypes的实例principal ,然后将此主体设置为该线程的当前主体:

 Thread.CurrentPrincipal = principal; 

稍后在ApiController方法中,可以通过访问控制器的User属性来读取委托人:

 public class ValuesController : ApiController { public void Post(TestModel model) { var user = User; // this should be the principal set in the handler //... } } 

这似乎工作正常,直到我最近添加一个使用Task库的自定义MediaTypeFormatter ,如下所示:

 public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) { var task = Task.Factory.StartNew(() => { // some formatting happens and finally a TestModel is returned, // simulated here by just an empty model return (object)new TestModel(); }); return task; } 

(我有这种方法从一些示例代码中的ReadFromStreamAsync Task.Factory.StartNew开始任务,这是错误的,也许是唯一的原因?

现在,“有时” – 对我来说,它似乎是随机的 – 控制器方法中的User主体不再是我在MessageHandler中设置的主体,即用户名, Authenticated标志和angular色都丢失了。 原因似乎是自定义MediaTypeFormatter导致MessageHandler和控制器方法之间的线程更改。 我已经通过比较MessageHandler和控制器方法中的Thread.CurrentThread.ManagedThreadId的值来确认这一点。 “有时”他们是不同的,然后校长是“失去”。

我已经看了一个替代设置Thread.CurrentPrincipal以某种方式安全地从自定义MessageHandler转移到控制器方法的主体,并在此博客文章中使用请求属性:

 request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, new string[0])); 

我想testing,但似乎HttpPropertyKeys类(这是在命名空间System.Web.Http.Hosting )没有一个UserPrincipalKey属性在最近的WebApi版本(发布候选人和上周最后发布以及) 。

我的问题是:如何更改上面的最后一个代码片段,以便与当前WebAPI版本一起使用? 或者一般来说:我如何在自定义MessageHandler中设置用户主体,并在控制器方法中可靠地访问?

编辑

这里提到“ HttpPropertyKeys.UserPrincipalKey …parsing为“MS_UserPrincipal” ”,所以我试图使用:

 request.Properties.Add("MS_UserPrincipal", new GenericPrincipal(identity, new string[0])); 

但它不能正常工作: ApiController.User属性不包含添加到上面Properties集合中的主体。

这里提到了在新线程上失去委托人的问题:

http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/

重要:在ASP.NET Web API中设置客户端主体

由于深陷在ASP.NET中的一些不幸的机制,在Web API Web主机中设置Thread.CurrentPrincipal是不够的。

在ASP.NET中托pipe时,创build新线程时,Thread.CurrentPrincipal可能会被HttpContext.Current.User重写。 这意味着你必须在线程和HTTP上下文中设置主体。

在这里: http : //aspnetwebstack.codeplex.com/workitem/264

今天,如果您使用自定义消息处理程序在Web托pipescheme中执行身份validation,则需要为用户主体设置以下两个选项。

 IPrincipal principal = new GenericPrincipal( new GenericIdentity("myuser"), new string[] { "myrole" }); Thread.CurrentPrincipal = principal; HttpContext.Current.User = principal; 

我已经将最后一行HttpContext.Current.User = principal (需要using System.Web; )添加到消息处理程序中,并且ApiController中的User属性现在始终具有正确的主体,即使线程由于任务在MediaTypeFormatter中。

编辑

只是为了强调:只有当WebApi被托pipe在ASP.NET / IIS中时,才需要设置当前用户的HttpContext的主体。 对于自托pipe,这是没有必要的(而且不可能,因为HttpContext是一个ASP.NET构造,并且在自托pipe时不存在)。

要避免上下文切换,请尝试使用TaskCompletionSource<object>而不是在自定义MediaTypeFormatter中手动启动另一个任务:

 public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) { var tcs = new TaskCompletionSource<object>(); // some formatting happens and finally a TestModel is returned, // simulated here by just an empty model var testModel = new TestModel(); tcs.SetResult(testModel); return tcs.Task; } 

使用自定义MessageHandler,可以通过调用System.ServiceModel.Channels定义的HttpRequestMessageExtensionMethods.SetUserPrincipal扩展方法来添加MS_UserPrincipal属性:

 protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { var user = new GenericPrincipal(new GenericIdentity("UserID"), null); request.SetUserPrincipal(user); return base.SendAsync(request, cancellationToken); } 

请注意,这只会将此属性添加到R​​equest的Properties集合,它不会更改附加到ApiController的用户。