如何實現 IPrincipal

摘要

Microsoft .NET Framework 提供了兩種實現 IPrincipal 接口的方法:WindowsPrincipalGenericPrincipal。這些類提供了滿足大多數應用程序方案所需的基於角色的授權檢查功能。

但在某些情況下,可能要開發自己的提供自定義功能的 IPrincipal 實現。本模塊描述瞭如何實現自定義 IPrincipal 類並在使用 Forms 身份驗證的 ASP.NET 應用程序中將這個類用於基於角色的授權。

預備知識

.NET Framework 提供了 WindowsPrincipal 類和 GenericPrincipal 類,它們分別爲 Windows 和非 Windows 身份驗證機制提供了基本的角色檢查功能。這兩個類都實現了 IPrincipal 接口。爲了在授權時使用,ASP.NET 要求這些對象存儲在 HttpContext.User 中。對於基於 Windows 的應用程序,這些對象必須存儲在 Thread.CurrentPrincipal 中。

這些類提供的功能可以滿足大多數應用程序方案的需要。應用程序可以顯式調用 IPrincipal. IsInRole 方法來執行編程角色檢查。PrincipalPermission 類的 Demand 方法在用於要求調用方屬於特定角色(通過聲明方式或強制方式)時,也產生對 IPrincipal.IsInRole 的調用。

在某些情況下,可能要通過創建實現 IPrincipal 接口的類來開發自己的用戶實現。實現 IPrincipal 的任何類都可以用於 .NET 授權。

實現自己的 IPrincipal 類的原因包括:

您希望擴展的角色檢查功能。可能需要一些允許您檢查特定用戶是否是多個角色的一個成員的方法。例如:
CustomPrincipal.IsInAllRoles( "Role1", "Role2", "Role3" )
CustomPrincipal.IsInAnyRole( "Role1", "Role2", "Role3" )
需要實現在數組中返回角色列表的額外的方法或屬性。例如:
string[] roles = CustomPrincipal.Roles;
希望應用程序強制具有角色層次結構邏輯。例如,Senior Manager 在層次結構中的層次要高於 Manager。使用如下方法可以測試這一點。
CustomPrincipal.IsInHigherRole("Manager");
CustomPrincipal.IsInLowerRole("Manager");
希望實現角色列表的惰性初始化。例如,只有在請求執行角色檢查時,您才能動態加載角色列表。

本模塊描述瞭如何實現自定義 IPrincipal 類,並在使用 Forms 身份驗證的 ASP.NET 應用程序中將其用於基於角色的授權。

創建一個簡單的 Web 應用程序

此過程創建一個新的 ASP.NET Web 應用程序。此應用程序將包含兩個頁,一個是隻允許經過身份驗證的用戶訪問的默認頁,另一個是用於收集用戶憑據的登錄頁。

要創建一個簡單的 Web 應用程序,請執行下列操作:

1. 啓動 Visual Studio .NET 並創建一個名爲 CustomPrincipalApp 的新的 C# ASP.NET Web 應用程序。
2. 將 WebForm1.aspx 重命名爲 Logon.aspx。
3. 將表 1 中列出的控件添加到 Logon.aspx 中來創建登錄窗體。
表 1:Logon.aspx 控件
控件類型 文本 ID

Label

User Name:

-

Label

Password

-

Text Box

-

txtUserName

Text Box

-

txtPassword

Button

Logon

btnLogon

 

1. 將密碼文本框控件的 TextMode 屬性設置爲 Password
2. Solution Explorer 中,右鍵單擊 CustomPrincipalApp,指向 Add,然後單擊 Add Web Form
3. 輸入 default.aspx 作爲新窗體的名稱,然後單擊 Open

 

配置 Web 應用程序進行 Forms 身份驗證

要編輯應用程序的 Web.config 文件來配置應用程序進行 Forms 身份驗證,請執行下列操作:

1. 使用 Solution Explorer 來打開 Web.config
2. 定位到 <authentication> 元素並將 mode 屬性更改爲 Forms
3. 將下列 <forms> 元素作爲 <authentication> 元素的子元素進行添加,並設置 loginUrlnametimeoutpath 屬性,如下所示:
<authentication mode="Forms">
  <forms loginUrl="logon.aspx" name="AuthCookie" timeout="60" path="/">
  </forms>
</authentication>
4. 將下列 <authorization> 元素添加到 <authentication> 元素下。這一步的目的是隻允許經過身份驗證的用戶訪問應用程序。前面建立的 <authentication> 元素的 loginUrl 屬性將未經身份驗證的請求重定向到 Logon.aspx 頁。
<authorization> 
  <deny users="?" />
  <allow users="*" />
</authorization>

 

爲經過身份驗證的用戶生成身份驗證票

此過程編寫代碼來爲經過身份驗證的用戶生成身份驗證票。身份驗證票是由 ASP.NET FormsAuthenticationModule 使用的一種 cookie。

身份驗證代碼通常涉及根據自定義數據庫或 Microsoft Active Directory® 目錄服務查找提供的用戶名和密碼。

