在Silverlight+WCF中应用以角色为基础的安全模式(一)基础篇之角色为基础的安全模式简介...
??? 引言
最近一直在研究Silverlight+WCF應用中的安全問題,如何設計一個安全,又符合Silverlight和WCF的規范的應用呢?又可以將以前的角色為基礎的開發框架拿來主義呢?
我們知道WCF在安全方面提供了很多的綁定協議,可是Silverlight3+WCF的話,只有basicHttpBinding可以使用,這就使得我們的選擇不多,還有就是項目本身是一個互聯網應用,還是使用比較通用的角色為基礎的權限系統比較好。
這個系列有兩篇文章,一篇講解.NET框架提供我們的角色為基礎的安全模式,以及如何根據我們的需求,自定義角色為基礎的安全模式;一篇講解在Silverlight+WCF應用中,如何設計的一種角色為基礎的應用方法。
文中的代碼下載:/Files/virusswb/RetrieveSecurity_src.zip
正文
?
.NET中的角色為基礎的安全
.NET 框架使得 你在應用中實現以角色為基礎的安全模式非常容易。迫使安全有兩部分組成,認證和授權。認證就是驗證你的身份。應用程序驗證你就是你所聲明的人。通常的做法是用戶輸入用戶名和密碼,應用查找你輸入的用戶名,然后驗證你輸入的密碼是否匹配。更高級的做法是依賴生物認證,例如:指紋或者是視網膜,又或者是一張綁定了個人PIN碼的認證卡。如果認證失敗,用戶將不被允許進入系統,除非系統允許匿名訪問,意味著如果系統確認了你的身份,就授予你訪問權。授權就是確認用戶是否能操作系統的某項功能。授權依賴于已知的用戶身份以及和用戶相關的安全信息,基于這些安全信息,系統就可以批準或者拒絕用戶的請求。
.NET框架提供了通過Identity訪問用戶信息,通過principal訪問授權信息。Thread.CurrentPrincipal提供了當前線程的principal信息,默認情況下,它是一個非認證的授權。框架提供了兩種不同的principal,一個是windows principal,一個是通用的授權generic principal。Windows principal工作在windows 操作系統上。所以,當前運行的線程會映射到一個windows帳戶上。如果你正在運行一個windows form的應用程序,它就是一個用戶。
有兩個辦法可以訪問windows principal
// set that a principal should be attached to the thread and
// it should be a windows principal
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
// get hold of the windows principal
WindowsPrincipal MyPrincipal = (WindowsPrincipal)Thread.CurrentPrincipal;
// get the windows identity
WindowsIdentity MyIdentity = MyPrincipal.Identity;
?
通過調用當前程序域的SetPrincipalPolicy,你告訴框架當前線程需要附加的principal,你需要在第一次訪問principal之前做這些設置。調用Thread.CurrentPrincipal返回和當前線程綁定的principal。第一次這么做的時候,框架會查詢windows的帳戶信息創建一個windows身份和一個windows授權并且綁定到這個線程。從windows principal你可以訪問windows identity。另一個辦法是
// get an identity object for the windows user
WindowsIdentity Identity = WindowsIdentity.GetCurrent();
// get hold of the windows principal
WindowsPrincipal MyPrincipal = new WindowsPrincipal(Identity);
WindowsIdentity.GetCurrent()查詢windows帳戶信息,同時創建一個identity代表當前用戶,那樣你可以用這個identity創建一個principal。這 樣做的缺點就是每次都需要查詢windows帳戶,然后創建一個identity和一個principal。第一種方法每次都會使用相同的identity和principal。通用的principal允許你創建不綁定任何windows帳戶的一個principal和identity。
// create the generic identity GenericIdentity
Identity = new GenericIdentity("Administrator");
// define the roles to associate with the generic principal
string[] Roles = new string[2] { "Manager", "Architect" };
// create the generic principal
GenericPrincipal MyPrincipal = new GenericPrincipal(Identity,Roles);
// bind the generic principal to the thread
Thread.CurrentPrincipal = MyPrincipal;
首先創建一個通用的identity,你需要提供identity的名稱,因為他不綁定任何windows帳戶,需要一個用戶名。然后定義你想要這個授權擁有的角色,最后創建一個principal,然后提供identity和角色列表。然后你可以將這個授權綁定到當前線程。
創建自定義的授權principal和認證identity
.NET框架允許通過實現IPrincipal和IIdentity接口,來自定義授權和認證。本文下面的代碼,將展示如何創建一個數據庫驅動的認證和授權。
授權過程在用戶表中檢查提供的用戶名和密碼。授權成功之后,讀取用戶信息和用戶的安全組信息,查看用戶屬于那些安全組。
這些信息對于自定義認證和授權都是必要的。但是授權還可以更進一步,還可以檢查個人權限信息,例如:用戶是否被允許查看預算等。這些信息都是從SecurityRightAssign表中讀取出來,讓我們先創建一個自定義身份。
public class UserIdentity : IIdentity
{
// the authentication type for us is always database
private static string AuthenticationTypeString = "Database";
// hash table with all the user info we have
private Hashtable UserInfo;
// create the user identity; all user information is in the hashtable passed along
private UserIdentity(Hashtable UserInfo)
{
????? this.UserInfo = UserInfo;
}
//create a user identity and return it to the caller
public static UserIdentity CreateUserIdentity(Hashtable UserInfo)
{
????? return new UserIdentity(UserInfo);
}
}
?
UserIdentity實現了IIdentity接口,需要我們事先三個屬性。類型的構造函數被私有化,防止通過實例化來構造對象。你需要通過靜態方法CreateUserIdentity,傳遞一個HashTable結構的用戶類型,然后創建一個身份的實例。Name屬性返回這個身份的名稱。
// returns the name of the identity
public string Name
{
? get
? {
??? return
??? Convert.ToString(UserInfo[UserNameKey],CultureInfo.InvariantCulture).Trim();
? }
}
// returns if identity is authenticated or not
public bool IsAuthenticated
{
? get
? {
??? return true;
? }
}
// the type of authentication
public string AuthenticationType
{
? get
? {
??? return AuthenticationTypeString;
? }
}
IsAuthenticated屬性返回用戶是否被認證通過,在上面的代碼中用戶總是被認證功過,因為我們return true。如果你允許匿名訪問,你就可以為匿名用戶設置為false。最后一個屬性AuthenticationType返回的是驗證的類型,在我們的代碼中返回的是“Database”。WindowsIdentity返回的是NTLM,GenericIdentity返回的是空字符串或者是實例化GenericIdentity的時候傳遞的驗證類型。下面,我們里實現自定義的principal。
public class SecurityPrincipal : IPrincipal
{
// stores the list of security rights the user belongs too
private Hashtable SecurityGroups;
// stores the list of security rights the user has
private Hashtable SecurityRights;
// the user identity we create and associate with this principal
private UserIdentity TheUserIdentity;
// constructor: stores role and permission info and creates custom identity
private SecurityPrincipal(Hashtable SecurityGroups, Hashtable SecurityRights,
????????????????? Hashtable UserInfo)
{
????? this.SecurityGroups = SecurityGroups;
????? this.SecurityRights = SecurityRights;
????? // creates the IIdentity for the user and associates it with this IPrincipal
????? TheUserIdentity = UserIdentity.CreateUserIdentity(UserInfo);
}
// create the security principal and return it to the caller
public static SecurityPrincipal CreateSecurityPrincipal(Hashtable SecurityGroups,
????????????????? Hashtable SecurityRights, Hashtable UserInfo)
{
????? return new SecurityPrincipal(SecurityGroups,SecurityRights,UserInfo);
}
}
實現IPrincipal接口需要實現Identity屬性和IsInRole()方法,同樣的這個類型的構造函數也是私有的,防止通過實例化來創建對象。你需要調用靜態方法CreateSecurityPrincipal,傳遞一個hashtable類型的用戶信息,一個用戶所屬的角色信息,還有就是用戶在系統中的特權。這個類型的構造函數調用自定義的Identity方法的靜態函數CreateUserIdentity,將用戶信息傳遞給CreateUserIdentity方法,然后返回一個UserIdentity。CreateSecurityPrincipal方法返回一個自定義的principal實例。Identity屬性返回和這個principal相關聯的identity。
// returns the Identity object associated with the principal
public IIdentity Identity
{
????? get
????? {
??????????? return TheUserIdentity;
????? }
}
// checks if user belongs to role
public bool IsInRole(string Role)
{
????? return SecurityGroups.ContainsValue(Role);
}
// checks if user has permission
public bool HasPermission(string Permission)
{
????? return SecurityRights.ContainsValue(Permission);
}
IsInRole方法檢查用戶是否屬于角色,是通過檢查角色是否在hashtable類型的SecurityGroups中,然后返回true 或者false。我們自定義的principal還實現了一個方法HasPermission,它和IsInRole方法類似,但是檢查的是提供的權限是否在特權列表中,然后返回true或者false。
這些已經實現了自定義的identity和principal,下面的代碼解釋了信息是如何從數據庫中讀取,最后要做的就是去使用它。
public static IPrincipal SetSecurityPrincipal(Hashtable SecurityGroups,
?????????????????????????????? Hashtable SecurityRights, Hashtable UserInfo)
{
// set that we want to use authentication within the current app-domain;
// this means a thread will have a IPrincipal associated which is then
// used by the .NET security classes when checking role based security
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
// we switch to the new security principal only if we didn't do so already;
// protects us from the client calling the method multiple times
if (!(Thread.CurrentPrincipal is SecurityPrincipal))
{
? // create a new instance of the security principal which we can do only
? // within a class member as we marked the constructor private
? SecurityPrincipal TheSecurityPrincipal = new SecurityPrincipal(SecurityGroups,
????????????????? SecurityRights,UserInfo);
? // get a reference to the current security principal so the caller
? //can keep hold of it
? IPrincipal CurrentSecurityPrincipal = Thread.CurrentPrincipal;
? // set the security principal for the executing thread to the newly created one
? Thread.CurrentPrincipal = TheSecurityPrincipal;
? // return the current security principal;
? return CurrentSecurityPrincipal;
}
// return null if we don't switch the security principal
else
????? return null;
}
為了使用,我們在SecurityPrincipal類型上提供了一個靜態方法SetSecurityPrincipal。首先調用AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
這樣做看起來是錯誤的,因為我們想要的不是一個windows principal,而是一個自定義的principal。這么做只是為了保證我們擁有一個綁定到當前線程的principal,然后我們檢查綁定到當前線程的principal是否是自定義的principal類型。如果是的話,我們什么都不需要做,因為我們已經為當前線程分配了我們自定義的principal,這確保了調用者在多線程的環境中調用不會產生負面的問題。只是在第一次我們會發現沒有綁定到自定義的principal,這時候我們創建自定義的principal,創建自定義的identity,并且綁定到當前線程。如果調用者需要的話,我們返回當前principal給他。
在使用Thread.CurrentPrincipal訪問用戶信息的時候,會檢查用戶的角色和特權,這些都可以通過在principal上調用IsInRole或者是訪問identity來實現。如果你想檢查用戶的特權,你可以使用從Thread.CurrentPrincipal中獲取的principal的HasPermission方法來實現。
public bool CheckSecurityPermission(string Permission)
{
????? // if the current IPrincipal is of the same type as our custom
????? // security principal then go and check the security right
????? if (Thread.CurrentPrincipal is SecurityPrincipal)
????? {
????????? SecurityPrincipal Principal = (SecurityPrincipal)
??????????????????????????????????????? Thread.CurrentPrincipal;
????????? // returns whether the user has the permission or not
????????? return Principal.HasPermission(Permission);
????? }
????? // if we have a standard IPrincipal in use then we can not check
????? // the permission and we always return false
????? else
????????? return false;
}
如果你正在創建一個新的應用程序域線程,你不想為每一個線程設置自定義的principal,你可以為每一個新創建的線程創建一個默認的principal。設置默認principal一定要在第一次第一次訪問principal之前設置Thread.CurrentPrincipal。
// create the custom principal
SecurityPrincipal MyPrincipal = SecurityPrincipal.CreateSecurityPrincipal(
????????????????? AppDomain.CurrentDomain.SetThreadPrincipal);
// set the custom principal as the app domain policy
AppDomain.CurrentDomain.SetThreadPrincipal(MyPrincipal);
你設置默認principal,只需要在應用程序域設置一次。在應用程序域設置多次會引發PolicyException異常。
?
示例代碼
?
示例代碼演示的是一個windows form程序,首先需要用戶登錄(在數據庫中已經有兩個用戶,virus和swb,密碼和用戶名一致)。btnLogon_Click()事件和btnLogin按鈕關聯,調用DataLayer.CheckUserNameAndPassword().用來驗證用戶,調用DataLayer.RetrieveUserInformation().來獲取用戶信息,最后通過調用DataLayer.RetrieveSecurityInformation().來獲取用戶所屬的角色和權限信息,在獲取了用戶信息、角色信息和權限信息之后,使用SecurityPrincipal.SetSecurityPrincipal()創建一個principal和identity,并且綁定到當前線程。
從示例中看出用戶屬于是三個角色,全部的權限,和用戶信息,可以檢查用戶是否屬于某一個角色,是否具有某一個權限,CheckSecurityRoles() and CheckSecurityPermissions()返回用戶是否屬于一個角色,是否有一個權限。logoff 按鈕的 LogOff_Click()方法恢復原始的principal,并且返回登陸界面,允許另外一個用戶登錄,繼續前面的處理過程。
在示例文件夾中你會發現一個叫做RetrieveSecurity.bak的文件,它是數據庫的備份文件。恢復數據庫,配置app.config文件中的連接字符串。你可以在數據庫中添加用戶、角色和權限信息。示例展示了在.NET 的角色為基礎的安全模型之后,如何實現數據庫驅動的驗證和安全模型。
?
總結
大多數應用都需要通過角色和權限來實現用戶驗證和安全模型。.NET框架使得這些變得容易,幾行代碼,就改變了windows賬號和安全組的影響。使用自定義的identity和principal可以很容易的擴展角色為基礎的安全框架,示例代碼展示的就是如何實現數據庫驅動的角色權限系統。
參考文獻
【1】Role-Based Security? Microsoft
【2】Introduction to Role-Based Security in .NET? Klaus Salchner
【3】在Identity 增加自己的屬性 部門,并且使用access mdb文件實現角色驗證? iHqq
?
感謝上面這些機構和作者的無私奉獻。
轉載于:https://www.cnblogs.com/virusswb/archive/2010/03/01/1675414.html
總結
以上是生活随笔為你收集整理的在Silverlight+WCF中应用以角色为基础的安全模式(一)基础篇之角色为基础的安全模式简介...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 赶走最令人不愉悦的一类BUG,你准备好了
- 下一篇: 与客户“调情”