异步方法调用和模拟

问题描述 投票:0回答:2

为什么模拟用户上下文仅在异步方法调用之前可用? 我编写了一些代码(实际上基于 Web API)来检查模拟用户上下文的行为。

async Task<string> Test()
{
    var context = ((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();
    await Task.Delay(1);
    var name = WindowsIdentity.GetCurrent().Name;
    context.Dispose();
    return name;
}

令我惊讶的是,在这种情况下,我将收到应用程序池用户的名称。代码在其下运行。这意味着我不再拥有模拟的用户上下文。如果将延迟改为0,则使调用同步:

async Task<string> Test()
{
    var context = ((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();
    await Task.Delay(0);
    var name = WindowsIdentity.GetCurrent().Name;
    context.Dispose();
    return name;
}

代码将返回当前模拟用户的名称。 据我了解等待以及调试器显示的内容,在分配名称之前不会调用 context.Dispose() 。

c# asp.net .net asynchronous async-await
2个回答
14
投票

在 ASP.NET 中,

WindowsIdentity
不会自动由
AspNetSynchronizationContext
流动,与
Thread.CurrentPrincipal
不同。每次 ASP.NET 进入新的池线程时,都会保存模拟上下文并将“此处”设置为应用程序池用户的上下文。当 ASP.NET 离开线程时,它会在here恢复。对于 await 延续也会发生这种情况,作为延续回调调用的一部分(按
AspNetSynchronizationContext.Post
排队的调用)。
因此,如果您想在 ASP.NET 中跨多个线程的等待中保持身份,则需要手动进行流动。您可以为此使用本地或类成员变量。或者,您可以通过 

逻辑调用上下文

,使用 .NET 4.6 AsyncLocal<T>

 或类似 
Stephen Cleary 的 AsyncLocal
或者,如果您使用

ConfigureAwait(false)

:

,您的代码将按预期工作
await Task.Delay(1).ConfigureAwait(false);

(请注意,在这种情况下你会失去 
HttpContext.Current

。)

上面的方法之所以有效,是因为,

在没有同步上下文的情况下,

WindowsIdentity确实会流经

await
。它的流动方式与 
Thread.CurrentPrincipal 的方式几乎相同,即跨入异步调用(但不在异步调用之外)。我相信这是作为
SecurityContext
流的一部分完成的,它本身也是 ExecutionContext
 的一部分,并显示相同的写时复制行为。
为了支持这一说法,我用
控制台应用程序
做了一个小实验:

using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static async Task TestAsync() { ShowIdentity(); // substitute your actual test credentials using (ImpersonateIdentity( userName: "TestUser1", domain: "TestDomain", password: "TestPassword1")) { ShowIdentity(); await Task.Run(() => { Thread.Sleep(100); ShowIdentity(); ImpersonateIdentity(userName: "TestUser2", domain: "TestDomain", password: "TestPassword2"); ShowIdentity(); }).ConfigureAwait(false); ShowIdentity(); } ShowIdentity(); } static WindowsImpersonationContext ImpersonateIdentity(string userName, string domain, string password) { var userToken = IntPtr.Zero; var success = NativeMethods.LogonUser( userName, domain, password, (int)NativeMethods.LogonType.LOGON32_LOGON_INTERACTIVE, (int)NativeMethods.LogonProvider.LOGON32_PROVIDER_DEFAULT, out userToken); if (!success) { throw new SecurityException("Logon user failed"); } try { return WindowsIdentity.Impersonate(userToken); } finally { NativeMethods.CloseHandle(userToken); } } static void Main(string[] args) { TestAsync().Wait(); Console.ReadLine(); } static void ShowIdentity( [CallerMemberName] string callerName = "", [CallerLineNumber] int lineNumber = -1, [CallerFilePath] string filePath = "") { // format the output so I can double-click it in the Debuger output window Debug.WriteLine("{0}({1}): {2}", filePath, lineNumber, new { Environment.CurrentManagedThreadId, WindowsIdentity.GetCurrent().Name }); } static class NativeMethods { public enum LogonType { LOGON32_LOGON_INTERACTIVE = 2, LOGON32_LOGON_NETWORK = 3, LOGON32_LOGON_BATCH = 4, LOGON32_LOGON_SERVICE = 5, LOGON32_LOGON_UNLOCK = 7, LOGON32_LOGON_NETWORK_CLEARTEXT = 8, LOGON32_LOGON_NEW_CREDENTIALS = 9 }; public enum LogonProvider { LOGON32_PROVIDER_DEFAULT = 0, LOGON32_PROVIDER_WINNT35 = 1, LOGON32_PROVIDER_WINNT40 = 2, LOGON32_PROVIDER_WINNT50 = 3 }; public enum ImpersonationLevel { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3 } [DllImport("advapi32.dll", SetLastError = true)] public static extern bool LogonUser( string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool CloseHandle(IntPtr hObject); } } }

更新
,正如@PawelForys在评论中建议的那样,自动流动模拟上下文的另一个选项是在全局
<alwaysFlowImpersonationPolicy enabled="true"/>

文件中使用aspnet.config(如果需要,也可以使用

<legacyImpersonationPolicy enabled="false"/>
,例如对于
HttpWebRequest
) ).
    
似乎在通过 httpWebRequest 使用模拟异步 http 调用的情况下


2
投票

设置

<legacyImpersonationPolicy enabled="false"/>
还需要在aspnet.config中进行设置。否则,HttpWebRequest 将代表应用程序池用户而不是模拟用户发送。


© www.soinside.com 2019 - 2024. All rights reserved.