要爲經過身份驗證的用戶生成身份驗證票,請執行下列操作:

1. 打開 Logon.aspx.cs 文件並將下列 using 語句添加到位於文件頂部的現有 using 語句下。
using System.Web.Security;
2. 將下列私有 Helper 方法添加到名爲 IsAuthenticated 的 WebForm1 類中,此類用於驗證用戶名和密碼來對用戶進行身份驗證。此代碼假定所有用戶名和密碼組合都是有效的。
private bool IsAuthenticated( string username, string password )
{
  // Lookup code omitted for clarity
  // This code would typically validate the user name and password
  // combination against a SQL database or Active Directory
  // Simulate an authenticated user
  return true;
}
3. 添加下列名爲 GetRoles 的私有 Helper 方法,此方法用於獲得用戶所屬的角色集合。
private string GetRoles( string username, string password )
{
  // Lookup code omitted for clarity
  // This code would typically look up the role list from a database table.
  // If the user was being authenticated against Active Directory, the
  // Security groups and/or distribution lists that the user belongs to may be 
  // used instead

  // This GetRoles method returns a pipe delimited string containing roles
  // rather than returning an array, because the string format is convenient 
  // for storing in the authentication ticket / cookie, as user data
  return "Senior Manager|Manager|Employee";
}
4. 在 Designer 模式下顯示 Logon.aspx 窗體,然後雙擊 Logon 按鈕創建一個單擊事件處理程序。
5. 添加一個對 IsAuthenticated 方法的調用,提供通過登錄窗體捕獲的用戶名和密碼。將返回值賦給一個 bool 類型的變量,此變量指出用戶是否已經過身份驗證。
bool isAuthenticated = IsAuthenticated( txtUserName.Text, 
                                        txtPassword.Text );
6. 如果用戶已經過身份驗證,則添加對 GetRoles 方法的調用來獲得用戶的角色列表。
if (isAuthenticated == true )
{
  string roles = GetRoles( txtUserName.Text, txtPassword.Text );
7. 創建一個包含用戶名、截止時間和用戶所屬的角色列表的新窗體身份驗證票。注意,身份驗證票的用戶數據屬性用於存儲用戶的角色列表。還要注意,儘管票/cookie 是否是永久性的取決於您採用的應用程序方案,但下列代碼創建了一個非永久性票。
// Create the authentication ticket
  FormsAuthenticationTicket authTicket = new 
       FormsAuthenticationTicket(1,                          // version
                                 txtUserName.Text,           // user name
                                 DateTime.Now,               // creation
                                 DateTime.Now.AddMinutes(60),// Expiration
                                 false,                      // Persistent
                                 roles );                    // User data
8. 添加代碼來創建票的加密字符串表示形式,並將其作爲數據存儲在 HttpCookie 對象中。
// Now encrypt the ticket.
  string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
  // Create a cookie and add the encrypted ticket to the 
  // cookie as data.
  HttpCookie authCookie = 
               new HttpCookie(FormsAuthentication.FormsCookieName,
                              encryptedTicket);
9. 將 cookie 添加到返回給用戶瀏覽器的 cookie 集合中。
// Add the cookie to the outgoing cookies collection. 
  Response.Cookies.Add(authCookie);
10. 將用戶重定向到最初的請求頁。
// Redirect the user to the originally requested page
  Response.Redirect( FormsAuthentication.GetRedirectUrl(
                                                txtUserName.Text, 
                                                false ));
}

 

創建一個實現和擴展 Iprincipal 的類

此過程創建一個實現 IPrincipal 接口的類。它還將其他方法和屬性添加到類中,以提供其他基於角色的授權功能。

要創建一個實現和擴展 IPrincipal 的類,請執行下列操作:

1. 將一個名爲 CustomPrincipal 的新類添加到當前項目中。
2. 將下列 using 語句添加到 CustomPrincipal.cs 的頂部。
using System.Security.Principal;
3. IPrincipal 接口派生 CustomPrincipal 類。
public class CustomPrincipal : IPrincipal
4. 將下列私有成員變量添加到類中,以維持與當前用戶和用戶的角色列表關聯的 IIdentity 對象。
private IIdentity _identity;
private string [] _roles;
5. 修改類的默認構造函數以接受 IIdentity 對象和角色的數組。使用提供的值初始化私有成員變量,如下所示。
public CustomPrincipal(IIdentity identity, string [] roles)
{
  _identity = identity;
  _roles = new string[roles.Length];
  roles.CopyTo(_roles, 0);
  Array.Sort(_roles);
}
6. 實現由 IPrincipal 接口定義的 IsInRole 方法和 Identity 屬性,如下所示。
// IPrincipal Implementation
public bool IsInRole(string role)
{
  return Array.BinarySearch( _roles, role ) >= 0 ? true : false;
}
public IIdentity Identity
{
  get
  {
    return _identity;
  }
}
7. 添加下列兩個提供了擴展的基於角色的檢查功能的公共方法。
// Checks whether a principal is in all of the specified set of roles
public bool IsInAllRoles( params string [] roles )
{
  foreach (string searchrole in roles )
  {
    if (Array.BinarySearch(_roles, searchrole) < 0 )
      return false;
  }
  return true;
}
// Checks whether a principal is in any of the specified set of roles
public bool IsInAnyRoles( params string [] roles )
{
  foreach (string searchrole in roles )
  {
    if (Array.BinarySearch(_roles, searchrole ) > 0 )
      return true;
  }
  return false;
}

 

