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

2019-06-27 03:51发布

对于基本身份验证我实现了自定义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.StartNewReadFromStreamAsync一些示例代码。它是错误的,也许对这个问题的唯一原因吗?)

现在,“有时” -对我来说似乎是随机的-在User在控制器方法主要不是主要的了我在的MessageHandler已经成立,即用户名, Authenticated标志和角色都将丢失。 原因似乎是定制MediaTypeFormatter引起的MessageHandler和控制器之间的方法线程的变化。 我已经通过比较值证实了这一点Thread.CurrentThread.ManagedThreadId中的MessageHandler,并在控制器方法。 “有时候,”他们是不同的,然后本金“丢失”。

我现在已经找了一个替代设置Thread.CurrentPrincipal以某种方式安全地从自定义的MessageHandler转移校长到控制器的方法,并在这个博客帖子请求属性使用:

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

我想测试,但它似乎HttpPropertyKeys类(在命名空间System.Web.Http.Hosting )不具有UserPrincipalKey在最近的WebAPI的版本属性了(上周发布候选版和最终版本以及) 。

我的问题是:如何更改上面的最后一个代码段,这样与当前的WebAPI版本的作品? 或一般:我怎样才能设置用户主要在自定义的MessageHandler和访问的可靠性是控制器的方法?

编辑

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

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

但是,如我所料不工作: ApiController.User属性不包含本金加到Properties集以上。

Answer 1:

失去了一个新的线程的主要的问题是这里所说的:

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

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

由于ASP.NET深埋一些不幸的机制,在网络API虚拟主机设置Thread.CurrentPrincipal中是不够的。

当ASP.NET虚拟主机,可能Thread.CurrentPrincipal中创建新线程时获得覆盖与HttpContext.Current.User。 这意味着你必须设置主线程和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; 

我已在最后一行添加HttpContext.Current.User = principal (需要using System.Web;的消息处理程序和User的财产ApiController不总是正确的校长现在,即使线程已经改变,由于任务在MediaTypeFormatter。

编辑

只是为了强调它:设置当前用户的主要HttpContext时的WebAPI在ASP.NET/IIS托管才是必需的。 对于自托管没有必要(和不可能的,因为HttpContext是ASP.NET结构,当自我托管不存在)。



Answer 2:

为了避免上下文切换尝试使用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;
}


Answer 3:

使用自定义的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);
}

请注意,这只会增加该属性请求的属性集合,它不会改变附着在ApiController用户。



文章来源: How can I safely set the user principal in a custom WebAPI HttpMessageHandler?