閱讀891 返回首頁    go 技術社區[雲棲]


[WCF權限控製]模擬(Impersonation)與委托(Delegation)[上篇]

由於服務操作是在寄宿進程中執行,在默認的情況下,服務操作是否具有足夠的權限訪問某個資源(比如文件)決定於執行寄宿進程Windows帳號的權限設置,而與作為客戶端的Windows帳號無關。在有多情況下,我們希望服務操作執行在基於客戶端的安全上下文中執行,以解決執行服務進行的帳號權限不足的問題。這就涉及到一個重要的話題——模擬(Impersonation)與委托(Delegation)[實例程序源代碼從這裏下載]

目錄:
一、從訪問令牌(Access Token)說起
二、再談WindowsIdentity
三、實例演示:通過身份模擬的方式讀取文件

當我們以某個Windows帳號的名義成功登錄Windows的時候,操作係統會創建一個訪問令牌(Access Token)。訪問令牌不僅僅包括登錄用戶身份相關信息,還包括該用戶具有的權限信息,比如ACL(Access Control List)。當我們開啟某個進程的時候,該訪問令牌會自動附加到該進程上,作為其安全上下文重要的組成部分。我們也可以將訪問令牌作為進程或者線程安全描述符的封裝。Windows下的訪問令牌主要具有如下兩種形式。

  • 主令牌(Primary Token):每一個進程都具有一個唯一的主令牌,進行通過主令牌被開啟;
  • 模擬令牌(Impersonation Token):在默認的情況下,當線程被開啟的時候,所在進程的主令牌會自動附加到當前線程上,作為線程的安全上下文。而線程可以運行在另一個非主令牌的訪問令牌下執行,而這個令牌被稱為模擬令牌。而指定線程的模擬令牌的過程被稱為模擬。

我們可以調用Win32函數,通過輸入Windows帳號、密碼、域名以及認證相關信息創建一個訪問令牌。LogonUser被調用的時候,會試圖進行基於本機的登陸操作。訪問令牌會在認證成功認證的情況下被創建並返回。LogonUser的定義如下,輸入參數依次代表的含義分別是用戶名、域名(可選參數)、密碼(明文)、登錄類型和登錄提供者。而創建的訪問令牌以輸出操作的形式返回。關於LogonUser函數的詳細說明,可以參考MSDN在線文檔。

   1: BOOL LogonUser(
   2:   __in      LPTSTR lpszUsername,
   3:   __in_opt  LPTSTR lpszDomain,
   4:   __in      LPTSTR lpszPassword,
   5:   __in      DWORD dwLogonType,
   6:   __in      DWORD dwLogonProvider,
   7:   __out     PHANDLE phToken
   8: );

當我們進行模擬而試圖創建一個模擬令牌的時候,我們需要使用到另外一個重要的Win32函數。顧名思義,該函數通過一個現有的令牌來“複製”一個新的令牌。DuplicateToken函數的定義如下麵的代碼片斷所示。

   1: BOOL WINAPI DuplicateToken(
   2:   __in   HANDLE ExistingTokenHandle,
   3:   __in   SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
   4:   __out  PHANDLE DuplicateTokenHandle
   5: );

從上麵給出的DuplicateToken函數的定義我們可以看出:除了傳入現有的訪問令牌作為輸入參數之外,還具有一個表示模擬級別的ImpersonationLevel的參數。模擬級別是一個非常重要的概念,它表示被複製的模擬令牌可以被使用的程度和訪問。模擬等級通過如下所示的SECURITY_IMPERSONATION_LEVEL枚舉表示。

   1: typedef enum _SECURITY_IMPERSONATION_LEVEL {
   2:   SecurityAnonymous,
   3:   SecurityIdentification,
   4:   SecurityImpersonation,
   5:   SecurityDelegation 
   6: } SECURITY_IMPERSONATION_LEVEL, *PSECURITY_IMPERSONATION_LEVEL;

