为什么模拟用户上下文仅在异步方法调用之前可用? 我编写了一些代码(实际上基于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()。
答案 0 :(得分:13)
在ASP.NET中,与WindowsIdentity
不同,AspNetSynchronizationContext
不会自动流过Thread.CurrentPrincipal
。每次ASP.NET进入新的池线程时,模拟上下文都会被保存并设置为{app}用户的here。当ASP.NET离开线程时,它将被恢复here。对await
延续也会发生这种情况,作为延续回调调用的一部分(由AspNetSynchronizationContext.Post
排队的那些)。
因此,如果要在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在评论中建议的那样,自动流动模拟上下文的另一个选项是在全局
<alwaysFlowImpersonationPolicy enabled="true"/>
文件中使用aspnet.config
(如果需要,还可以使用{{1}例如,对于<legacyImpersonationPolicy enabled="false"/>
)。
答案 1 :(得分:2)
如果通过httpWebRequest
使用模拟的异步http调用,则会出现这种情况HttpWebResponse webResponse;
using (identity.Impersonate())
{
var webRequest = (HttpWebRequest)WebRequest.Create(url);
webResponse = (HttpWebResponse)(await webRequest.GetResponseAsync());
}
还需要在aspnet.config中设置<legacyImpersonationPolicy enabled="false"/>
设置。否则,HttpWebRequest将代表app pool用户而不是模拟用户发送。