我很难理解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

这里的重点是这个。异步之后的代码实际上称为我的业务逻辑,或者仅要求正确设置安全上下文。存在潜在的完整性问题。

任何人都可以帮助您了解正在发生的事情吗?

提前致谢。

最佳答案

我没有所有的答案,但是我可以帮助您填补一些空白并猜测问题所在。

默认情况下,ASP.NET SynchronizationContext将流动,但the way it flows identity is a bit weird流动。它实际上流HttpContext.Current.User,然后将Thread.CurrentPrincipal设置为此。因此,如果仅设置Thread.CurrentPrincipal,您将看不到它的正确流动。

实际上,您将看到以下行为:

  • 从在线程上设置Thread.CurrentPrincipal以来,该线程将具有相同的主体,直到重新输入ASP.NET上下文为止。
  • 当任何线程进入ASP.NET上下文时,都会清除Thread.CurrentPrincipal(因为将其设置为HttpContext.Current.User)。
  • 在ASP.NET上下文之外使用线程时,它仅保留碰巧要在其上设置的Thread.CurrentPrincipal

  • 将其应用于原始代码并输出:
  • 明确设置了CurrentPrincipal后,线程3同步报告了前3个线程,因此它们都具有预期值。
  • 线程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正常运行。我确定微软已经测试了wazoo的ExecutionContext流;否则,世界上每个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;
    

    关于c# - 使用ASP.NET Web API,我的ExecutionContext没有在异步操作中流动,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15964244/

    10-10 13:26