2015-07-20 26 views
10

Tại sao ngữ cảnh người dùng mạo danh chỉ khả dụng cho đến khi phương thức async gọi? Tôi đã viết một số mã (thực sự dựa trên API Web) để kiểm tra hành vi của ngữ cảnh người dùng bị mạo danh.Gọi và giả mạo phương thức không đồng bộ

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; 
} 

Trước sự ngạc nhiên của tôi trong trường hợp này, tôi sẽ nhận được tên người dùng trong ứng dụng. theo đó mã đang chạy. Điều đó có nghĩa là tôi không còn bối cảnh người dùng bị thiếu nữa. Nếu độ trễ được thay đổi thành 0, điều này làm cho cuộc gọi đồng bộ:

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; 
} 

Mã sẽ trả về tên của người dùng hiện đang mạo danh. Theo như tôi hiểu được chờ đợi và những gì trình gỡ lỗi cho thấy là tốt, context.Dispose() không được gọi cho đến khi tên được gán.

+2

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

+1

@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

+0

@Noseratio cần biết. – usr

Trả lời

12

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).

+3

Cảm ơn bạn rất nhiều vì câu trả lời sâu sắc. Nó thực sự đã giúp tôi hiểu những gì nằm phía sau bối cảnh được chuyển đến và từ các cuộc gọi không đồng bộ. Tôi đã tìm thấy một giải pháp có thể thay đổi hành vi mặc định và cho phép truyền danh tính cho các chủ đề cuối cùng được tạo bởi async. Điều này được giải thích ở đây: http://stackoverflow.com/a/10311823/637443. Các cài đặt: cũng sẽ hoạt động đối với các ứng dụng sử dụng app.config. Sử dụng cấu hình này, danh tính sẽ được chuyển và được giữ nguyên. Vui lòng sửa nếu điều đó không đúng. –

+1

@ PawełForys, tôi nghĩ rằng '' thôi nên làm điều đó, có thể bạn không cần 'legacyImpersonationPolicy'. Cho chúng tôi biết nếu mà làm việc. – Noseratio

+1

Chính xác, một mình cho phép lưu lượng nhận dạng qua các luồng. Cảm ơn! –

2

Dường như trong trường hợp của việc sử dụng mạo nhận cuộc gọi http async qua HttpWebRequest

HttpWebResponse webResponse; 
      using (identity.Impersonate()) 
      { 
       var webRequest = (HttpWebRequest)WebRequest.Create(url); 
       webResponse = (HttpWebResponse)(await webRequest.GetResponseAsync()); 
      } 

thiết lập <legacyImpersonationPolicy enabled="false"/> nhu cầu cũng có thể được thiết lập trong aspnet.config. Nếu không HttpWebRequest sẽ gửi thay mặt cho người dùng ứng dụng trong hồ bơi và không mạo danh người dùng.

Các vấn đề liên quan