如何在自定义Web API HttpMessageHandler中安全地设置用户主体?

时间:2012-08-19 17:43:47

标签: asp.net .net asp.net-mvc security asp.net-web-api

对于基本身份验证,我已根据Darin Dimitrov在此处的回答中显示的示例实现了自定义HttpMessageHandlerhttps://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中设置用户主体并在控制器方法中可靠地访问它?

修改

提到hereUserPrincipalKey ...已解析为HttpPropertyKeys.UserPrincipalKey”,因此我尝试使用:

“MS_UserPrincipal”

但它不能按我的预期工作:request.Properties.Add("MS_UserPrincipal", new GenericPrincipal(identity, new string[0])); 属性不包含添加到上面ApiController.User集合的主体。

3 个答案:

答案 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);
}

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