【问题标题】:How can I safely set the user principal in a custom WebAPI HttpMessageHandler?如何在自定义 WebAPI HttpMessageHandler 中安全地设置用户主体?
【发布时间】:2012-08-15 05:58:08
【问题描述】:

对于基本身份验证,我根据 Darin Dimitrov 的回答中显示的示例实现了自定义 HttpMessageHandlerhttps://stackoverflow.com/a/11536349/270591

代码使用用户名和角色创建GenericPrincipal 类型的实例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 标志和角色全部丢失。原因似乎是自定义 MediaTypeFormatter 导致 MessageHandler 和控制器方法之间的线程发生变化。我通过比较 MessageHandler 和控制器方法中的 Thread.CurrentThread.ManagedThreadId 的值来确认这一点。 “有时”它们是不同的,然后主体就“丢失”了。

我现在正在寻找一种替代方法来设置 Thread.CurrentPrincipal 以某种方式将主体从自定义 MessageHandler 安全地传输到控制器方法,并在 this blog post 中使用请求属性:

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

我想对此进行测试,但似乎 HttpPropertyKeys 类(位于命名空间 System.Web.Http.Hosting 中)在最近的 WebApi 版本中不再具有 UserPrincipalKey 属性(候选版本和上周的最终版本)以及)。

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

编辑

提到here,“HttpPropertyKeys.UserPrincipalKey ...解析为“MS_UserPrincipal””,所以我尝试使用:

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

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

【问题讨论】:

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


【解决方案1】:

这里提到了在新线程上丢失本金的问题:

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

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

由于一些不幸的机制深埋在 ASP.NET 中,设置 Web API 网络托管中的 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;)添加到消息处理程序中,并且ApiController 中的User 属性现在始终具有正确的主体,即使线程由于MediaTypeFormatter 中的任务。

编辑

强调一点:仅当 WebApi 托管在 ASP.NET/IIS 中时,才需要设置 HttpContext 的当前用户主体。对于自托管,这不是必需的(也不可能,因为HttpContext 是一个 ASP.NET 结构,自托管时不存在)。

【讨论】:

    【解决方案2】:

    为避免上下文切换,请尝试使用TaskCompletionSource&lt;object&gt;,而不是在您的自定义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;
    }
    

    【讨论】:

    • 它可以工作,但我有点担心必须小心使用任务和线程以保持安全功能完好无损。我找到了另一个解决方案,请在此处查看我自己的答案。
    • @Darin Dimitrov,我已经安装了 vs 2012 的 RTM 和最新的 asp.net web api 位和 HttpPropertyKeys.UserPrincipalKey 给出了语法错误。在对象浏览器中查看类显示此密钥在最终版本中不存在。有什么想法我还能用什么?
    • 为什么要使用HttpPropertyKeys.UserPrincipalKey?您在我的回答中哪里看到使用它?所以不要使用它。
    • @DarinDimitrov,这样做不会破坏异步的显式线程吗? IE。原始代码在池线程上运行任务。难道没有不同的方法来保留上下文吗?
    【解决方案3】:

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

    请注意,这只会将此属性添加到请求的属性集合中,不会更改附加到 ApiController 的用户。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      • 2021-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-10
      • 1970-01-01
      相关资源
      最近更新 更多