.NET框架中基於角色的安全性

 
     【簡 介】
      在過去的相當長一段時間內,計算機及信息犯罪的比例正在逐漸升高。美國聯邦調查局的計算機安全組織在2001年的研究調查中發現85%企業的企業安全受到侵害。在對這些企業進行調查之後提出的財物損失報告中指出,合計損失爲3億7千7百萬美元,比起2000年的2億6千5百萬美金增加了42%。由此可清楚的看出,計算機及信息犯罪的發生次數越來越頻繁,其所造成的損失也越來越大,另外,犯罪的手段也越來越豐富,令企業安全人員防不勝防。因此企業必須有所行動來保護有價值的信息資產。自然而然的,安全性在現在的程序開發中越來越成爲一個不可忽視的問題。   傳統的安全模型將安全性建立在用戶以及用戶組的機制上來提供隔離和訪問控制。這就意味着用戶要麼可以運行全部代碼,要麼都不能運行。而這正是現在大多數操作系統採取的安全模型,即使現在看來這種機制也是很有效的,但是深入思考之後我們可以發現這種機制存在的假設是所有的代碼都具有相同的信任程度。當所有的代碼都是來自你或者你的系統管理員,那麼這種假設是行之有效的。但是現在大多數計算機都連上了Internet之後,這種"都行"或者"都不行"的方式就不那麼好了。.NET框架提供了全面的安全系統,足以應付現在已有的大多數安全性問題。在.NET框架中提供了與傳統模型相似的,但卻是由開發人員自定義的安全模型,稱爲基於角色的安全性(Role-Based Security)。基於角色的安全性最重要的概念就是授權(Principals)和標識(Identity)。
   
   
   
    基於角色的安全性
   
    簡單的說,程序安全性的目的就是防止不懷好意的人或者程序不能做管理員和開發人員不允許做的事情。在前面提到的傳統的安全機制着眼於控制用戶的權限,通過驗證用戶的身份標識來限制用戶的操作,從而可以控制特定的用戶對資源的訪問,在過去很長的一段時間裏,Windows和UNIX這兩個最成功的操作系統都採取了這種安全機制。在討論.NET的安全編程之前,我們將先來看看.NET平臺提供的安全模型,只有深入理解安全模型,我們才能更加有效的利用平臺給我們提供的更好的安全性保證(以下討論的操作系統以Windows 2000及Windows XP爲主)。
   
    .NET的安全模型在系統的安全模型的上層,並且與一些服務器程序的安全特性結合的很好(當然,目前這些產品還僅限於微軟自己的產品,比如SQL Server和Internet Information Services (IIS))。正因爲.NET與操作系統的層次不同,所以.NET程序的安全性就取決於這樣幾個因素:.NET安全性是如何配置的,程序組件是怎樣編寫的,以及一些由Windows,網絡設置或者其他程序設定的安全特性。
   
    下面這幅圖說明了.NET安全模型是如何在Windows的安全子系統上工作的。管理員使用管理控制檯snap-ins設置用戶帳號並制定安全策略。同時,管理員也負責管理.NET安全配置。當用戶登陸操作系統並運行.NET託管程序,CLR將驗證用戶並允許程序進行某些動作,接着將這些操作傳遞給操作系統的安全監視程序。
   
   
    不過有一個需要特別注意的問題,就是無論你怎樣使用.NET的安全,資源仍然是處於操作系統級的保護之下。對於受特殊保護的資源,.NET平臺的安全權限是無能爲力的(其實這一點也正好符合.NET與操作系統的層次關係)。
   
    下面我們就來詳細的看看基於角色的安全性中的幾個概念,以及這幾個概念在.NET中是如何運用的。
  驗證(authentication)指的是確定用戶身份的過程,而授權(authorization)指的是經過上面的過程之後給予用戶訪問特定資源的權限,說明白一點,驗證就是知道"你是誰",而授權則是"讓你可以做什麼"。.NET爲實現這兩個過程提供了Principal和Identity對象,其中,基於角色的安全性基礎建立在Principal對象之上,該對象封裝了當前用戶的信息,既包含用戶身份,也包含他所扮演的角色;用戶身份用Identity對象來指明,Identity對象中不僅包含指定的用戶身份信息(用戶名稱或賬號),還包括了"如何驗證這一身份"的方法。
   
    Identity對象
   
    Identity對象是實現了IIdentity接口的類的實例。IIdentity接口包括三個只讀屬性:
   
     string AuthenticationType {get;} 獲取所使用的身份驗證的類型
     bool IsAuthenticated {get;} 獲取布爾值,該值指出登陸用戶是否經過驗證
     string Name {get;} 獲取當前用戶的名稱
   
    .NET中實現了接口的有以下四個類:
   
     1、GenericIdentity 用來表示一般性的用戶,可以用於自定義登陸驗證的情況。
   
     2、WindowsIdentity 用來表示登陸Windows系統成功的普通Windows用戶。
   
     3、FormsIdentity 用來表示ASP.NET應用程序中使用Forms身份驗證的用戶。
   
     4、PassportIdentity 用來表示在使用Passport的應用程序中的用戶。不過要注意必須要安裝了Passport SDK才能使用這個類。
   
    因爲在當前的具體開發中使用得最多的是前三個,而FormsIdentity類將在後文專門講到,所以下面我們將詳細討論前兩個類
    GenericIdentity類
   
    GenericIdentity類其實相當簡單,它並不與任何特定的驗證協議相關聯。因此,它往往被用在採用了自定義登陸機制的場合。比如一個程序可以自己提示用戶輸入用戶名和密碼,然後到自定義的用戶數據庫中去查詢。如果用戶名和密碼有效,那麼程序就會創建一個基於數據庫中的匹配記錄的principal和(對應的)identity對象。
   
    GenericIdentity類除了三個IIdentity接口定義的屬性之外沒有更多的東西了。不過,GenericIdentity類提供了兩個構造函數。一個構造函數接受一個字符串參數,該參數指定的是用戶名;另一個構造函數接受兩個參數:第一個是用戶名字符串,第二個是給定的驗證類型字符串。
   
   
  public GenericIdentity(string name);
  public GenericIdentity(string name, string type);
   
    現在我們不過多地講述使用GenericIdentity類的細節問題,在後面我們將會看到在一個實際的程序當中是如何使用GenericIdentity對象的。
   
    WindowsIdentity類
   
    作爲實現了IIdentity接口的派生類,WindowsIdentity類主要用於表示登陸Windows成功的用戶。下面我們依次來看看WindowsIdentity類的構造函數,屬性和方法。
   
    在構造函數中會用到IntPtr類型的參數,我們先來看看這種數據類型,IntPtr類型通常用來表示與平臺相關的數據類型,比如一個內存指針或者是一個句柄。在我們使用的情況下,IntPtr參數通常用來代表一個Win32句柄,而這個句柄指向的是一個32位的用戶的帳號標記(account token)。這個標記一般是通過調用非託管的Win32 API獲得的。
   
   
  public WindowsIdentity(IntPtr userToken);
  public WindowsIdentity(IntPtr userToken, string authType);
  public WindowsIdentity(IntPtr userToken, string authType, WindowsAccountType acctType);
  public WindowsIdentity(IntPtr userToken, string authType, WindowsAccountType acctType, bool isAuthenticated);
   
    每一個構造函數都帶有相同的IntPtr參數,後面跟着一些帶有其他信息的參數:驗證類型,Windows帳號類型以及驗證狀態。要注意WindowsAccountType參數必須要使用下列枚舉值之一:Anonymous,Guest,Normal,System。
   
    理所當然的,WindowsIdentity類也具有IIdentity接口的三個只讀屬性: AuthenticationType,IsAuthenticated和Name。另外,WindowsIdentity類還有自身特有的屬性:IsAnonymous,IsGuest和IsSystem,有了這三個屬性可以更好的確定用戶帳號。
   
    接着再來看看WindowsIdentity類的方法。除了繼承於Object類的方法之外,WindowsIdentity類還有這樣三個方法:GetAnonymous,GetCurrent和Impersonate。
   
    1、GetAnonymous方法返回的是一個表示匿名用戶的WindowsIdentity對象。
   
    2、GetCurrent方法返回的是一個表示當前用戶的WindowsIdentity對象。
   
    3、Impersonate方法可以讓你的代碼臨時模擬出一個用戶。
   
    GetAnonymous和GetCurrent方法都返回一個WindowsIdentity對象,使用也很簡單,我們需要注意的是Impersonate方法,該方法有兩個版本:實例版本(instance version)和靜態版本(static version)。實例版本的方法不帶參數,返回一個基於被調用WindowsIdentity對象的WindowsImpersonationContext對象(WindowsImpersonationContext類表示模擬操作之前的 Windows 用戶);靜態版本則需要一個IntPtr參數。這種模擬操作對於服務器程序來說是很有用的,它可以降低客戶端訪問服務器所用用戶帳號的權限,從而在一定程度上提高了安全性。下面是上述方法的具體語法:
   
   
  public static WindowsIdentity GetAnonymous();
  public static WindowsIdentity GetCurrent();
  public virtual WindowsImpersonationContext Impersonate();
  public static WindowsImpersonationContext Impersonate(IntPtr userToken);
   

     Principal對象
   
    Principal對象是實現了IPrincipal接口的類的實例,這些對象用來表示用戶,並且包括了用戶的身份信息。System.Security.Principal命名空間包括了幾種類型的Principal類,這些類中封裝了程序代碼運行的的安全環境(security context)。我們在後面將會看到對用戶名和角色進行檢查以確定根據用戶身份和角色資格是否可以讓用戶執行某些特定操作的示例代碼。
   
    對於每一個線程來說都與一個principal對象相關聯。這個principal對象包括了表示運行當前線程的用戶的identity對象。我們可以利用Thread類的靜態屬性CurrentPrincipal來獲得這個principal對象。
   
    下面我們來看看IPrincipal接口,該接口只有一個Identity公共屬性和IsInRole公共方法:
   
    1、Identity屬性指向一個與principal 對象關聯的IIdentity對象。
   
    2、IsInRole方法需要一個字符串參數,該字符串是一個角色的名稱,並且返回布爾值,指出principal對象是否屬於指定的角色。
   
    由於實際開發的需要,我們更多接觸到的是WindowsPrincipal類,下面將詳細討論WindowsPrincipal類,相對而言,GenericPrincipal類就要簡略一些了。
   
    GenericPrincipal類
   
    GenericPrincipal類用來表示一個通過自定義驗證的用戶,通常與GenericIdentity類一起使用。下面是一段簡單的程序,說明了這兩個類如何使用:
   
   
  //創建一個GenericIdentity對象
  IIdentity myGenericIdentity = new GenericIdentity(strUserName, "MyAuthenticationType");
   
  //創建一個GenericPrincipal對象
  String[] roles = null;
  GenericPrincipal myGenericPrincipal = new GenericPrincipal(myGenericIdentity, roles);
   
  //將創建的GenericPrincipal對象附加到當前線程上
  Thread.CurrentPrincipal = myGenericPrincipal;
   
    注意在上面的例子中,我們可以把MyAuthenticationType的驗證類型換成熟知的Kerberos身份驗證或者NTLM身份驗證。
   
    下面是驗證的過程:
   
   
  //取得當前線程的principal對象
  IPrincipal principal = Thread.CurrentPrincipal;
   
  if (!principal.Identity.Name.Equals("TrustedUser"))
  {
  throw new SecurityException(
  strUserName + " NOT PERMITTED to proceed./n");
  }
  Console.WriteLine(
  strUserName + " is PERMITTED to proceed./n");
   
    WindowsPrincipal類
   
    WindowsPrincipal類作爲我們在開發中最常遇到的實現了IPrincipal接口的類,構造函數相當簡單:
  public WindowsPrincipal(WindowsIdentity ntIdentity);
   
    下面的代碼說明了如何創建一個WindowsPrincipal對象:
   
   
  WindowsIdentity wi = WindowsIdentity.GetCurrent();
  WindowsPrincipal wp = new WindowsPrincipal(wi);
   
    WindowsPrincipal類中需要注意的是下面這三個重載的IsInRole方法:
   
   
  public virtual bool IsInRole(int);
   
    第1個重載函數接受一個整型參數,該參數表示用戶組對應的RID(RID也就是與域相關聯的下級憑證(domain-relative subauthority)ID)。RID值定義在Platform SDK的頭文件Winnt.h中,Winnt.h中包括一些常見的用戶和組,比如DOMAIN_USER_RID_ADMIN、 DOMAIN_USER_RID_GUEST、DOMAIN_GROUP_RID_ADMINS、DOMAIN_GROUP_RID_USERS和DOMAIN_GROUP_RID_GUESTS等等,可以在.../Microsoft Visual Studio .NET/Vc7/PlatformSDK/Include文件夾中找到該文件。
   
   
  public virtual bool IsInRole(string);
   
    第2個重載函數接受一個字符串參數,該參數表示一個用戶組名稱,比如MYCOMPUTER/Developer(MachineName/GroupName)表示了機器名爲MYCOMPUTER的計算機上的Developer用戶組。不過對於系統內置的用戶組就不能這樣表示了,比如Administrators,不能表示爲MYCOMPUTER/Administrators,而應該像BUILTIN/Administrators這樣,不過這樣總覺得有點多餘,不夠自然。於是我們可以使用下面的重載函數。
  public virtual bool IsInRole(WindowsBuiltInRole);
   
    第3個重載函數接受一個WindowsBuiltInRole枚舉類型參數,下面就是WindowsBuiltInRole枚舉中定義的值:
   
    1、AccountOperator- 管理計算機上或域中的用戶帳號。
   
    2、Administrator- 可以任意訪問計算機或域
   
    3、BackupOperator- 可以在文件系統上執行備份和恢復操作。
   
    4、Guest- 和User類似,不過有更多的限制。
   
    5、PowerUser- 和Administrator地位相近,不過有一些限制。
   
    6、PrintOperator- 執行打印操作。
   
    7、Replicator- 在域中執行文件複製。
   
    8、SystemOperator- 管理計算機。
   
    9、User- 用戶不能執行危害系統或者影響整個系統的操作。      Permissions對象
   
    作爲.NET安全性兩個重要的分支,基於角色的安全性和代碼訪問安全都離不開一個重要的概念--權限(permissions)。在基於角色的安全性中,PrincipalPermission類用來檢查調用線程的用戶身份;而在代碼訪問安全中,從CodeAccessPermission派生的類則用來檢查執行當前方法的所有線程各自的權限。
   
    權限對象通過已有的安全策略來說明操作是否被允許或拒絕。對於代碼訪問安全權限(不過這不適用於用戶權限),.NET CLR提供了堆棧遍歷機制來確定所有的調用堆棧幀是否具有應有的權限。需要注意的一點是,如果permission對象爲null,那麼我們可以將它和PermissionState.None視爲等同的,說明沒有提供任何權限。權限常用於下面這些場合:
   
    1、定義執行代碼所需要的權限。
   
    2、系統的安全策略可以承認或拒絕代碼請求的權限。
   
    3、代碼通過Demand方法來保證(要求)它調用的代碼具有所需的權限。
   
    4、代碼還可以使用Assert,Deny或PermitOnly方法來跳過安全堆棧檢查機制。
   
    另外,我們還可以使用成組的權限,在PermissionSet類中我們就可以使用各種不同權限組集合。
   
    下面我們還是先來看看最常用到的PrincipalPermission類,至於另外一個常用的CodeAccessPermission類,稍後將會在代碼訪問安全的內容中詳細介紹。
   
    作爲PrincipalPermission類實現的三個接口之一,IPermission接口在PrincipalPermission類中有了舉足輕重的作用。IPermission接口提供了以下方法:
   
    1、Copy 創建並返回當前權限的相同副本。
   
    2、Demand 如果調用堆棧上的內容不滿足權限對象所指定的權限內容,則會在運行時引發SecurityException。該方法可以讓當前代碼不會被其他的惡意代碼所利用。
   
    3、Intersect 創建並返回一個權限,該權限是當前權限和指定權限的交集。
   
    4、IsSubsetOf 確定當前權限是否爲指定權限的子集。
   
    5、Union 創建一個權限,該權限是當前權限與指定權限的並集。
   
    在上面列出的方法中,Demand方法是最常用也是最重要的方法。Demand方法會對當前方法的所有調用者進行檢查以確定它們是否有足夠的權限訪問以指定的方式訪問特定的資源(通常的檢查方式是從調用堆棧上最新的調用方法開始通過執行完全的堆棧遍歷來滿足安全條件,但堆棧遍歷不是必須的,PrincipalPermission.Demand就沒有進行堆棧遍歷),如果檢查失敗的話,Demand方法會拋出SecurityException異常,只有沒有任何異常拋出的情況下,Demand方法才成功返回。
   
    下圖顯示的是IPermission接口的繼承層次:
   
  [imghttp://myarticle.enet.com.cn/images/200407/1090981312024.gif[/img]
   
    接着我們來討論PrincipalPermission類,這裏就需要我們前面所談到principal對象了,在PrincipalPermission類的實例執行Demand方法時,實際上是在判斷當前的principal對象是否匹配給定PrincipalPermission對象,如果不匹配的話,就拋出SecurityException。另外,Demand方法還可以用來強行讓principal對象的identity驗證通過,以便能不拋出異常,進行正常的操作。
   
    下面的代碼片斷簡要地說明了如何使用principalpermission對象:
   
   
  String user1 = "Abbott";
  String role1 = "StraightMan";
  PrincipalPermission PrincipalPerm1 =
  new PrincipalPermission(user1, role1);
  String user2 = "Costello";
  String role2 = "FunnyMan";
  PrincipalPermission pp =
  new PrincipalPermission(user2, role2);
  PrincipalPerm1.Union(pp).Demand();
   
    上面的代碼說明了如何使用合併兩個principalpermission對象,在合併之後,如果用戶是StraightMan角色的Abbott或是FunnyMan角色的Costello,那麼Demand將會成功返回。
   
    以上就是基於角色的安全性中所需要用到的基礎類和一些示例代碼,希望大家能夠從上面的內容中粗窺.NET安全性的門堂,能更好的應用.NET安全性來開發安全的程序。 
發佈了55 篇原創文章 · 獲贊 3 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章