使用ASP.NET的Web API,我的ExecutionContext是不是在异步操作流(Usin

2019-08-22 04:50发布

我有困难,了解背后的ExecutionContext的机制。

从我在线阅读,上下文相关的项目,如安全(螺纹校长),文化等,应在整个异步线程工作的执行单元的范围内流动。

我虽然遇到非常混乱,有潜在危险的错误。 我注意到我的线程的CurrentPrincipal是越来越跨越异步执行丢失。


下面是一个例子的ASP.NET Web API的情景:

首先,让我们设置有用于测试目的的两个委托处理一个简单的Web API配置。

他们做的是写出调试信息并通过对通过请求/响应,除了第一次“DummyHandler”,这将线程的主要以及一块被跨越的上下文共享数据(请求的关联ID)。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new DummyHandler());
        config.MessageHandlers.Add(new AnotherDummyHandler());

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

public class DummyHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        CallContext.LogicalSetData("rcid", request.GetCorrelationId());
        Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new[]{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "dgdev") }, "myauthisthebest")));

        Debug.WriteLine("Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("RCID: {0}", CallContext.LogicalGetData("rcid"));

        return base.SendAsync(request, cancellationToken)
                   .ContinueWith(task =>
                       {
                           Debug.WriteLine("Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
                           Debug.WriteLine("User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
                           Debug.WriteLine("RCID: {0}", CallContext.LogicalGetData("rcid"));

                           return task.Result;
                       });
    }
}

public class AnotherDummyHandler : MessageProcessingHandler
{
    protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("  Another Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("  User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("  RCID: {0}", CallContext.LogicalGetData("rcid"));

        return request;
    }

    protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
    {
        Debug.WriteLine("  Another Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("  User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("  RCID: {0}", CallContext.LogicalGetData("rcid"));

        return response;
    }
}

够简单。 接下来,让我们添加一个ApiController来处理HTTP POST,因为如果你上传的文件。

public class UploadController : ApiController
{
    public async Task<HttpResponseMessage> PostFile()
    {
        Debug.WriteLine("    Thread: {0}", Thread.CurrentThread.ManagedThreadId);
        Debug.WriteLine("    User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
        Debug.WriteLine("    RCID: {0}", CallContext.LogicalGetData("rcid"));

        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

        try
        {
            await Request.Content.ReadAsMultipartAsync(
                new MultipartFormDataStreamProvider(
                    HttpRuntime.AppDomainAppPath + @"upload\temp"));

            Debug.WriteLine("    Thread: {0}", Thread.CurrentThread.ManagedThreadId);
            Debug.WriteLine("    User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
            Debug.WriteLine("    RCID: {0}", CallContext.LogicalGetData("rcid"));

            return new HttpResponseMessage(HttpStatusCode.Created);
        }
        catch (Exception e)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
        }
    }
}

在运行使用Fiddler测试,这是我收到的输出:

Dummy Handler Thread: 63
User: dgdev
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

  Another Dummy Handler Thread: 63
  User: dgdev
  RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

    Thread: 63
    User: dgdev
    RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

    Thread: 77
    User:                                     <<<  PRINCIPAL IS LOST AFTER ASYNC
    RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

  Another Dummy Handler Thread: 63
  User:                                       <<<  PRINCIPAL IS STILL LOST
  RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

Dummy Handler Thread: 65
User: dgdev                                   <<<  PRINCIPAL IS BACK?!?
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476

为了使问题更混乱,当我追加后续的异步线路:

await Request.Content.ReadAsMultipartAsync(
    new MultipartFormDataStreamProvider(..same as before..))
.ConfigureAwait(false); <<<<<<

我现在收到此输出:

Dummy Handler Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2

  Another Dummy Handler Thread: 40
  User: dgdev
  RCID: 8d944500-cb52-4362-8537-dab405fa12a2

    Thread: 40
    User: dgdev
    RCID: 8d944500-cb52-4362-8537-dab405fa12a2

    Thread: 65
    User: dgdev                               <<<  PRINCIPAL IS HERE!
    RCID: 8d944500-cb52-4362-8537-dab405fa12a2

  Another Dummy Handler Thread: 65
  User:                                       <<<  PRINCIPAL IS LOST
  RCID: 8d944500-cb52-4362-8537-dab405fa12a2

Dummy Handler Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2

这里的关键是这样的。 异步下面的代码我其实叫我的业务逻辑或只要求安全上下文正确设置。 目前正在进行一个潜在的完整性问题。

任何人都可以帮助一些启发一个发生了什么?

提前致谢。

Answer 1:

我不知道所有的答案,但我可以帮助填补空白部分,并在问题猜测。

默认情况下,ASP.NET SynchronizationContext会流动,但它流动身份的方式是有点不可思议 。 它实际上流动HttpContext.Current.User ,然后设置Thread.CurrentPrincipal了这一点。 所以,如果你只是设置Thread.CurrentPrincipal ,你不会看到它正确地流动。

事实上,你会看到以下行为:

  • 从时间Thread.CurrentPrincipal的一个线程组,该线程将有同样的本金,直到它重新进入ASP.NET环境。
  • 当任何线程进入ASP.NET背景下, Thread.CurrentPrincipal被清除(因为它设置为HttpContext.Current.User )。
  • 当一个线程使用ASP.NET环境之外 ,它只是保留任何Thread.CurrentPrincipal碰巧设置就可以了。

将其应用到您的原码输出:

  • 前3个都是从同步线程63后的报道CurrentPrincipal被明确设置,所以它们都具有预期值。
  • 螺纹77是用来恢复async方法,从而进入ASP.NET上下文和清除任何CurrentPrincipal它可能有。
  • 螺纹63用于ProcessResponse 。 它重新进入ASP.NET的背景下,清除其Thread.CurrentPrincipal
  • 螺纹65是有趣的。 它运行的是ASP.NET上下文(在外面ContinueWith没有调度),所以它只是保留任何CurrentPrincipal它发生在面前。 我认为它的CurrentPrincipal只是从早期的测试运行遗留下来的。

更新后的代码改变PostFile到ASP.NET上下文之外运行其第二部分。 所以它拿起螺纹65,这恰好有CurrentPrincipal集。 因为它是在ASP.NET环境之外, CurrentPrincipal不会被清零。

因此,它看起来对我来说, ExecutionContext流动的罚款。 我敢肯定,微软已经测试ExecutionContext流出来的wazoo; 否则世界上所有的ASP.NET应用程序将有一个严重的安全漏洞。 需要注意的是,在这段代码是很重要的Thread.CurrentPrincipal只是指当前用户的说法,并不代表实际的模拟。

如果我的猜测是正确的,那么解决方法是相当简单:在SendAsync ,改变这一行:

Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new[]{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "dgdev") }, "myauthisthebest")));

为此:

HttpContext.Current.User = new ClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new[]{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "dgdev") }, "myauthisthebest")));
Thread.CurrentPrincipal = HttpContext.Current.User;


文章来源: Using ASP.NET Web API, my ExecutionContext isn't flowing in async actions