四個枚舉項,SecurityAnonymous、SecurityIdentification、SecurityImpersonation和SecurityDelegation分別代表如下四種模擬的等級。

  • 匿名(Anonymous):無法獲取有關客戶端的標識信息,且無法模擬客戶端;
  • 識別(Identification):可以獲取有關客戶端的信息(如安全標識符和特權),但是無法模擬客戶端;
  • 模擬(Impersonation):可以在本地模擬客戶端的安全上下文。,但無法在遠程係統上模擬客戶端;
  • 委托(Delegation):可以在本地和遠程係統上模擬客戶端的安全上下文。

對於模擬級別,對於托管代碼的應用編程接口中也具有一個匹配的枚舉TokenImpersonationLevel。如下麵的代碼片斷所示,除了上述的四個對應於具體模擬級別的枚舉項之外,TokenImpersonationLevel還有一個額外的枚舉值None,表示具體的模擬級別上為指定。

   1: public enum TokenImpersonationLevel
   2: {
   3:     None,
   4:     Anonymous,
   5:     Identification,
   6:     Impersonation,
   7:     Delegation
   8: }

關於模擬在托管代碼的實現,我們不得不提我們之前已經介紹過的類型WindowsIdentity。從某種意義上講,一個WindowsIdentity對象可以看成是對一個訪問令牌的封裝。WindowsIdentity直接為我們提供了模擬的功能。從下麵給出的WindowsIdentity部分成員定義可以看到,它具有一個IntPtr類型的隻讀屬性,實際上代表的就是我們上麵介紹的訪問令牌。而我們可以通過直接指定這個訪問令牌創建一個WindowsIdentity對象。ImpersonationLevel表示訪問令牌的模擬級別。

   1: public class WindowsIdentity : IIdentity,...
   2: {
   3:     //其他成員
   4:     public WindowsIdentity(IntPtr userToken);
   5:     public virtual IntPtr Token { get; }
   6:     public TokenImpersonationLevel ImpersonationLevel { get; }
   7:  
   8:     
   9:     public virtual WindowsImpersonationContext Impersonate();
  10:     public static WindowsImpersonationContext Impersonate(IntPtr userToken);
  11: }

對應一個現有的WindowsIdentity對象,隻要其訪問令牌的模擬級別為Impersonation或者Delegation,我們就能調用Impersonate方法模擬這個身份。此外WindowsIdentity還提供了靜態的Impersonate是我們可以直接根絕一個訪問令牌實施身份模式。一旦Impersonate方法被調用,。Impersonate方法返回的是一個WindowsImpersonationContext對象。如果需要將安全上下文恢複到模擬之前的狀態,可以調用WindowsImpersonationContext的Undo方法。而WindowsImpersonationContext實現了IDisposable接口,Undo方法實際上也會在Dispose方法中被調用。WindowsImpersonationContext定義如下。

   1: public class WindowsImpersonationContext : IDisposable
   2: {    
   3:     public void Dispose();
   4:     public void Undo();
   5: }

一般來說,隻有某些特殊的操作(比如訪問一個受權限控製的文件)才需要在被模擬的身份下執行。當這些操作執行完畢或者在執行過程中拋出異常,我們都需要恢複線程安全上下文到被模式之前的狀態。所以正確的模擬編程應該采用如下的方式。

   1: WindowsImpersonationContext impersonationContext = identity.Impersonate();
   2: try
   3: {
   4:     //在模擬身份下執行的操作
   5: }
   6: catch (Exception ex)
   7: {
   8:     //異常處理
   9: }
  10: finally
  11: {
  12:     impersonationContext.Undo();
  13: }

或者

   1: using (WindowsImpersonationContext impersonationContext = identity.Impersonate())
   2: { 
   3:    //在模擬身份下執行的操作     
   4: }

有一點需要注意的是:。

為了讓讀者對身份模式的作用和實現具有一個深刻的認識,我們來演示一個簡單的實例。在這個實例中,我們將通過ACL設置一個文件的讀取權限,然後演示針對不同Windows帳號進行模擬的情況下,是否能夠正常讀取該文件。

我們需要創建了兩個本機的Windows帳戶Foo和Bar(密碼為Password)。然後你在某個公共的目錄下(比如D:\盤)創建一個簡單的文本(比如impersonationTest.txt)。然後。

