[WCF許可權控制]從兩個重要的概念談起:Identity與Principal[下篇]

行者武松發表於2017-10-26

毫不誇張地說,安全主體(Principal)是整個授權機制的核心。我們可以簡單地將將安全主體定義成能夠被成功實施授權的主體。一個安全主體具有兩個基本的要素:基於某個使用者的安全身份和該使用者具有的許可權。絕大部分的授權都是圍繞著“角色”進行的,我們將一組相關的許可權集和一個角色繫結,然後分配給某個使用者。所以在基於角色授權環境下,我們可以簡單地將安全主體表示成:身份
+ 角色。在.NET基於安全的應用程式設計介面中,通過IPrincipal介面表示安全主體。

目錄
一、IPrincipal
二、WindowsPrincipal
三、GenericPrincipal
四、 基於安全主體的授權

一、IPrincipal

用以表示安全主體的IPrincipal介面定義在System.Security.Principal名稱空間下。IPrincipal的定義體現在如下的程式碼片斷中,從中我們可以看出IPrincipal僅僅具有兩個成員。只讀屬性Identity表示安全主體的身份,而IsInRole用以判斷安全主體對應的使用者是否被分配了給定的角色。

   1: public interface IPrincipal
   2: {
   3:     bool IsInRole(string role);
   4:     IIdentity Identity { get; }
   5: }

上面我們具體介紹了IIdentity介面的兩個實現,WindowsIdentity和GenericIdentity。實際上IPrincipal也具有相類似的實現型別:WindowsPrincipal和GenericPrincipal,它們均定義在System.Security.Principal名稱空間下。

二、WindowsPrincipal

我們先來談談WindowsPrincipal。之前我們談到一個安全主體具有身份與許可權兩個基本要素,在Windows安全體系下,某個使用者具有的許可權決定於它被新增到那些使用者組(User

Group)中。Windows預設為我們建立了一些使用者組,比如Adminstrators和Guests等。你也根據需要建立自定義使用者組。從本質上講,Windows的使用者組和我們之前談到的角色並沒有本質的區別,都是一組許可權的載體。

WindowsPrincipal的定義如下。表示安全身份的只讀屬性Identity返回一個WindowsIdentity物件,該物件在WindowsPrincipal被建立的時候通過建構函式指定。所以在Windows安全體系,一個使用者組具有多種不同的標識方式,比如相對識別符號(RID:Relative
Identifier)、安全識別符號(SID:Security
Identifier)和使用者組名稱,對於一些與定義的使用者組甚至還可以通過System.Security.Principal.WindowsBuiltInRole列舉來表示,所以WindowsPrincipal具有若干過載的IsInRole方法。

   1: public class WindowsPrincipal : IPrincipal
   2: {
   3:     public WindowsPrincipal(WindowsIdentity ntIdentity);
   4:     public virtual bool IsInRole(int rid);
   5:     public virtual bool IsInRole(SecurityIdentifier sid);
   6:     public virtual bool IsInRole(WindowsBuiltInRole role);
   7:     public virtual bool IsInRole(string role);
   8:     public virtual IIdentity Identity { get; }
   9: }

三、GenericPrincipal

而一個GenericPrincipal物件本質上就是對一個IIdentity物件和表示角色列表的字元創陣列的封裝而以。下面的程式碼片斷體現了整個GenericPrincipal的定義。

   1: public class GenericPrincipal : IPrincipal
   2: {
   3:     public GenericPrincipal(IIdentity identity, string[] roles);
   4:     public virtual bool IsInRole(string role);
   5:     public virtual IIdentity Identity { get; }
   6: }

四、基於安全主體的授權

一個通過介面IPrincipal表示的安全主體不僅僅可以表示被授權使用者的身份(通過Identity屬性),其本身就具有授權判斷的能力(通過IsInRole方法)。如果我們在訪問者成功實施認證後根據使用者的許可權設定構建一個安全主體物件,並將其儲存在當前的上下文中,在需要的時候就可以改安全主體獲取出來以完成對授權的實現。

實際上Windows授權機制的實現就是安全這樣的原理實現的,而這個所謂的上下文就是當前執行緒的執行緒本地儲存(TLS:Thread Local
Storage)。而反映在程式設計上,你可以通過Thread型別的CurrentPrincipal屬性來獲取或者設定這個當前的安全主體。

   1: public sealed class Thread
   2: {
   3:     //其他成員
   4:     public static IPrincipal CurrentPrincipal {  get;  set; }
   5: }

一旦為當前執行緒設定了安全主體,在需要確定當前使用者是否有許可權執行某項操作或者訪問某個資源的時候,就可以通過上述的這個CurrentPrincipal屬性將設定的安全主體獲取出來,通過呼叫IsInRole方法判斷當前使用者是否具有相應的許可權。下面的程式碼體現了使用者需要具有Administrators角色(或者Windows使用者組)才能執行被授權的操作,否則會丟擲一個安全異常。

   1: IPrincipal currentPrincipal = Thread.CurrentPrincipal;
   2: if (currentPrincipal.IsInRole("Administrators"))
   3: {
   4:     //執行被授權的操作
   5: }
   6: else
   7: { 
   8:     //丟擲安全異常
   9: }

