对于基本身份验证,我已根据Darin Dimitrov在此处的回答中显示的示例实现了自定义HttpMessageHandler
:https://stackoverflow.com/a/11536349/270591
代码使用用户名和角色创建类型为principal
的实例GenericPrincipal
,然后将此主体设置为线程的当前主体:
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
//...
}
}
这似乎工作正常,直到我最近添加了一个使用MediaTypeFormatter
库的自定义Task
,如下所示:
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;
}
(我使用这种方法从Task.Factory.StartNew
开始使用ReadFromStreamAsync
从一些示例代码中启动任务。这是错误的,也许是问题的唯一原因吗?)
现在,“有时” - 对我而言似乎是随机的 - 控制器方法中的User
主体不再是我在MessageHandler中设置的主体,即用户名{{1标志和角色都丢失了。原因似乎是自定义MediaTypeFormatter导致MessageHandler和控制器方法之间的线程发生更改。我通过比较MessageHandler和控制器方法中Authenticated
的值来证实了这一点。 “有时”他们是不同的,然后校长“迷失”。
我现在寻找另一种方法来设置Thread.CurrentThread.ManagedThreadId
以某种方式将主体安全地从自定义MessageHandler转移到控制器方法,并使用this blog post请求属性:
Thread.CurrentPrincipal
我想测试一下,但似乎request.Properties.Add(HttpPropertyKeys.UserPrincipalKey,
new GenericPrincipal(identity, new string[0]));
类(在名称空间HttpPropertyKeys
中)在最近的WebApi版本中不再具有System.Web.Http.Hosting
属性(候选版本)上周也是最后一次发布。)
我的问题是:如何更改上面的最后一个代码段,以便与当前的WebAPI版本一起使用?或者通常:如何在自定义MessageHandler中设置用户主体并在控制器方法中可靠地访问它?
修改
提到here“UserPrincipalKey
...已解析为HttpPropertyKeys.UserPrincipalKey
”,因此我尝试使用:
“MS_UserPrincipal”
但它不能按我的预期工作:request.Properties.Add("MS_UserPrincipal",
new GenericPrincipal(identity, new string[0]));
属性不包含添加到上面ApiController.User
集合的主体。
答案 0 :(得分:75)
这里提到了在新线程上丢失主体的问题:
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中托管时,可能会覆盖Thread.CurrentPrincipal 在创建新线程时使用HttpContext.Current.User。这意味着 你必须在线程和HTTP上下文中设置主体。
在这里:http://aspnetwebstack.codeplex.com/workitem/264
今天,您需要为用户主体设置以下两项 如果您使用自定义消息处理程序来执行身份验证 网络托管方案。
IPrincipal principal = new GenericPrincipal( new GenericIdentity("myuser"), new string[] { "myrole" }); Thread.CurrentPrincipal = principal; HttpContext.Current.User = principal;
我已将最后一行HttpContext.Current.User = principal
(需要using System.Web;
)添加到消息处理程序中,User
中的ApiController
属性现在始终具有正确的主体,甚至如果线程由于MediaTypeFormatter中的任务而发生了变化。
修改强>
只是强调它:只有当WebApi托管在ASP.NET / IIS中时,才需要设置当前用户的HttpContext
主体。对于自托管,没有必要(并且不可能,因为HttpContext
是ASP.NET构造,并且在自托管时不存在。)
答案 1 :(得分:5)
要避免上下文切换,请尝试使用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;
}
答案 2 :(得分:5)
使用自定义MessageHandler,您可以通过调用MS_UserPrincipal
中定义的HttpRequestMessageExtensionMethods.SetUserPrincipal
扩展方法添加System.ServiceModel.Channels
属性:
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的User。