概述
在過去的相當長一段時間內,計算機及信息犯罪的比例正在逐漸升高。美國聯邦調查局的計算機安全組織在2001年的研究調查中發現85%企業的企業安全受到侵害。在對這些企業進行調查之后提出的財物損失報告中指出,合計損失為3億7千7百萬美元,比起2000年的2億6千5百萬美金增加了42%。由此可清楚的看出,計算機及信息犯罪的發生次數越來越頻繁,其所造成的損失也越來越大,另外,犯罪的手段也越來越豐富,令企業安全人員防不勝防。因此企業必須有所行動來保護有價值的信息資產。自然而然的,安全性在現在的程序開發中越來越成為一個不可忽視的問題。
傳統的安全模型將安全性建立在用戶以及用戶組的機制上來提供隔離和訪問控制。這就意味著用戶要么可以運行全部代碼,要么都不能運行。而這正是現在大多數操作系統采取的安全模型,即使現在看來這種機制也是很有效的,但是深入思考之后我們可以發現這種機制存在的假設是所有的代碼都具有相同的信任程度。當所有的代碼都是來自你或者你的系統管理員,那么這種假設是行之有效的。但是現在大多數計算機都連上了Internet之后,這種"都行"或者"都不行"的方式就不那么好了。.NET框架提供了全面的安全系統,足以應付現在已有的大多數安全性問題。在.NET框架中提供了與傳統模型相似的,但卻是由開發人員自定義的安全模型,稱為基于角色的安全性(Role-Based Security)?;诮巧陌踩宰钪匾母拍罹褪鞘跈啵≒rincipals)和標識(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位的用戶的帳號標記(aclearcase/" target="_blank" >ccount 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); |
//創建一個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接口的繼承層次:
接著我們來討論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安全性來開發安全的程序。
原文轉自:http://www.anti-gravitydesign.com