如何实现 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 角色的成员
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章