在ASP.NET中,WindowsIdentity
不會自動流動AspNetSynchronizationContext
,不像說Thread.CurrentPrincipal
。每次ASP.NET進入一個新的線程池時,模擬上下文都會被保存,並將其設置爲應用程序池用戶的here。當ASP.NET離開該線程時,它將被恢復爲here。作爲延續回調調用的一部分(由AspNetSynchronizationContext.Post
排隊的調用),這也會發生在await
延續中。因此,如果您希望跨越ASP.NET中多個線程的等待事件來保持身份,則需要手動進行流式處理。你可以使用本地或類成員變量。或者,你可以通過logical call context,.NET 4.6 AsyncLocal<T>
或類似的東西Stephen Cleary's AsyncLocal
。
或者,您的代碼將工作,如果你使用ConfigureAwait(false)
預期:
await Task.Delay(1).ConfigureAwait(false);
(注意:雖然你會在這種情況下失去了HttpContext.Current
。)
上面會的工作,因爲在沒有同步上下文,WindowsIdentity
確實流過了await
。它幾乎流入the same way as Thread.CurrentPrincipal
does,即跨越並進入異步調用(但不在這些異步調用之外)。我相信這是作爲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建議在評論中,另一種選擇流動模擬環境是自動在全局文件
aspnet.config
中使用
<alwaysFlowImpersonationPolicy enabled="true"/>
(如果需要,也可使用
<legacyImpersonationPolicy enabled="false"/>
,例如
HttpWebRequest
)。
你已經模擬了一些隨機線程池線程。運行它的下一個請求可能會受此影響。超級危險。 – usr
@usr,因爲它變成了,除非你冒充類似'UnsafeQueueUserWorkItem'這樣的東西,否則它並不是那麼危險。否則,身份得到正確的傳播和恢復,它不會留在池線程中。看[這個小實驗](https://gist.github.com/noserati/940c21b488e59d502dd1),特別是'GoThruThreads'。在ASP.NET中更安全,請檢查我的更新。 – Noseratio
@Noseratio很高興知道。 – usr