要想使用基于角色的身份验证,必须定制该过程。在介绍基于角色的安全的基础结构和它的整个体系结构时,我们已经看到各种身份验证的模块实际上都利用我们能够使用的相同事件,特别是可以在 Global.asax 文件中给处理程序附加一个 AuthenticateRequest 事件。
对于自定义身份验证的实现,首先使用 GenericPrincipal 和 GenericIdentity 对象,这两个对象可以为我们提供合理和简单的实现。万一它们不够用,我们还可以继承和扩展它们,甚至可以直接在自定义类中实现 IPrincipal 和 IIdentity 接口。
主要处理事件是 AuthenticateRequest 事件,在该事件处理程序中,可以先执行一些操作,例如验证用户的用户名和密码(与数据库做比对),然后将 Context.User 的属性设置成自定义的主体和身份对象。与任何其他的身份验证方案一样,该安全环境也将通过页、用户控件、后台编码页来跟随用户。只要运行了代码,我们都可以访问这些对象。
为了定制身份验证,需要在某—点截取该过程。在这种情况下,我们将把代码保留在它所在的Login.aspx页中不动,让Forms身份验证模块执行所有目前它正在进行的工作,直到到达某一特定点。再次看一看应用程序中一个典型请求的步骤,并注意在什么地方重写该默认行为:
(1) 用户请求 Default.aspx——要进入应用程序的初始请求。
(2) Application_AuthenticateRequest 事件被引发,因为 IsAuthenticated=false,所以 Forms 模块重定向到Login.aspx?ReturnUrl=...。
(3) 重定向产生对另一页的请求,这次是Login.aspx页。
(4) Application_ AuthenticateRequest 事件被引发——这次通过访问 Login.aspx 页引发该事件。该模块可以识别出这是处理身份验证的页,因此不会再次定位到本身。
(5) 用户输入凭证并提交——向自身提交该窗体实际上是再次发出一个新的请求。
(6) Application_AuthenticateRequest 事件再次被引发。又是 IsAuthenticated=false,因此该模块不会再重新定位。
(7) 代码按照数据库进行检查,返回OK——模块保存具有身份验证的cookie和UserID,并重新定向到ReturnUrl(ReturnUrl)。
(8) 作为重新定位的结果,重新请求 Default.aspx 页。这次,已设置了身份验证cookie。
(9) Application_AuthenticateRequest 事件被引发,这是我们第一次获得 IsAuthenticated=true。应用程序从这里开始处理,并基于用附加到身份验证 cookie 的 UserID 从数据库检索信息,来重新生成 GenericPrincipal 和 GenericIdentity 的定制版本。我们使用新的 Principal 代替 Context.User。
从上面的顺序可以看到,在最后的AuthenticateRequest中,IsAuthenticated属性第一次返回True。从现在开始,这是发给身份验证请求的惟一响应,因为身份验证cookie将会出现,并且Forms身份验证模块将根据它恢复UserID。实际上我们是在Forms身份验证模块处理cookie之后定制该身份验证机制的。
奖我们要访问的页称为受限制页。它可以是应用程序中任何受保护的资源。
使用 FormsAuthentication.RedirectFromLoginPage()方法之后,实际上再次请求了受限制的页——但是这次设置了安全令牌。这时可以重写 Forms 身份验证实现的默认行为,可以将 Context.User 的属性设置成一个能够更好的表示我们需要的对象。该对象是包含与当前用户相关联的角色的 GenericPrincipal 对象。
过程是这样的:
修改Global.asax文件。
首先引入我们要使用的命名空间
using System.Security.Principal;
using System.Configuration;
编写Application_AuthenticateRequest事件
首先获得HttpApplication的对象,我们使用的应用程序请求的IsAuthenticated属性,而不是以前使用的Context.User.Identity.IsAuthenticated属性,这样使用的原因是,第一次访问该页时,Context.User的属性没有被初始化,因而会导致一个异常。基于这方面的因素,可以使用下面的代码来代替上面的代码:
if((Context.User != null) && (Context.User.Identity.IsAuthenticated))
{}
如果传递IsAuthenticated检查,这就意味着 Forms 身份验证已经完成了工作,UserID放置在了通常我们能够发现的地方:在Context.User.Identity.Name属性中。这是在Login.aspx页中已经完成的工作。
然后使用一个包含用户所属实际角色的对象来代替空的 GenericPrincipal 对象(由 Forms 身份验证模块创建的)。因此,在Application_AuthenticateRequest()处理程序中,我们可以获得UserID,并使用它发出一个数据库查询,以查看他是否对应于管理员。
GenericPrincipal构造函数接收一个身份和包含所属角色的一个字符串数组。我们重新使用 Forms 身份验证所创建的身份,它一直附在我们使用的Context.User.Identity属性上,因为我们不需要对它做什么改变
GenericPrincipal ppal;
ppal = new GenericPrincipal(Context.User.Identity,roles);
最后,将新创建的主体附在Context.User属性上
Context.User = ppal;
如果您回到显示执行这些动作的流程图,就会发现接着被处理的是请求的原始页。因此当执行到达我们页的代码时,将访问我们附属的新的角色识别主体。在Default.aspx页的Page_Load()方法中用此显示—个到用户的管用页的链接:
this.whlk_ManageUser.Visible = User.IsInRole("Admin");
User是Page对象的一个提供Context.User的简化形式的属性,并且它的IsInRole()方法允许我们检查它是否属于一个特定角色。
我们有选择性地在页中使用自定义的、角色识别的新主体来显示信息。但是,仅仅隐藏或显示链接是不够的:如果非管理员用户知道管理页的位置和名称,那么他们就可以在浏览器的地址栏中输入地址和访问应该限制的资源!为了解决该问题,我们将在限制访问的文件夹(例如:Admin文件夹)中添加一个配置文件(Web.config文件),以保护该文件夹中所有文件的安全。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<authorization>
<allow roles="Admin"></allow>
<deny roles="User"></deny>
</authorization>
</system.web>
</configuration>
现在作为非管理员用户进行登陆,试着在浏览器的地址栏中直接输入URL来看看会出现什么情况——您应该被重定向到登录页。所有这些检查都会自动进行,重新定位到登录页是有道理的,因为无需角色的用户甚至可能是一个未验证的用户。只有属于管理员角色的用户才能够看到我们构建的管理页,无论他们如何试图访问它。
例子:下载