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

Sla*_*uma 50 .net asp.net security asp.net-mvc asp.net-web-api

对于基本身份验证,我已经实现了一个HttpMessageHandler基于Darin Dimitrov在这里回答的示例的自定义:https://stackoverflow.com/a/11536349/270591

代码创建具有用户名和角色principal的类型实例,GenericPrincipal然后将此主体设置为线程的当前主体:

Thread.CurrentPrincipal = principal;
Run Code Online (Sandbox Code Playgroud)

稍后在ApiController方法中,可以通过访问controllers User属性来读取主体:

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

这似乎工作正常,直到我最近添加了一个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;
}
Run Code Online (Sandbox Code Playgroud)

(我有这种方法来启动一项任务Task.Factory.StartNewReadFromStreamAsync一些示例代码.它是错误的,也许对这个问题的唯一原因吗?)

现在,"有时" - 对我而言似乎是随机的 - User控制器方法中的主体不再是我在MessageHandler中设置的主体,即用户名,Authenticated标志和角色都丢失了.原因似乎是自定义MediaTypeFormatter导致MessageHandler和控制器方法之间的线程发生了变化.我已经通过比较Thread.CurrentThread.ManagedThreadIdMessageHandler和控制器方法中的值来证实了这一点."有时"他们是不同的,然后校长"迷失".

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

request.Properties.Add(HttpPropertyKeys.UserPrincipalKey,
    new GenericPrincipal(identity, new string[0]));
Run Code Online (Sandbox Code Playgroud)

我想测试一下,但似乎HttpPropertyKeys该类(在命名空间中System.Web.Http.Hosting)UserPrincipalKey在最近的WebApi版本中不再具有属性(发布候选版本和上周的最终版本).

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

编辑

它提到在这里说," HttpPropertyKeys.UserPrincipalKey......解析为“MS_UserPrincipal”",所以我试图用:

request.Properties.Add("MS_UserPrincipal",
    new GenericPrincipal(identity, new string[0]));
Run Code Online (Sandbox Code Playgroud)

但它不能像我预期的那样工作:该ApiController.User属性不包含添加到Properties上面集合中的主体.

Sla*_*uma 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中托管时,在创建新线程时,可能会使用HttpContext.Current.User覆盖Thread.CurrentPrincipal.这意味着您必须在线程和HTTP上下文中设置主体.

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

今天,如果使用自定义消息处理程序在Web托管方案中执行身份验证,则需要为用户主体设置以下两项.

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

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

编辑

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


Dar*_*rov 5

要避免上下文切换,请尝试使用a 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 它有效,但我有点担心必须小心使用任务和线程来保持安全功能完好无损.我找到了另一个解决方案,请参阅我自己的答案. (2认同)

gmo*_*979 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);
}
Run Code Online (Sandbox Code Playgroud)

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