我很难理解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上下文为止。 Thread.CurrentPrincipal
(因为将其设置为HttpContext.Current.User
)。 Thread.CurrentPrincipal
。 将其应用于原始代码并输出:
CurrentPrincipal
后,线程3同步报告了前3个线程,因此它们都具有预期值。 async
方法,从而进入ASP.NET上下文并清除其可能拥有的所有CurrentPrincipal
。 ProcessResponse
。它重新输入ASP.NET上下文,清除其Thread.CurrentPrincipal
。 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/