我怎样才能安全地在自定义的WebAPI HttpMessageHandler中设置用户主体?
对于基本身份validation,我已经实现了一个自定义的HttpMessageHandler
基于Darin Dimitrov的答案中显示的示例: https : HttpMessageHandler
该代码创build一个具有用户名和angular色的GenericPrincipal
types的实例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); }
请注意,这只会将此属性添加到Request的Properties集合,它不会更改附加到ApiController的用户。