本页内容
目标
本章的目标是:
• | 创建一个实现 IPrincipal 接口的类,该接口可与基于 .NET 角色的安全性结合在一起使用。 |
返回页首
适用范围
本章适用于以下产品和技术:
• | Microsoft Windows® XP 或 Windows 2000 Server (Service Pack 3) 以及更高版本的操作系统 |
• | .NET Framework 版本 1.0 (Service Pack 2) 和更高版本 |
• | Microsoft Visual C#® .NET |
返回页首
如何使用本章内容
若要学好本章内容:
• | 您必须具有使用 Visual C# .NET 和 Microsoft Visual Studio® .NET 进行编程的经验。 |
• | 您必须具有使用 Visual Studio .NET 开发环境的经验。 |
• | 您必须具有使用 ASP.NET 开发 Web 应用程序的经验。 |
• | 阅读第 3 章身份验证和授权。这一章介绍了基于 .NET 角色的安全性并讨论了 IPrincipal 接口。 |
• | 阅读第 8 章 ASP.NET 安全性。这一章深入介绍了 IPrincipal 以及如何在 ASP.NET Web 应用程序中使用基于 .NET 角色的安全性。 |
• | 阅读如何将窗体身份验证用于 Active Directory 。这一章提供了有关如何从 Active Directory 获取用户帐户信息的详细信息。 |
• | 阅读如何将窗体身份验证用于 SQL Server 2000 。这一章提供了有关如何将 Microsoft SQL Server™ 2000 用作用户帐户数据库的详细信息。 |
返回页首
摘要
Microsoft® .NET Framework 提供了两种实现 IPrincipal 接口的方法: WindowsPrincipal 和 GenericPrincipal 。这些类提供了基于角色的授权检查功能,对于大多数应用程序方案而言,这些功能已经足够了。
但是,在某些情况下,您可能需要开发自己的 IPrincipal 实现方案,以提供自定义功能。本章介绍了如何实现自定义 IPrincipal 类以及如何在使用窗体身份验证的 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( " 角色 1", " 角色 2", " 角色 3" ) CustomPrincipal.IsInAnyRole( " 角色 1", " 角色 2", " 角色 3" ) |
• | 您想实现额外的方法或属性以返回数组中角色的列表。例如: string[] roles = CustomPrincipal.Roles; |
• | 您想让应用程序强制实施角色分级逻辑。例如,高级管理员在层次结构中被认为高于普通管理员。可以使用如下方法进行测试: CustomPrincipal.IsInHigherRole("Manager"); CustomPrincipal.IsInLowerRole("Manager"); |
• | 您想实现惰性的角色列表初始化。例如,只有在需要进行角色检查时,才能动态加载角色列表。 |
本章介绍如何实现自定义的 IPrincipal 类以及如何在使用窗体身份验证的 ASP.NET 应用程序中将其用于基于角色的授权。
返回页首
创建一个简单的 Web 应用程序
此过程创建一个新的 ASP.NET Web 应用程序。该应用程序将包含两个页面:默认页(仅允许通过身份验证的用户进行访问)和登录页(用于搜集用户凭据)。
• | 创建一个简单的 Web 应用程序
1. | 启动 Visual Studio .NET ,然后新建一个名为 CustomPrincipalApp 的 C# ASP.NET Web 应用程序。 | 2. | 将 WebForm1.aspx 重命名为 Logon.aspx 。 | 3. | 向 Logon.aspx 添加表 1 中列出的控件,创建一个登录窗体。 表 1 : Logon.aspx 控件
控件类型 | 文本 | ID | 标签 | 用户名: | - | 标签 | 密码 | - | 文本框 | - | txtUserName | 文本框 | - | txtPassword | 按钮 | 登录 | btnLogon | | 4. | 将密码文本框控件的 TextMode 属性设置为 Password 。 | 5. | 在解决方案资源管理器中,右键单击 “CustomPrincipalApp” ,指向 “ 添加 ” ,然后单击 “ 添加 Web 窗体 ” 。 | 6. | 输入 “default.aspx” 作为新的窗体名,然后单击 “ 打开 ” 。 | |
返回页首
配置 Web 应用程序的窗体身份验证
• | 编辑应用程序的 Web.config 文件,以便配置应用程序的窗体身份验证
1. | 使用解决方案资源管理器打开 Web.config 。 | 2. | 查找 <authentication> 元素,并将 mode 属性更改为 Forms 。 | 3. | 将 <forms> 元素添加为 <authentication> 元素的子元素,并按以下所示设置 loginUrl 、 name 、 timeout 和 path 属性: <authentication mode="Forms"> <forms loginUrl="logon.aspx" name="AuthCookie" timeout="60" path="/"> </forms> </authentication> | 4. | 在 <authentication> 元素下面添加以下 <authorization> 元素。这样将只允许通过身份验证的用户访问该应用程序。先前为 <authentication> 元素建立的 loginUrl 属性会将未通过身份验证的请求重定向到 Logon.aspx 页。 <authorization> <deny users="?" /> <allow users="*" /> </authorization> | |
返回页首
为通过身份验证的用户生成身份验证票证
此过程编写代码,为通过身份验证的用户生成身份验证票证。身份验证票证是 ASP.NET FormsAuthenticationModule 使用的一种 cookie 类型。
身份验证代码通常涉及在自定义数据库或 Microsoft Active Directory® 目录服务中查找提供的用户名和密码。
有关执行这些查找的信息,请参见本指南中的以下章节:
• | 如何将窗体身份验证用于 Active Directory 。 |
• | 如何将窗体身份验证用于 SQL Server 2000 。 |
• | 为通过身份验证的用户生成身份验证票证
1. | 打开 Logon.aspx.cs 文件,并将下面的 using 语句添加到文件顶部的现有 using 语句的下面: using System.Web.Security; | 2. | 将以下私有帮助器方法添加到名为 IsAuthenticated 的 WebForm1 类中,该类用于验证用户名和密码,从而验证用户的身份。该代码假设所有用户名和密码的组合都有效。 private bool IsAuthenticated( string username, string password ) { // 查找为清楚起见而省略的代码 // 此代码通常将针对 SQL 数据库或 Active Directory // 验证用户名和密码组合 // 模拟已通过身份验证的用户 return true; } | 3. | 添加以下名为 GetRoles 的私有帮助器方法,该方法用于获取用户所属的角色集: private string GetRoles( string username, string password ) { // 查找为清楚起见而省略的代码 // 此代码通常将从数据库表中查找 角色列表。 // 如果用户已经为 Active Directory 进行了身份验证,则可能使用 // 该用户所属的安全组和 / 或 // 通讯组列表 // 此 GetRoles 方法返回一个包含角色的以管道符分隔的 // 字符串,而不是返回一个数组,因为字符串格式便于 // 存储在身份验证票证 /cookie 中,就像用户数据那样 return "Senior Manager|Manager|Employee"; } | 4. | 在 “ 设计器 ” 模式下显示 Logon.aspx 窗体,然后双击 “ 登录 ” 按钮以创建一个单击事件处理程序。 | 5. | 添加一个对 IsAuthenticated 方法的调用,提供通过登录窗体捕获的用户名和密码。将返回值分配给一个布尔型变量,指示用户是否通过身份验证。 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, // 版本 txtUserName.Text, // 用户名 DateTime.Now, // 创建 DateTime.Now.AddMinutes(60),// 到期 false, // 永久 roles ); // 用户数据 | 8. | 添加代码,为该票证创建一个加密的字符串表示,并将它作为数据存储在 HttpCookie 对象内。 // 现在对票证进行加密。 string encryptedTicket = FormsAuthentication.Encrypt(authTicket); // 创建一个 cookie 并将加密的票证添加到 // 该 cookie 作为数据。 HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket); | 9. | 将 cookie 添加到返回给用户浏览器的 cookie 集合中。 // 将该 cookie 添加到传出 cookie 集合。 Response.Cookies.Add(authCookie); | 10. | 将用户重定向到最初请求的页面。 // 将用户重定向到最初请求的页面 Response.Redirect( FormsAuthentication.GetRedirectUrl( txtUserName.Text, false )); } | |
返回页首
创建一个实现和扩展 IPrincipal 的类
此过程创建一个实现 IPrincipal 接口的类。它还将其他方法和属性添加到该类中,以便提供基于角色的额外授权功能。
• | 创建一个实现和扩展 IPrincipal 的类
1. | 将名为 CustomPrincipal 的新类添加到当前项目中。 | 2. | 在 CustomPrincipal.cs 的顶部添加以下 using 语句: 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. | 添加以下两个公用方法以提供基于角色的扩展检查功能: // 查看主体是否在所有指定的角色集合中 public bool IsInAllRoles( params string [] roles ) { foreach (string searchrole in roles ) { if (Array.BinarySearch(_roles, searchrole) < 0 ) return false; } return true; } // 查看主体是否在任意指定的角色集合中 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. | 从解决方案资源管理器中,打开 global.asax 。 | 2. | 切换到代码视图,然后在文件的顶部添加以下 using 语句: using System.Web.Security; using System.Security.Principal; | 3. | 找到 Application_AuthenticateRequest 事件处理程序并添加以下代码,以便从随请求一起传输的 cookie 集合中获取窗体身份验证 cookie : // 提取窗体身份验证 cookie string cookieName = FormsAuthentication.FormsCookieName; HttpCookie authCookie = Context.Request.Cookies[cookieName]; if(null == authCookie) { // 没有身份验证 cookie 。 return; } | 4. | 添加以下代码,从窗体身份验证 cookie 中提取身份验证票证并进行解密: FormsAuthenticationTicket authTicket = null; try { authTicket = FormsAuthentication.Decrypt(authCookie.Value); } catch(Exception ex) { // 记录异常情况详细信息(为简便起见,已省略) return; } if (null == authTicket) { // 无法解密 Cookie 。 return; } | 5. | 添加以下代码,解析当用户最初通过身份验证后附加到票证的以管道符分隔的角色名列表: // 创建票证后,为 UserData 属性指定一个 // 以管道符分隔的角色名字符串。 string[] roles = authTicket.UserData.Split('|'); | 6. | 添加以下代码,使用从票证名中获取的用户名创建一个 FormsIdentity 对象,并创建一个包含此标识以及用户角色列表的 CustomPrincipal 对象: // 创建一个标识对象 FormsIdentity id = new FormsIdentity( authTicket ); // 该主体将通过整个请求。 CustomPrincipal principal = new CustomPrincipal(id, roles); // 将新的主体对象附加到当前的 HttpContext 对象 Context.User = principal; | |
返回页首
测试应用程序
此过程向 default.aspx 页中添加代码,显示附加到当前 HttpContext 对象上的 CustomPrincipal 对象中的信息,并确认已正确构造该对象并将其分配给当前的 Web 请求。它还测试新类支持的基于角色的功能。
• | 测试应用程序
1. | 在解决方案资源管理器中,双击 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( " 已通过身份验证的标识为: " + cp.Identity.Name ); Response.Write( "<p>" ); | 5. | 添加以下代码,以使用 CustomPrincipal 类支持的标准 IsInRole 方法和附加的 IsInAnyRoles 和 IsInAllRoles 方法测试当前已验证标识的角色成员身份: if ( cp.IsInRole("Senior Manager") ) { Response.Write( cp.Identity.Name + " 在 " + "Senior Manager 角色 " 中 ); Response.Write( "<p>" ); } if ( cp.IsInAnyRoles("Senior Manager", "Manager", "Employee", " Sales") ) { Response.Write( cp.Identity.Name + " 在某个指定的 角色中 "); Response.Write( "<p>" ); } if ( cp.IsInAllRoles("Senior Manager", "Manager", "Employee", " Sales") ) { Response.Write( cp.Identity.Name + " 在所有指定的 角色中 " ); Response.Write( "<p>" ); } else { Response.Write( cp.Identity.Name + " 不在所有指定的角色中 " ); Response.Write("<p>"); } if ( cp.IsInRole("Sales") ) Response.Write( " 用户在 Sales 角色中 <p>" ); else Response.Write( " 用户不在 Sales 角色中 <p>" ); | 6. | 在解决方案资源管理器中,右键单击 default.aspx ,然后单击 “ 设为起始页 ” 。 | 7. | 在 “ 构建 ” 菜单中,单击 “ 构建解决方案 ” 。 | 8. | 按 CTRL +F5 键以运行应用程序。因为 default.aspx 已配置为起始页,所以这是最初请求的页面。 | 9. | 当您被重定向到登录页(因为您最初并没有身份验证票证)时,请输入用户名和密码(输入什么都可以),然后单击 “ 登录 ” 。 | 10. | 确认您被重定向到 default.aspx ,并显示了用户标识和正确的角色细节。用户是 Senior Manager 、 Manager 和 Employee 角色的成员,而不是 Sales 角色的成员。 | |