为什么模拟用户上下文仅在异步方法调用之前可用?
我编写了一些代码(实际上基于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;
}
令我惊讶的是,在这种情况下,我将收到App pool用户的名字。代码运行的位置。这意味着我不再拥有被模仿的用户上下文。如果延迟更改为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()。 
             
            
            
            
在ASP.NET中, WindowsIdentity 不会自动流过 AspNetSynchronizationContext,不像说 Thread.CurrentPrincipal。每次ASP.NET进入新的池线程时,都会保存并设置模拟上下文 这里 应用程序池用户的。当ASP.NET离开线程时,它将被恢复 这里。这发生了 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调用的情况下似乎
HttpWebResponse webResponse;
            using (identity.Impersonate())
            {
                var webRequest = (HttpWebRequest)WebRequest.Create(url);
                webResponse = (HttpWebResponse)(await webRequest.GetResponseAsync());
            }
那个设定 <legacyImpersonationPolicy enabled="false"/> 还需要在aspnet.config中设置。否则,HttpWebRequest将代表app pool用户而不是模拟用户发送。