.NET 7中使用ExecutionContext.SuppressFlow()抛出异常问题咨询
我的WebApi客户端项目调用HttpClient.SendAsync时,当前用户的WindowsIdentity会丢失,HttpContext.User返回应用池标识而非模拟的WindowsIdentity。项目基于.NET Standard,兼容.NET Framework和.NET Core。用RestSharp时无需ExecutionContext.SuppressFlow()就能正常运行,但客户端由NSwag生成,没法改用RestSharp。
在.NET Framework中,用ExecutionContext.SuppressFlow()可以把Identity正确委派到服务器;但在.NET 7中会抛出异常:
Cannot call Set on a null context
异常调用栈:
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Security.Principal.WindowsIdentity.RunImpersonatedInternal(SafeAccessTokenHandle token, Action action) at System.Security.Principal.WindowsIdentity.GetName() at System.Net.Http.CurrentUserIdentityProvider.GetIdentity() at System.Net.Http.HttpConnectionPoolManager.GetConnectionKey(HttpRequestMessage request, Uri proxyUri, Boolean isProxyConnect) at System.Net.Http.HttpConnectionPoolManager.SendAsyncCore(HttpRequestMessage request, Uri proxyUri, Boolean async, Boolean doRequestAuth, Boolean isProxyConnect, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPoolManager.SendAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.HttpAuthenticatedConnectionHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpMessageHandlerStage.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.<SendAsync>d__4.MoveNext() at System.Net.Http.HttpClient.<<SendAsync>g__Core|83_0>d.MoveNext()
最小复现代码片段:
UriBuilder uriBuilder = new() { Scheme = Uri.UriSchemeHttps, Host = "localhost", Port = 7042 }; var urlBuilder = new StringBuilder(); urlBuilder.Append(uriBuilder.ToString() + "/WeatherForecast"); HttpClientHandler handler = new() { UseDefaultCredentials = true, PreAuthenticate = true }; HttpClient client = new(handler); using var request = new HttpRequestMessage(); request.Method = new HttpMethod("GET"); var url = urlBuilder.ToString(); request.RequestUri = new Uri(url, UriKind.RelativeOrAbsolute); ExecutionContext.SuppressFlow(); var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
请问在.NET Core中是否需要以不同方式使用ExecutionContext?
在.NET Core/.NET 5+中,ExecutionContext的处理逻辑和.NET Framework有差异,直接调用ExecutionContext.SuppressFlow()会导致上下文为空的异常,因为.NET Core中ExecutionContext的流动控制更严格。针对Windows身份委派的问题,推荐以下两种处理方式:
方式一:安全包裹ExecutionContext.SuppressFlow()
调用SuppressFlow()前先检查当前是否有可流动的上下文,并用try/finally确保恢复流动状态,避免上下文泄漏:
bool flowSuppressed = false; try { if (!ExecutionContext.IsFlowSuppressed()) { ExecutionContext.SuppressFlow(); flowSuppressed = true; } var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); } finally { if (flowSuppressed) { ExecutionContext.RestoreFlow(); } }
方式二:自定义HttpMessageHandler处理身份委派
创建自定义DelegatingHandler,在发送请求时显式传递当前模拟的WindowsIdentity,绕过ExecutionContext的流动限制:
public class ImpersonationHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var currentIdentity = WindowsIdentity.GetCurrent(); if (currentIdentity != null && currentIdentity.ImpersonationLevel != TokenImpersonationLevel.None) { using (currentIdentity.Impersonate()) { return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } } return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } }
使用时将该handler添加到HttpClient管道:
HttpClientHandler handler = new() { UseDefaultCredentials = true, PreAuthenticate = true }; var client = new HttpClient(new ImpersonationHandler { InnerHandler = handler });
关键说明
- .NET Core中
HttpClient的连接池机制会复用连接,ExecutionContext的流动会干扰连接池的身份关联,这是直接调用SuppressFlow()报错的核心原因。 - NSwag生成的客户端可通过配置自定义
HttpMessageHandler集成上述方案,无需修改生成的客户端代码,只需在初始化客户端时传入自定义handler即可。
内容的提问来源于stack exchange,提问作者user22464113