我們通過編寫具體授權邏輯的程式設計方式稱為指令式程式設計(Imperative Programming)。如果一個針對某個方法的授權(當前使用者是否有許可權呼叫需要被授權的方法),我們還可以省卻所有授權程式碼,採用一種宣告式的程式設計方式(Declarative Programming)。宣告式的授權需要使用到一個特殊的特性:PrincipalPermissionAttribute

從如下程式碼片斷給出的關於PrincipalPermissionAttribute型別的定義我們不難看出,這是一個與程式碼訪問安全(CAS:Code
Access
Security)的特性(繼承自CodeAccessSecurityAttribute)。如果在某個方法上應用了該特性,授權將被以檢驗程式碼訪問安全的方式來執行。PrincipalPermissionAttribute的Authenticated屬性用於指定目標方法是否一定需要在認證使用者環境下執行。而Name和Role表示執行目標方法所允許的使用者名稱和角色。

   1: [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true]
   2: public sealed class PrincipalPermissionAttribute : CodeAccessSecurityAttribute
   3: {    
   4:     
   5:     //其他成員
   6:     public PrincipalPermissionAttribute(SecurityAction action);
   7:  
   8:     public bool Authenticated { get; set; }
   9:     public string Name { get; set; }
  10:     public string Role { get; set; }
  11: }

從應用在PrincipalPermissionAttribute上面的AttributeUsageAttribute定義我們可以看出,該特性指定應應到型別和方法級別,並且可以在同一個目標元素上應用多個PrincipalPermissionAttribute特性。如果在同一個方法上應用了不止一個PrincipalPermissionAttribute特性,那麼只要定義在任何一個PrincipalPermissionAttribute上的授權策略通過檢驗,就任何目標方法被授權了

在下面的程式中,我們建立了四個應用了PrincipalPermissionAttribute特性的測試方法(TestMethod1、TestMethod2、TestMethod3和TestMethod4)。其中TestMethod1和TestMethod2上設定了不同的使用者名稱Foo和Bar,而TestMethod3和TestMethod4則設定了不同的角色,前者設定的單一的角色Adminstrators,後者則設定了兩個角色Adminstrators和Guests。四個訪均在Try/Catch中執行,在指定之前一個GenericPrincipal物件被建立並設定成當前執行緒的安全主體。該GenericPrincipal安全身份是一個使用者名稱為Foo的GenericIdentity,並且具有唯一的角色Guests。通過最終的輸出,我們可以看出系統自動為我們完成的授權正式採用了定義於應用在目標方法上的PrincipalPermissionAttribute特性中的授權策略。

   1: static void Main(string[] args)
   2: {
   3:     GenericIdentity identity = new GenericIdentity("Foo");
   4:     Thread.CurrentPrincipal = new GenericPrincipal(identity, new string[] { "Guests" });
   5:     Invoke(() => TestMethod1());
   6:     Invoke(() => TestMethod2());
   7:     Invoke(() => TestMethod3());
   8:     Invoke(() => TestMethod4());
   9: }
  10:  
  11: public static void Invoke(Action action)
  12: {
  13:     try
  14:     {
  15:         action();
  16:     }
  17:     catch(Exception ex)
  18:     {
  19:         Console.WriteLine(ex.Message);
  20:     }
  21: }
  22:  
  23: [PrincipalPermission(SecurityAction.Demand, Name = "Foo")]
  24: public static void TestMethod1()
  25: {
  26:     Console.WriteLine("TestMethod1方法被成功執行。");
  27: }
  28: [PrincipalPermission(SecurityAction.Demand, Name = "Bar")]
  29: public static void TestMethod2()
  30: {
  31:     Console.WriteLine("TestMethod2方法被成功執行。");
  32: }
  33: [PrincipalPermission(SecurityAction.Demand, Role="Adminstrators")]
  34: public static void TestMethod3()
  35: {
  36:     Console.WriteLine("TestMethod3方法被成功執行。");
  37: }
  38: [PrincipalPermission(SecurityAction.Demand, Role = "Adminstrators")]
  39: [PrincipalPermission(SecurityAction.Demand, Role = "Guests")]
  40: public static void TestMethod4()
  41: {
  42:     Console.WriteLine("TestMethod4方法被成功執行。");
  43: }

輸出結果:

   1: TestMethod1方法被成功執行。
   2: 對主體許可權的請求失敗。
   3: 對主體許可權的請求失敗。
   4: TestMethod4方法被成功執行。

雖然從應用在PrincipalPermissionAttribute的AttributeUsageAttribute特性定義上看,PrincipalPermissionAttribute是可同時應用在類和方法上的。但是,當我們採用這個特性以宣告的方式進行WCF服務授權的時候,我們只能將PrincipalPermissionAttribute應用在服務操作方法上,而不能應用在服務型別上

從兩個重要的概念談起:Identity與Principal[上篇]

從兩個重要的概念談起:Identity與Principal[下篇]

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


相關文章