gpt4 book ai didi

c# - 使用 ASP.NET Web API,我的 ExecutionContext 不在异步操作中流动

转载 作者:IT王子 更新时间:2023-10-29 04:34:41 26 4
gpt4 key购买 nike

我很难理解 ExecutionContext 背后的机制。

根据我在网上阅读的内容,安全(线程主体)、文化等上下文相关的项目应该在工作执行单元的范围内跨异步线程流动。

不过,我遇到了非常令人困惑且具有潜在危险的错误。我注意到我线程的 CurrentPrincipal 在异步执行过程中丢失了。


这是一个示例 ASP.NET Web API 场景:

首先,让我们设置一个简单的 Web API 配置,其中包含两个用于测试的委托(delegate)处理程序。

他们所做的就是写出调试信息并传递请求/响应,除了第一个“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

将此应用于您的原始代码和输出:

  • 前 3 个都是在显式设置 CurrentPrincipal 后从线程 63 同步报告的,因此它们都具有预期值。
  • 线程 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 运行良好。我确定 Microsoft 已经测试了 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;

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

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com