在ASP.NET中,与
Thread.CurrentPrincipal
不同,
WindowsIdentity
不会自动流动到
AspNetSynchronizationContext
。每次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();
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 = "")
{
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);
}
}
}