創建 CustomPrincipal 對象

此過程實現了一個應用程序身份驗證事件處理程序,並根據身份驗證票中包含的信息構造一個 CustomPrincipal 對象來代表經過身份驗證的用戶。

要構造 CustomPrincipal 對象,請執行下列操作:

1. Solution Explorer 中,打開 global.asax
2. 切換到代碼視圖,然後將下列 using 語句添加到文件的頂部。
using System.Web.Security;
using System.Security.Principal;
3. 定位到 Application_AuthenticateRequest 事件處理程序並添加下列代碼,從隨請求傳遞的 cookie 集合中獲得窗體身份驗證 cookie。
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];

if(null == authCookie)
{
  // There is no authentication cookie.
  return;
}
4. 添加下列代碼以從窗體身份驗證 cookie 中提取和解密身份驗證票。
FormsAuthenticationTicket authTicket = null;
try
{
  authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch(Exception ex)
{
  // Log exception details (omitted for simplicity)
  return;
}

if (null == authTicket)
{
  // Cookie failed to decrypt.
  return; 
}
5. 添加下列代碼來解析出在最初對用戶進行身份驗證時附加到票上的管道分隔的角色名稱列表。
// When the ticket was created, the UserData property was assigned a
// pipe delimited string of role names.
string[] roles = authTicket.UserData.Split('|');
6. 添加下列代碼以創建一個用戶名從票名稱中獲得的 FormsIdentity 對象和一個將此身份與用戶角色列表一起包含的 CustomPrincipal 對象。
// Create an Identity object
FormsIdentity id = new FormsIdentity( authTicket ); 

// This principal will flow throughout the request.
CustomPrincipal principal = new CustomPrincipal(id, roles);
// Attach the new principal object to the current HttpContext object
Context.User = principal;

 

測試應用程序

此過程將代碼添加到 default.aspx 頁以顯示已附加到當前 HttpContext 對象的 CustomPrincipal 對象中的信息,用於確認是否已正確構造此對象且分配給當前的 Web 請求。它還測試受新類支持的基於角色的功能。

要測試應用程序,請執行下列操作:

1. Solution Explorer 中,雙擊 default.aspx
2. 雙擊 default.aspx Web 窗體來顯示頁加載事件處理程序。
3. 滾動到文件頂部並將下列 using 語句添加到現有 using 語句下。
using System.Security.Principal;
4. 返回到頁加載事件處理程序並添加下列代碼,以顯示附加到與當前 Web 請求關聯的 CustomPrincipal 的標識名稱。
CustomPrincipal cp = HttpContext.Current.User as CustomPrincipal;
Response.Write( "Authenticated Identity is: " +  
                cp.Identity.Name );
Response.Write( "<p>" );
5. 添加下列代碼,使用標準 IsInRole 方法和另外由 CustomPrincipal 類支持的 IsInAnyRolesIsInAllRoles 方法來測試當前經過身份驗證的身份的角色成員關係。
if ( cp.IsInRole("Senior Manager") )
{
  Response.Write( cp.Identity.Name + " is in the " + "Senior Manager Role" );
  Response.Write( "<p>" );            
}

  if ( cp.IsInAnyRoles("Senior Manager", "Manager", "Employee", "Sales") )
  {
    Response.Write( cp.Identity.Name + " is in one of the specified roles");
    Response.Write( "<p>" );
  }
  if ( cp.IsInAllRoles("Senior Manager", "Manager", "Employee", "Sales") )
  {
    Response.Write( cp.Identity.Name + " is in ALL of the specified roles" );
    Response.Write( "<p>" );
  }
  else
  {
    Response.Write( cp.Identity.Name + 
                    " is not in ALL of the specified roles" );
    Response.Write("<p>");
  }

  if ( cp.IsInRole("Sales") )
    Response.Write( "User is in Sales role<p>" );
  else
    Response.Write( "User is not in Sales role<p>" );
6. Solution Explorer 中,右鍵單擊 default.aspx,然後單擊 Set As Start Page
7. Build 菜單上,單擊 Build Solution
8. CTRL+F5 運行此應用程序。由於 default.aspx 被配置爲啓動頁,所以這是最初的請求頁。
9. 當您被重定向到登錄頁時(因爲最初您沒有身份驗證票),輸入用戶名和密碼(可隨意輸入),然後單擊 Logon
10. 確認您被重定向到 default.aspx 且顯示了用戶身份和正確的角色詳細信息。此用戶爲 Senior Manager、Manager 和 Employee 角色的成員,但不是 Sales 角色的成員
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章