然後我們創建一個簡單的控製台應用作為演示程序。在默認創建的Program類中,定義如下一個CreateWindowsIdentity靜態方法。該方法通過輸入用戶名、密碼和模擬級別創建相應的WindowsIdentity。在CreateWindowsIdentity方法內部實際上是直接調用了上麵介紹的兩個Win32函數LogonUser和DuplicateToken。

   1: class Program
   2: {
   3:     //其他成員
   4:     public const int LOGON32_PROVIDER_DEFAULT = 0;
   5:     public const int LOGON32_LOGON_INTERACTIVE = 2;
   6:  
   7:     [DllImport("advapi32.dll", SetLastError = true)]
   8: public static extern bool LogonUser(string userName, string domainName, string password, int logonType, int loggonProvider, ref IntPtr token);
   9:  
  10:     [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  11: public extern static bool DuplicateToken(IntPtr existingToken, int imperonationLevel, ref IntPtr newToken);
  12:  
  13:     public static WindowsIdentity CreateWindowsIdentity(string userName, string password, TokenImpersonationLevel tokenImpersonationLevel)
  14:     {
  15:         IntPtr token = IntPtr.Zero;
  16:         IntPtr duplicateToken = IntPtr.Zero;
  17:         if (LogonUser(userName, string.Empty, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token))
  18:         {
  19:             int impersonationLevel;
  20:             switch (tokenImpersonationLevel)
  21:             {
  22:                 case TokenImpersonationLevel.Anonymous:
  23:                     { impersonationLevel = 0; break; }
  24:                 case TokenImpersonationLevel.Impersonation:
  25:                     { impersonationLevel = 2; break; }
  26:                 case TokenImpersonationLevel.Delegation:
  27:                     { impersonationLevel = 3; break; }
  28:                 default:
  29:                     { impersonationLevel = 1; break; }
  30:             }
  31:             if (DuplicateToken(token, impersonationLevel, ref duplicateToken))
  32:             {
  33:                 return new WindowsIdentity(duplicateToken);
  34:             }
  35:             else
  36:             {
  37:                 throw new InvalidOperationException(string.Format("創建模擬令牌失敗 (錯誤代碼: {0}) ", Marshal.GetLastWin32Error()));
  38:             }
  39:         }
  40:         else
  41:         {
  42:             throw new InvalidOperationException(string.Format("用戶登錄失敗 (錯誤代碼: {0}) ", Marshal.GetLastWin32Error()));
  43:         }
  44:     }        
  45: }

然後我們定義如下一個靜態的ReadFile方法。在這個方法中,我們根據傳入的用戶名和密碼調用上述的CreateWindowsIdentity方法創建相應的WindowsIdentity。然後模擬該用戶進行文件的讀取。在成功讀取和拋出異常的情況下分別輸出相應的指示性文字。在Main方法中,分別傳入傳入賬號Foo和Bar以及相應的密碼對該方法進行調用。從輸出的結果可以看出,在模擬帳號Foo時,文件被成功讀取,而在模擬帳號Bar的時候卻失敗了。這和測試文件的ACL設置時一致的。

   1: public static void ReadFile(string userName, string password)
   2: {
   3:     try
   4:     {
   5:         WindowsIdentity identity = CreateWindowsIdentity(userName, password, TokenImpersonationLevel.Impersonation);
   6:         using (WindowsImpersonationContext context = identity.Impersonate())
   7:         {
   8:             Console.WriteLine("當前用為:{0}", WindowsIdentity.GetCurrent().Name);
   9:             File.ReadAllText("D:\\impersonationTest.txt");
  10:             Console.WriteLine("成功讀取文件內容...");
  11:         }
  12:     }
  13:     catch
  14:     {
  15:         Console.WriteLine("讀取文件失敗...");
  16:     }
  17: }
  18: static void Main(string[] args)
  19: {
  20:     ReadFile("Foo", "Password");
  21:     ReadFile("Bar", "Password");
  22: }

輸出結果(Jinnan-PC為我本機的機器名):

   1: 當前用戶為:Jinnan-PC\Foo
   2: 成功讀取文件內容...
   3: 當前用戶為:Jinnan-PC\Bar
   4: 讀取文件失敗...

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 16:34:06

  上一篇:go  [WCF權限控製]基於Windows用戶組的授權方式[下篇]
  下一篇:go  [WCF權限控製]ASP.NET Roles授權[上篇]