Trong ASP.NET, WindowsIdentity
không được tự động lưu chuyển bởi AspNetSynchronizationContext
, không giống như nói Thread.CurrentPrincipal
. Mỗi khi ASP.NET đi vào một chủ đề hồ bơi mới, bối cảnh mạo danh được lưu và thiết lập here cho người dùng trong nhóm ứng dụng. Khi ASP.NET rời khỏi chủ đề, nó được khôi phục here. Điều này cũng xảy ra đối với việc tiếp tục await
, như là một phần của các lời gọi lại gọi lại tiếp tục (các yêu cầu gọi lại là AspNetSynchronizationContext.Post
).
Vì vậy, nếu bạn muốn giữ danh tính trên đang chờ mở rộng nhiều chuỗi trong ASP.NET, bạn cần phải lưu nó theo cách thủ công. Bạn có thể sử dụng biến cục bộ hoặc thành viên của lớp cho điều đó. Hoặc, bạn có thể truyền nó qua logical call context, với .NET 4.6 AsyncLocal<T>
hoặc một cái gì đó như Stephen Cleary's AsyncLocal
.
Ngoài ra, mã của bạn sẽ làm việc như mong đợi nếu bạn sử dụng ConfigureAwait(false)
:
await Task.Delay(1).ConfigureAwait(false);
(Lưu ý mặc dù bạn sẽ mất HttpContext.Current
trong trường hợp này.)
Trên đây sẽ làm việc bởi vì, trong không có bối cảnh đồng bộ hóa, WindowsIdentity
không được chuyển qua await
. Nó chảy trong khá nhiều the same way as Thread.CurrentPrincipal
does, tức là, trên và thành các cuộc gọi không đồng bộ (nhưng không nằm ngoài các cuộc gọi đó). Tôi tin rằng điều này được thực hiện như một phần của luồng SecurityContext
, chính nó là một phần của ExecutionContext
và hiển thị cùng một hành vi sao chép trên ghi.
Để hỗ trợ tuyên bố này, tôi đã làm một chút thử nghiệm với một ứng dụng console:
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);
}
}
}
Cập nhật, như @PawelForys gợi ý trong các ý kiến, tùy chọn khác để chảy bối cảnh mạo danh tự động là sử dụng
<alwaysFlowImpersonationPolicy enabled="true"/>
trong tệp toàn cầu
aspnet.config
(và, nếu cần,
<legacyImpersonationPolicy enabled="false"/>
, ví dụ:
HttpWebRequest
).
Bạn đã mạo danh một số chủ đề chuỗi chủ đề ngẫu nhiên. Yêu cầu tiếp theo để chạy trên nó có thể bị ảnh hưởng bởi điều này. Siêu nguy hiểm. – usr
@usr, khi nó quay, nó không phải là nguy hiểm, trừ khi bạn mạo danh bên trong một cái gì đó như 'UnsafeQueueUserWorkItem'. Nếu không, danh tính được truyền và khôi phục chính xác, nó sẽ không bị treo trên một chuỗi hồ bơi. Xem [thí nghiệm nhỏ này] (https://gist.github.com/noserati/940c21b488e59d502dd1), đặc biệt là 'GoThruThreads'. Nó thậm chí còn an toàn hơn trong ASP.NET, kiểm tra cập nhật của tôi. – Noseratio
@Noseratio cần biết. – usr