打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
基于角色的授权 C#

本文档是Visual C# 教程 (转至 Visual Basic 教程 ) 。

在本教程中,我们首先了解 Roles 框架如何将用户的角色与其安全上下文相关联。然后探讨如何应用基于角色的URL 授权规则。最后,我们将讨论如何通过声明式和编码,改变ASP.NET 网页上的数据显示和功能。

<<前一篇教程下一篇教程>>

简介

在《基于用户的授权 》教程中,我们学习了如何使用URL 授权来规定哪些用户能够访问某个网页集。只需在Web.config 中写上一些标记,我们就能够让ASP.NET 只允许经过身份验证的用户访问一个网页。或者我们可以规定,只允许用户Tito 和Bob 访问;或规定,除Sam 以外的所有经过身份验证的用户都允许访问。

除了 URL 授权之外,我们还探讨了如何通过声明和编码,基于访问用户来控制网页上的数据显示和功能。特别是,我们还创建了一个网页,在上面列出了当前目录的内容。所有人都可以访问该网页,但只有经过身份验证的用户能够看到文件的内容,而只有Tito 能够删除文件。

按逐个用户应用授权规则,会让人坠入一场记帐式的恶梦中。一个更便于维护的方法是使用基于角色的授权。令人欣慰的是,我们所配备的应用授权规则的工具,无论是针对角色还是针对用户帐户,都可以良好地工作。URL 授权规则可以指定角色而不是用户。至于LoginView 控件,它能向经过身份验证的用户和匿名用户显示不同的网页内容,也能设置为基于登录用户的角色显示不同的内容。而Roles API 中包括确定登录用户角色的方法。

在本教程中,我们首先了解 Roles 框架如何将用户的角色与其安全上下文相关联。然后探讨如何应用基于角色的URL 授权规则。最后,我们将讨论如何通过声明和编码,改变ASP.NET 网页上的数据显示和功能。 让我们开始吧!

了解角色如何与用户的安全上下文相关联

每当一个请求进入ASP.NET 管道,它便关联上一个安全上下文,其中包括请求者的身份信息。当使用表单身份验证时,用一个身份验证票证作为身份标记。正如我们在《 表单身份验证概述 》和《 表单身份验证配置和高级主题 》教程中所讨论过的,是在AuthenticateRequest 事件 中,由FormsAuthenticationModule 负责确定请求者的身份。

如果找到了一个有效的、没有过期的身份验证票证,FormsAuthenticationModule 则对其解码,确定请求者的身份。它创建一个新的GenericPrincipal 对象并将其分配到 HttpContext.User 对象。像 GenericPrincipal 这样的主体(principal )的目的,是识别经过身份验证的用户的用户名和他所属的角色。该目的是很显然的,因为所有主体对象都有一个Identity 属性和一个 IsInRole(roleName) 方法。但是,FormsAuthenticationModule 却不记录角色信息,而且它所创建的 GenericPrincipal 对象也不指定任何角色。

如果启用了 Roles 框架,在FormsAuthenticationModule 之后就会进入 RoleManagerModule HTTP Module ,并在 PostAuthenticateRequest 事件 中识别经过身份验证的用户的角色,上述事件在AuthenticateRequest 事件之后激发。如果该请求是由一个经过身份验证的用户发出,则RoleManagerModule 覆盖由 FormsAuthenticationModule 所创建的 GenericPrincipal 对象,用一个RolePrincipal 对象 替换它。RolePrincipal 类使用Roles API 确定该用户属于哪些角色。

图 1 说明了使用表单身份验证和Roles 框架时的 ASP.NET 管道流程。首先执行的是 FormsAuthenticationModule ,通过身份验证票证识别用户,并创建一个新的GenericPrincipal 对象。然后,进入RoleManagerModule ,并用 RolePrincipal 对象覆盖 GenericPrincipal 对象。

如果一个匿名用户访问网站,无论FormsAuthenticationModule 还是 RoleManagerModule 都不创建一个主体对象。

图1 :使用表单身份验证和 Roles 框架时,对经过身份验证的用户的 ASP.NET 管道事件(单击此处查看实际大小的图像

在Cookie 中缓存角色信息

RolePrincipal 对象的IsInRole(roleName) 方法调用 Roles.GetRolesForUser 获取用户角色,以确定该用户是否是roleName 的成员。当使用 SqlRoleProvider 时,这引出一个到角色数据库的查询。当使用基于角色的URL 授权规则时,对于受基于角色的 URL 授权规则保护的网页的每次访问请求,都调用RolePrincipal 的 IsInRole 方法。但并不需要在每次请求时都到数据库去查阅角色信息,Roles 框架有一个选项,将用户角色存到一个cookie 里。

如果 Roles 框架配置为将用户角色缓存到 cookie 里,RoleManagerModule 就在 ASP.NET 管道的 EndRequest 事件 期间创建该 cookie 。当RolePrincipal 对象创建后,PostAuthenticateRequest 请求中也使用该 cookie 。如果该 cookie 有效且没有过期,cookie 里的数据就被解析并用来显示用户角色,不需要让RolePrincipal 通过调用Roles 类来确定用户角色。图 2 说明了该流程。

图2 :在 Cookie 中存储用户角色信息以提高效率(单击此处查看实际大小的图像

在默认状态下,并不启用角色缓存的cookie 机制。可以通过Web.config 中的<roleManager> 配置标记将其启用。在《 创建和管理角色 》教程中,我们讨论过如何使用<roleManager> 元素 指定角色提供者,所以在您的应用程序的Web.config 文件中应该已经有该元素。将角色缓存cookie 配置指定为<roleManager> 元素的属性,表1 对此做了一个总结。

注意 : 表1 列出的配置,是角色缓存 cookie 的属性。如果想了解有关 cookies 的更多信息,了解它们是如何工作的以及其各种属性,请参阅Cookies 教程

属性

说明

cacheRolesInCookie

布尔值,表示是否使用 cookie 缓存。默认是 false 。

cookieName

角色缓存 cookie 的名称。默认值是 “.ASPXROLES” 。

cookiePath

角色名称 cookie 的路径。路径属性让开发者能将 cookie 的范围限制在一个特定的目录层次上。默认值是“/” ,它通知浏览器将身份验证票证 cookie 传给域里的任何请求。

cookieProtection

指明使用什么技术保护角色缓存cookie 。允许的值是:All ( 默认 ); Encryption; None; 和Validation 。有关这些保护等级的更多信息,请复习《 表单身份验证配置和高级主题 》 教程中的步骤3 。

cookieRequireSSL

布尔值,表示是否要求用 SSL 连接传送身份验证cookie 。 默认值为 false 。

cookieSlidingExpiration

布尔值,表示用户在一个会话期内每次访问网站时是否都要重设cookie 的超时时间。 默认值为 false 。 只有当 createPersistentCookie 设为 true 时,该值才有效。

cookieTimeout

规定一个时间(以分钟计),超过时间后,身份验证票证cookie 过期。默认值是 30 。只有当 createPersistentCookie 设为 true 时,该值才有效。

createPersistentCookie

布尔值,指定角色缓存 cookie 是一个会话 cookie 还是一个持久性 cookie 。若该值设为 false ( 默认) ,就是使用会话 cookie ,当浏览器关闭时就将它删除了。若该值为true ,就是使用持久性 cookie ,它在超过了 cookieTimeout 规定的时间后失效;这时间是从它创建时起,还是从上次访问时起,取决于cookieSlidingExpiration 的值。

domain

规定 cookie 的域值。默认值是一个空字符串,这使浏览器使用它发布所在的域(例如www.yourdomain.com )。在这种情况下,当向子域(如admin.yourdomain.com )发出请求时,并传送cookie 。如果想要将 cookie 传到所有子域,就必须将 domain 属性设置为 “yourdomain.com” 。

maxCachedResults

规定缓存在 cookie 中的角色名称的最大数量。默认值是 25 。如果超过了 maxCachedResults 规定的角色数量,RoleManagerModule 不为用户再创建 cookie 。这时,RolePrincipal 对象的 IsInRole 方法将使用 Roles 类确定用户角色。

设立 maxCachedResults 该属性的原因是因为许多用户代理商不允许cookies 大于 4,096 字节。所以设立该属性减少超过上述字节限制的可能性。如果您有特别长的角色名,则可以考虑将maxCachedResults 的值设置得小一些;反之,如果您的角色名特别短,则可以考虑增大该值。

表1 : 角色缓存Cookie 的配置选项

配置我们的应用程序为使用不持久性的角色缓存cookies 。为此,在 Web.config 中修改 <roleManager> 元素,包括下列与 cookie 有关的属性:

  1. <roleManager enabled="true"    
  2.           defaultProvider="SecurityTutorialsSqlRoleProvider"    
  3.           cacheRolesInCookie="true"    
  4.           createPersistentCookie="false"    
  5.           cookieProtection="All">    
  6.      <providers>    
  7.       ...    
  8.      </providers>    
  9. </roleManager>

我是这样修改 <roleManager> 元素的,在其中加了三个属性:cacheRolesInCookie 、createPersistentCookie 和 cookieProtection 。设置cacheRolesInCookie 为 true ,RoleManagerModule 就会自动地将用户角色缓存到一个cookie 中,而不用在每次请求时都去查看用户角色信息。我明确地分别将createPersistentCookie 和 cookieProtection 属性设为 false 和All 。其实,从技术上说我并不需要指定这些属性的值,因为在这里设的都是它们的默认值。但是我将它们写在上面,是为了清楚地表明我不使用持久性cookie ,而且 cookie 是经过加密的、有效的。

这样就可以了 !从此以后,Roles 框架将将用户角色缓存到 cookie 中。如果用户浏览器不支持 cookie ,或他们的 cookie 因为某种原因被删除了或丢失了,这也不要紧,在没有cookie 可用的情况下(失效或过期了),RolePrincipal 对象会去使用 Roles 类。

注意 :Microsoft 的Patterns & Practices 小组不提倡使用持久性角色缓存 cookie 。因为拥有了角色缓存 cookie ,就足以证明成员资格。如果黑客用某种方法获得了某用户有效的cookie 数据,他就可以模仿那个用户。如果用户浏览器的cookie 是持久性的,则增加了发生这种事情的可能性。有关该安全性建议的更多信息,以及其他安全性内容,请参阅 Security Question List for ASP.NET 2.0

步骤1 :定义基于角色的URL 授权规则

正如我们在《 基于用户的授权 》教程中所学过的,URL 授权提供了一种手段,基于逐个用户或基于逐个角色,对一个网页集的访问做出限制。在Web.config 中,用<authorization> 元素 的<allow> 和<deny> 子元素,规定URL 授权规则。除了在以前教程中讨论过的基于用户的授权规则之外,每个<allow> 和<deny> 子元素还可以包括:

  • 一个特定的角色
  • 一个用逗号分隔的角色列表

例如,URL 授权规则批准担任 Administrators 和 Supervisors 角色的那些用户访问,但拒绝所有其他用户访问:

  1. <authorization>
  2.      <allow roles="Administrators, Supervisors" />
  3.      <deny users="*" />
  4. </authorization>

以上标记中的 <allow> 元素表示,Administrators 和Supervisors 角色是允许的;而 <deny> 元素则指明,所有用户都是不允许的。

让我们配置我们的应用程序,让ManageRoles.aspx 、UsersAndRoles.aspx 和CreateUserWizardWithRoles.aspx 页都只允许担任 Administrators 角色的那些用户访问,而RoleBasedAuthorization.aspx 页则保留允许所有访问者进入。

为此,首先将一个 Web.config 文件加到Roles 文件夹中。

图3 :加入一个 Web.config 文件到 Roles 目录(单击此处查看实际大小的图像

然后,添加以下配置标记到 Web.config 中:

  1. <?xml version="1.0"?>    
  2. <configuration>    
  3.      <system.web>    
  4.           <authorization>    
  5.                <allow roles="Administrators" />    
  6.                <deny users="*"/>    
  7.           </authorization>    
  8.      </system.web>
  9.  
  10.      <!-- Allow all users to visit RoleBasedAuthorization.aspx -->    
  11.      <location path="RoleBasedAuthorization.aspx">    
  12.           <system.web>    
  13.                <authorization>    
  14.                     <allow users="*" />    
  15.                </authorization>    
  16.           </system.web>    
  17.      </location>    
  18. </configuration>

在 <system.web> 段落中的<authorization> 元素表示,只有担任 Administrators 角色的用户能够访问 Roles 目录下的ASP.NET 资源。<location> 元素定义了对 RoleBasedAuthorization.aspx 页的URL 授权规则的一个替换设置,允许所有用户访问此页。

在保存了在 Web.config 上所做的这些改动之后,以一个不是Administrators 角色的用户身份登录,然后试着访问一个这些被保护的页面。UrlAuthorizationModule 马上探测出,您没有获得对所请求资源的访问许可,因此,FormsAuthenticationModule 将将您重新引到登录页面。然后,登录页面将您再引到UnauthorizedAccess.aspx 页(见图4 )。发生最后该从登录页到UnauthorizedAccess.aspx 页的引导,是因为我们在《 | 基于用户的授权 》教程的步骤 2 里,在登录页里加入了代码。特别是,如果查询字符串中包含ReturnUrl 参数,登录页就自动将任何经身份验证的用户引导到UnauthorizedAccess.aspx 页,因为该参数表示用户是在试图访问一个没有向他授权的网页后抵达登录页的。

图4 :只有担任 Administrators 角色的用户能够查阅被保护的网页(单击此处查看实际大小的图像

现在,退出登录再重新以一个担任Administrators 角色的用户登录。现在,您应该能够查阅三个被保护的网页了。

图5 :Tito 能够访问 UsersAndRoles.aspx 页是因为他担任着 Administrators 角色(单击此处查看实际大小的图像

注意 在规定URL 授权规则时,无论是对角色还是对用户,要记住的很重要的一点是,规则是从上至下逐一分析的。一旦发现一个匹配,用户就被批准或拒绝访问,取决于匹配是在<allow> 还是在<deny> 元素中找到的。如果没有找到匹配,就批准用户访问。因此,如果您想限制一个或多个用户帐户的访问,就必须要用一个<deny> 元素作URL 授权配置的最后一个元素。如果您的URL 授权规则中没有包括一个<deny> 元素,就相当于批准所有用户访问。 有关如何分析URL 授权规则的更全面的讨论,请复习《 基于用户的授权 》教程中的“UrlAuthorizationModule 如何使用授权规则批准或拒绝访问”一节。

步骤2 :基于当前登录用户的角色限制页面功能

有了 URL 授权规则,就很容易制定一个粗略的授权规则,允许或拒绝哪些身份的用户访问某一特定的网页(或一个文件夹及其子文件夹里的所有网页)。但在某些情况下,我们想允许所有用户访问一个网页,但想基于访问用户的角色限制网页上的功能。这就需要基于用户的角色显示或隐藏数据,或对那些属于某一特定角色的用户提供附加的功能。

这种精细的基于角色的授权规则,可以通过声明或编码(或两者的结合)实现。下面一节,我们就将看看如何通过LoginView 控件制定声明方式的精细授权。再往后,我们接着探讨通过编码完成此项任务的技术。但是,在我们考察如何制定精细授权规则之前,我们首先需要创建一个网页,其功能依赖于访问其用户的角色。

我们创建一个网页,它在一个 GridView 中列出系统中所有的用户帐户。GridView 中将包括每个用户的用户名、邮箱地址、上次登录日期和对该用户的评论。除了显示每个用户的信息之外,该GridView 还包括编辑和删除功能。我们最初创建该网页时,允许所有用户使用编辑和删除功能。在“使用LoginView 控件”和“通过编码限制功能”两节中,我们将会看到如何基于访问用户的角色,启用或不启用这些功能。

注意 : 我们准备创建的ASP.NET 网页使用一个 GridView 控件列出用户帐户。由于本系列教程集中讨论表单身份验证、授权、用户帐户和角色,我们不想用太多时间讨论GridView 控件的内部功能。尽管本教程详细提供了设置该网页的一步步的指导,但我们并不探究为什么要做出某些选择,或某些特定属性将呈现什么输出效果等细节。对GridView 控件的更全面的说明,请参阅我的Working with Data in ASP.NET 2.0 系列教程。

首先,在 Roles 文件夹中打开RoleBasedAuthorization.aspx 网页。将一个 GridView 拖到网页的 Designer 上并将其ID 设置为 UserGrid 。一会儿我们将写代码调用 Membership.GetAllUsers 方法,并将产生的 MembershipUserCollection 对象绑定到该 GridView 。MembershipUserCollection 对系统中的每个用户帐户包含一个 MembershipUser 对象;而 MembershipUser 对象具有属性 UserName 、Email 、LastLoginDate 等。

在我们写代码绑定用户帐户到该表格之前,我们先定义GridView 的列。在 GridView 的智能标记中,单击 Edit Columns 链接,调出一个 Fields 对话框(见图 6 )。在对话框的左下角,不勾选 Auto-generate fields 复选框。因为我们想要该 GridView 包括编辑和删除功能,加入一个CommandField ,并设置其 ShowEditButton 和 ShowDeleteButton 属性为True 。然后,加入四个列分别显示 UserName 、Email 、LastLoginDate 和 Comment 属性。对两个只读属性(UserName 和LastLoginDate )使用 BoundField ,对两个可编辑列(Email 和 Comment )使用 TemplateField 。

用第一个 BoundField 显示UserName 属性,设置其 HeaderText 和 DataField 属性为 “UserName” 。该列是不能编辑的,所以设置其 ReadOnly 属性为 True 。再配置 LastLoginDate BoundField ,将其 HeaderText 设为 “Last Login” ,其DataField 设为 “LastLoginDate” 。我们还要规定一下该 BoundField 的输出格式,让其只显示日期(而不是显示日期和时间)。为此,设置该BoundField 的 HtmlEncode 属性为 False ,并且设其DataFormatString 属性为 “{0:d}” 。ReadOnly 属性也设为 True 。

设置其他两个 TemplateField 的HeaderText 属性为 “Email” 和 “Comment” 。

图6 :通过 Fields 对话框配置 GridView 的列(单击此处查看实际大小的图像

我们现在需要为 “Email” 和“Comment” 的 TemplateField 定义 ItemTemplate 和 EditItemTemplate 。添加一个Web 标签控件到每个 ItemTemplate 上,并将它们的 Text 属性分别绑定到 Email 和 Comment 属性。

对于 “Email” TemplateField ,添加一个名为Email 的 TextBox 到其 EditItemTemplate ,并使用双向数据绑定,将其 Text 属性绑定到 Email 属性。添加一个RequiredFieldValidator 和一个 RegularValidator 到EditItemTemplate ,以保证在编辑 Email 属性时输入正确格式的邮箱地址。对于“Comment” TemplateField ,添加一个名为 Comment 的多行 TextBox 到其 EditItemTemplate 。设置 TextBox 的Columns 和 Rows 属性分别为40 和 4 ,然后用双向数据绑定将其 Text 属性绑定到 Comment 属性。

在配置完这些 TemplateField 之后,它们的声明标记应该如下所示:

  1. <asp:TemplateField HeaderText="Email">    
  2.      <ItemTemplate>    
  3.           <asp:Label runat="server" ID="Label1" Text='<%# Eval("Email") %>'></asp:Label>    
  4.      </ItemTemplate>    
  5.      <EditItemTemplate>    
  6.           <asp:TextBox runat="server" ID="Email" Text='<%# Bind("Email") %>'></asp:TextBox>    
  7.           <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"    
  8.                ControlToValidate="Email" Display="Dynamic"    
  9.                ErrorMessage="You must provide an email address." 
  10.                SetFocusOnError="True">*</asp:RequiredFieldValidator>    
  11.           <asp:RegularValidator ID="RegularValidator1" runat="server"    
  12.                ControlToValidate="Email" Display="Dynamic"    
  13.                ErrorMessage="The email address you have entered is not valid. Please fix 
  14.                this and try again."    
  15.                SetFocusOnError="True"    
  16.                Validation="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">*
  17.           </asp:RegularValidator>    
  18.      </EditItemTemplate>    
  19. </asp:TemplateField>
  20.  
  21. <asp:TemplateField HeaderText="Comment">    
  22.      <ItemTemplate>    
  23.           <asp:Label runat="server" ID="Label2" Text='<%# Eval("Comment") %>'></asp:Label>    
  24.      </ItemTemplate>
  25.       <EditItemTemplate>  
  26.          <asp:TextBox runat="server" ID="Comment" TextMode="MultiLine"  
  27.               Columns="40" Rows="4" Text='<%# Bind("Comment") %>'>  
  28.          </asp:TextBox>  
  29.     </EditItemTemplate> 
  30. </asp:TemplateField>

在编辑或删除一个用户帐户时,我们需要知道用户的UserName 属性值。设置 GridView 的 DataKeyNames 属性为 “UserName” ,这样,通过 GridView 的DataKeys 集合就可获得该信息。

最后 , 在页面上添加一个 ValidationSummary 控件 , 将其 ShowMessageBox 属性设为 True ,ShowSummary 属性设为 False 。 做了这些设置后,当操作者编辑用户帐户时没有输入或输入了不正确的邮箱地址后,ValidationSummary 就会弹出一个客户端警告信息。

  1. <asp:ValidationSummary ID="ValidationSummary1"
  2.                runat="server"
  3.                ShowMessageBox="True"
  4.                ShowSummary="False" />

我们现在已经完成了本页的声明标记。下一步的任务是绑定用户帐户集合到GridView 。加入一个名为 BindUserGrid 的方法到 RoleBasedAuthorization.aspx 页的代码文件类,它绑定由 Membership.GetAllUsers 返回的 MembershipUserCollection 到 UserGrid GridView 。在第一次打开网页时的 Page_Load 事件处理程序中调用该方法。

  1. protected void Page_Load(object sender, EventArgs e)    
  2. {    
  3.      if (!Page.IsPostBack)    
  4.           BindUserGrid();    
  5. }
  6.  
  7. private void BindUserGrid()    
  8. {    
  9.      MembershipUserCollection allUsers = Membership.GetAllUsers();    
  10.      UserGrid.DataSource = allUsers;    
  11.      UserGrid.DataBind();    
  12. }

放置好这些代码之后,通过浏览器访问该网页。如图7 所示,在一个 GridView 中列出了系统中每个用户的信息。

图7 :UserGrid GridView 列出系统中每个用户的信息(单击此处查看实际大小的图像

注意 :UserGrid GridView 是在一个不能分页的界面上列出所有用户信息的。这种简单的表格界面并不适于有几十个或更多用户的场合。可选方法之一是设置GridView 为可分页的。Membership.GetAllUsers 方法有两个重载:一个不接收输入参数返回所有用户,另一个接收页面索引和页面大小的整数值,仅返回一个特定的用户子集。使用第二个重载可以更高效地翻页查看用户,因为返回的只是适合显示的用户帐户的子集,而不是全体用户。如果您有几千个用户帐户,就可能需要考虑使用筛选器形式的界面了。例如,只显示UserName 由选定字母打头的那些用户。Membership.FindUsersByName方法 用于建立筛选器形式的用户界面是很方便的。在以后的教程中,我们将探讨如何建立这样的界面。

在绑定到一个配置完好的数据源控件如SqlDataSource 或 ObjectDataSource 后,GridView 控件提供了内置的编辑和删除功能。但是,UserGrid GridView 的数据是通过编码绑定的,所以我们必须写代码来执行这两项任务。特别是,我们需要为GridView 的 RowEditing 、RowCancelingEdit 、RowUpdating 和RowDeleting 事件创建事件处理程序,这些事件是在访问者单击了GridView 的 Edit 、Cancel 、Update 或 Delete 按钮后引发的。

首先为 GridView 的RowEditing 、RowCancelingEdit 和RowUpdating 事件创建事件处理程序,然后添加以下代码:

  1. protected void UserGrid_RowEditing(object sender, GridViewEditEventArgs e)
  2. {
  3.      // Set the grid's EditIndex and rebind the data
  4.      UserGrid.EditIndex = e.NewEditIndex;
  5.      BindUserGrid();
  6. }
  7.  
  8. protected void UserGrid_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
  9. {
  10.      // Revert the grid's EditIndex to -1 and rebind the data
  11.      UserGrid.EditIndex = -1;
  12.      BindUserGrid();
  13. }
  14.  
  15. protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
  16. {
  17.      // Exit if the page is not valid
  18.      if (!Page.IsValid)
  19.           return;
  20.  
  21.      // Determine the username of the user we are editing
  22.      string UserName = UserGrid.DataKeys[e.RowIndex].Value.ToString();
  23.  
  24.      // Read in the entered information and update the user
  25.      TextBox EmailTextBox = UserGrid.Rows[e.RowIndex].FindControl("Email"as TextBox;
  26.      TextBox CommentTextBox = UserGrid.Rows[e.RowIndex].FindControl("Comment"as TextBox;  
  27.     // Return information about the user  
  28.     MembershipUser UserInfo = Membership.GetUser(UserName);  
  29.     // Update the User account information  
  30.     UserInfo.Email = EmailTextBox.Text.Trim();  
  31.     UserInfo.Comment = CommentTextBox.Text.Trim();  
  32.     Membership.UpdateUser(UserInfo);  
  33.     // Revert the grid's EditIndex to -1 and rebind the data  
  34.     UserGrid.EditIndex = -1;  
  35.     BindUserGrid(); 
  36. }

RowEditing 和RowCancelingEdit 事件处理程序就是设置 GridView 的EditIndex 属性,然后重新绑定这些用户帐户到该表格。更多的内容在RowUpdating 事件处理程序里。该事件处理程序首先确认数据是有效的,然后从DataKeys 集合中获取被编辑的用户帐户的 UserName 值。然后通过编码定位两个 TemplateField 上 EditItemTemplate 中的 Email 和Comment 文本框。它们的 Text 属性中包含编辑完的邮箱地址和评论。

为了用 Membership API 更新用户帐户,我们需要首先获得用户的信息,这通过调用Membership.GetUser(userName) 来实现。然后,返回的 MembershipUser 对象的Email 和 Comment 属性由编辑界面中输入到两个文本框里的值所更新。最后,通过调用Membership.UpdateUser 保存所做的这些修改。RowUpdating 事件处理程序的最后,将 GridView 转回到它编辑前的状态。

下一步,创建 RowDeleting 事件处理程序,并加入以下代码:

  1. protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
  2. {
  3.      // Determine the username of the user we are editing
  4.      string UserName = UserGrid.DataKeys[e.RowIndex].Value.ToString();
  5.  
  6.      // Delete the user
  7.      Membership.DeleteUser(UserName);
  8.  
  9.      // Revert the grid's EditIndex to -1 and rebind the data
  10.      UserGrid.EditIndex = -1;
  11.      BindUserGrid(); 
  12. }

以上事件处理程序首先从 GridView 的DataKeys 集合获取 UserName 的值,然后,将该 UserName 值送到 Membership 类的 DeleteUser 方法 。DeleteUser 方法从系统中删除用户帐户,包括相应的成员数据(例如该用户属于哪些角色)。删除完用户之后,表格的EditIndex 设为 -1 (考虑到这种情况:当用户单击 Delete 按钮时,另一行正处在编辑状态),调用BindUserGrid 方法。

注意 : 这的Delete 按钮在删除用户帐户前没有要求用户做任何确认。我建议您还是加上某些方式的用户确认,以减少帐户被误删的危险。一个最简单的操作确认方式就是通过一个客户端确认对话框。有关这项技术的更多信息,参阅《删除时添加客户端确认 》 。

让我们验证一下本页的功能。您现在应该能够编辑任何一个用户的邮箱地址和评论,也能够删除任何一个用户帐户。因为所有用户都可以访问到RoleBasedAuthorization.aspx 页,所以现在任何一个用户(甚至匿名用户)都能够访问本页,并能够编辑和删除用户帐户!我们下面准备修改该网页,让只有担任Supervisors 和 Administrators 角色的用户能够编辑用户的邮箱地址和评论,而只有Administrators 能够删除用户帐户。

在“使用 LoginView 控件”这节里,我们将考察如何使用 LoginView 控件专门针对用户角色显示说明。如果一个有着Administrators 角色的人访问该网页,我们就显示有关如何编辑和删除用户的说明。如果一个担任Supervisors 角色的用户到了该网页,我们就向他显示编辑用户信息的说明。但如果访问者是匿名用户,或既不是Supervisors 也不是 Administrators 角色,我们就向他显示为何不能编辑或删除用户的解释信息。在“通过编码限制功能”一节,我们将写代码,根据用户的角色通过编码显示或隐藏Edit 和 Delete 按钮。

使用LoginView 控件

我们在以前的教程中学过,使用LoginView 控件可以针对经身份验证的用户和匿名用户显示不同的界面。其实,LoginView 控件还可以用来针对用户角色显示不同的界面。我们现在根据访问用户的角色,使用LoginView 控件显示不同的说明。

首先在 UserGrid GridView 上面添加一个LoginView 。我们过去学过,LoginView 控件有两个内置的模板:AnonymousTemplate 和 LoggedInTemplate 。在这两个模板中都输入一段简短的信息,通知用户他们不能编辑或删除任何用户信息。

  1. <asp:LoginView ID="LoginView1" runat="server">
  2.      <LoggedInTemplate>
  3.           You are not a member of the Supervisors or Administrators roles. Therefore you
  4.           cannot edit or delete any user information.
  5.      </LoggedInTemplate>
  6.      <AnonymousTemplate>
  7.           You are not logged into the system. Therefore you cannot edit or delete any user
  8.            information.  
  9.     </AnonymousTemplate> 
  10. </asp:LoginView>

除了 AnonymousTemplate 和LoggedInTemplate 之外,LoginView 控件还可以包括一些 RoleGroup ,这是特别针对角色的模板。每个 RoleGroup 包含一个单一的属性 Roles ,它指定 RoleGroup 用于哪些角色。Roles 属性可以设为单个角色(如 ”Administrators” ),或是以逗号隔开的一组角色(如“Administrators, Supervisors” )。

为管理这些 RoleGroup ,从控件的智能标记中单击 Edit RoleGroups 链接,打开 RoleGroup Collection Editor 。添加两个新的 RoleGroup 。设置第一个 RoleGroup 的 Roles 属性为 “Administrators” ,设置第二个为 “Supervisors” 。

图8 :通过 RoleGroup Collection Editor 管理 LoginView 的角色模板(单击此处查看实际大小的图像

单击 OK 关闭RoleGroup Collection Editor 。这也就修改了LoginView 的声明标记,使之包括一个<RoleGroups> 段落,其中在 RoleGroup Collection Editor 中定义的每个RoleGroup 都带一个 <asp:RoleGroup> 子元素。此外,在 LoginView 智能标记上的 Views 下拉列表里,最初只包括AnonymousTemplate 和 LoggedInTemplate ,现在有了新加入的 RoleGroups 。

编辑这两个 RoleGroup ,使页面上对担任Supervisors 角色的用户显示如何编辑用户帐户的说明,对担任Administrators 角色的用户显示编辑和删除的说明。 做了这些改变后 , LoginView 的声明标记应如下所示。

  1. <asp:LoginView ID="LoginView1" runat="server">
  2.      <RoleGroups>  
  3.          <asp:RoleGroup Roles="Administrators">  
  4.               <ContentTemplate>  
  5.                    As an Administrator, you may edit and delete user accounts.  
  6.                    Remember: With great power comes great responsibility!  
  7.               </ContentTemplate>  
  8.          </asp:RoleGroup>  
  9.          <asp:RoleGroup Roles="Supervisors">  
  10.               <ContentTemplate>  
  11.                    As a Supervisor, you may edit users&#39; Email and Comment information.  
  12.                    Simply click the Edit button, make your changes, and then click Update.  
  13.               </ContentTemplate>  
  14.          </asp:RoleGroup> 
  15.      </RoleGroups>  
  16.     <LoggedInTemplate>  
  17.          You are not a member of the Supervisors or Administrators roles.  
  18.          Therefore you cannot edit or delete any user information.  
  19.     </LoggedInTemplate>  
  20.     <AnonymousTemplate>  
  21.          You are not logged into the system. Therefore you cannot edit or delete any user  
  22.          information.  
  23.     </AnonymousTemplate> 
  24. </asp:LoginView>

做完这些修改之后,保存网页并通过浏览器访问它。第一次作为一个匿名用户访问。您应该看到的信息是:“You are not logged into the system.Therefore you cannot edit or delete any user information.” (“您还没有登录。所以您不能编辑或删除任何用户信息。”)然后,再用一个经过身份验证的用户登录,但既不是Supervisors 也不是 Administrators 角色。这次,您应该读到的信息是:“You are not a member of the Supervisors or Administrators roles.Therefore you cannot edit or delete any user information.” (“您不是 Supervisors 或Administrators 角色的成员,所以您不能编辑或删除任何用户信息。”)

下一步,以一个担任 Supervisors 角色的用户身份登录。这次您看到的应该是专门对Supervisors 角色的说明信息(见图 9 )。而如果您以一个 Administrators 角色的用户登录,您应该看到的是专门对Administrators 角色的说明信息(见图 10 )。

图9 :向 Bruce 显示专门对 Supervisors 角色的说明信息(单击此处查看实际大小的图像

图10 :向 Tito 显示专门对 Administrators 角色的说明信息(单击此处查看实际大小的图像

从图 9 和图10 所示的截屏画面上看,LoginView 只呈现一个模板,即使应用到了多个模板。Bruce 和 Tito 都是以用户身份登录的,但 LoginView 只呈现了相匹配的 RoleGroup 而不是 LoggedInTemplate 。再有,Tito 属于 Administrators 和 Supervisors 两种角色,但LoginView 控件呈现的是专门对 Administrators 角色的模板而不是对 Supervisors 角色的。

图 11 说明了LoginView 控件所使用的流程,决定呈现哪个模板。注意,如果不只指定了一个RoleGroup ,LoginView 模板呈现第一个相匹配的 RoleGroup 。换句话说,如果我们放置 Supervisors RoleGroup 作为第一个 RoleGroup ,而 Administrators 作为第二个,则当Tito 访问该页面时,他看到的会是对 Supervisors 的信息。

图11 :LoginView 控件决定呈现哪个模板的流程(单击此处查看实际大小的图像

通过编码限制功能

尽管 LoginView 控件基于访问页面用户的角色显示了不同的说明,但所有人都仍然可以看到Edit 和 Cancel 按钮。我们要通过编码,对匿名用户和既不是Supervisors 也不是 Administrators 角色的用户隐藏 Edit 和Delete 按钮。还要对所有不是 Administrator 的用户隐藏 Delete 按钮。为此,我们要写一些代码,通过编码定位CommandField 上的Edit 和 Delete 按钮,并在需要时设置它们的 Visible 属性为 false 。

最简单的办法通过编码定位一个CommandField 上的控件,就是先将该列转化为一个模板。为此,在GridView 的智能标记上单击 Edit Columns 链接,从当前列的列表中选择该 CommandField ,并单击Convert this field into a TemplateField 链接。这就将 CommandField 转化为一个 带ItemTemplate 和 EditItemTemplate 的 TemplateField 。该 ItemTemplate 包含Edit 和 Delete LinkButton ,而 EditItemTemplate 包含 Update 和Cancel LinkButton 。

图12 : 将 CommandField 转换为 TemplateField (单击此处查看实际大小的图像

修改 ItemTemplate 上的Edit 和 Delete LinkButton ,分别将其 ID 属性设置为 EditButton 和 DeleteButton 。

  1. <asp:TemplateField ShowHeader="False">
  2.      <EditItemTemplate>
  3.           <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True"
  4.                CommandName="Update" Text="Update"></asp:LinkButton>
  5.            <asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False"
  6.                 CommandName="Cancel" Text="Cancel"></asp:LinkButton>
  7.      </EditItemTemplate>
  8.      <ItemTemplate>
  9.           <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False"
  10.                CommandName="Edit" Text="Edit"></asp:LinkButton>
  11.            <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False"
  12.                CommandName="Delete" Text="Delete"></asp:LinkButton>
  13.      </ItemTemplate>
  14. </asp:TemplateField>

每当将数据绑定到 GridView ,该GridView 就在其 DataSource 属性中放置记录,并生成一个相应的 GridViewRow 对象。在每个GridViewRow 对象创建时,都引发 RowCreated 事件。为了向未授权用户隐藏 Edit 和 Delete 按钮,我们需要为该事件创建一个事件处理程序,通过编码定位Edit 和 Delete LinkButton ,再相应地设置它们的 Visible 属性。

创建 RowCreated 事件的事件处理程序并加入以下代码:

  1. protected void UserGrid_RowCreated(object sender, GridViewRowEventArgs e)
  2. {
  3.      if (e.Row.RowType == DataControlRowType.DataRow && e.Row.RowIndex != UserGrid.EditIndex)
  4.      {
  5.           // Programmatically reference the Edit and Delete LinkButtons
  6.           LinkButton EditButton = e.Row.FindControl("EditButton"as LinkButton;
  7.           LinkButton DeleteButton = e.Row.FindControl("DeleteButton"as LinkButton;
  8.  
  9.           EditButton.Visible = (User.IsInRole("Administrators") || User.IsInRole("Supervisors"));
  10.           DeleteButton.Visible = User.IsInRole("Administrators");
  11.      }
  12. }

必须知道,对于 GridView 的所有行都引发RowCreated 事件,包括表头、脚注和分页标记等。如果我们是处理不在编辑模式的数据行(因为处在编辑模式的行上有Update 和 Cancel 按钮,而不是 Edit 和 Delete 按钮),则我们希望通过编码定位的只是Edit 和 Delete 按钮。该检查是通过 if 语句实现的。

如果我们是处理一个不在编辑模式的数据行,则定位Edit 和 Delete 按钮后,根据 User 对象的 IsInRole(roleName) 所返回的布尔值,设置它们的 Visible 属性。User 对象定位由RoleManagerModule 创建的主体,因此,IsInRole(roleName) 方法使用 Roles API 确定当前访问者是否属于roleName 。

注意 : 我们也可以直接使用Roles 类,而不用通过调用Roles.IsUserInRole(roleName)方法 调用User.IsInRole(roleName) 。我决定在该例子中使用主体对象的 IsInRole(roleName) 方法,是因为它比直接使用 Roles API 效率更高。本教程前面我们曾讨论过通过配置role manager 在 cookie 中缓存用户角色。只有当调用主体的 IsInRole(roleName) 方法时,才用到该缓存的 cookie 数据,而直接调用 Roles API 却需要到库里去取出存储的角色数据。即使角色数据没有缓存到cookie 中,调用主体对象的 IsInRole(roleName) 方法通常也效率更高,因为当在请求中第一次调用它时,它将结果缓存。而Roles API 却不做任何缓存。因为 GridView 的每行都引发一次 RowCreated 事件,使用 User.IsInRole(roleName) 只需要到库里去取一次角色数据,而 Roles.IsUserInRole(roleName) 则需要到库里去取 N 次,其中 N 是显示在表格中的用户帐户数。

如果访问本页的用户属于 Administrators 或Supervisors 角色,Edit 按钮的 Visible 属性设为 true ,否则设它为 false 。只有当用户属于 Administrators 角色时,Delete 按钮的 Visible 属性才设为true 。

打开浏览器测试此页面。 如果您以一个匿名用户身份或既不是 Supervisor 也不是 Administrator 的用户身份访问本页,CommandField 便是空的;这一列仍然在那里,只是变成了一个窄条,没有了Edit 和 Delete 按钮。

注意 : 当非Supervisor 和非 Administrator 访问网页时,也能做到将 CommandField 整个隐藏起来。 我将此作为练习留给读者完成。

图13 :Edit 和 Delete 按钮对非 Supervisors 和非 Administrators 隐藏了起来(单击此处查看实际大小的图像

如果访问用户属于 Supervisors 角色(但不属于Administrators 角色),他就只能看到 Edit 按钮。

图14 :Edit 按钮对 Supervisors 开放,而Delete 按钮却隐藏了起来(单击此处查看实际大小的图像

而当一个 Administrator 来访问时,他既可以使用 Edit 按钮,也可以使用 Delete 按钮。

图15 :只对 Administrators 开放 Edit 和 Delete 按钮(单击此处查看实际大小的图像

步骤3 :将基于角色的授权规则应用于类和方法

在步骤 2 中我们做了限制,使只有 Supervisors 和 Administrators 角色能够使用编辑功能,而只有Administrators 能够使用删除功能。我们是用程序方式,对未授权用户隐藏相应的用户界面元素来做到这一点的。但这种方法并不能完全避免未授权用户使用本应限制他们的操作。一些隐藏的界面元素可能后来又被加进去了,而我们又忘记将它们对未授权用户再隐藏起来。或是黑客可能通过某些方法获得ASP.NET 页,并执行他们想要的方法。

一个最简便的保证某个特定功能不对未授权用户开放的方法,是用PrincipalPermission 属性 改写那个类或方法。当 .NET runtime 使用一个类或执行其一个方法时,它要做检查以确保当前的安全上下文是许可的。PrincipalPermission 属性提供了一个机制,通过它我们可以定义这些规则。

在前面的《 基于用户的授权 》教程中,我们曾学习过使用PrincipalPermission 属性。具体来说,我们学习了如何改写GridView 的SelectedIndexChanged 和RowDeleting 事件处理程序,使它们只能够被经过身份验证的用户和Tito 执行。该PrincipalPermission 属性对于角色也同样适用。

现在,我们示范一下在 GridView 的RowUpdating 和 RowDeleting 事件处理程序中使用 PrincipalPermission 属性,禁止未授权用户执行。我们所需要做的,就是在每个功能定义的上面加上适当的属性:

  1. [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
  2. [PrincipalPermission(SecurityAction.Demand, Role = "Supervisors")]
  3. protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
  4. {
  5.      ...
  6. }
  7.  
  8. [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
  9. protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
  10. {
  11.      ...
  12. }

RowUpdating 事件处理程序规定,只有担任 Administrators 或 Supervisors 角色的用户能够执行事件处理程序。而RowDeleting 事件处理程序的属性限定,只有担任 Administrators 角色的用户才能执行它。

注意 :PrincipalPermission 属性是作为一个类在System.Security.Permissions 命名空间中出现的。确保要在代码文件类文件的顶部加上一条使用System.Security.Permissions 的语句以导入该命名空间。

如果不知何故一个非 Administrator 用户试图执行RowDeleting 事件处理程序,或是一个非 Supervisor 及非 Administrator 用户试图执行 RowUpdating 事件处理程序的话,.NET runtime 将发出 SecurityException 异常信息。

图16 :如果安全上下文没有授权执行方法,则发出SecurityException 异常信息(单击此处查看实际大小的图像

除了 ASP.NET 网页外,很多应用程序还具有包括各种层的架构,如业务逻辑层和数据访问层。这些层所起的作用就像Class Libraries 一样,提供类和方法,执行与业务逻辑和数据访问相关的功能。PrincipalPermission 属性也同样可以对于这些层应用授权规则。

有关使用 PrincipalPermission 属性对类和方法定义授权规则的更多信息,参阅Scott Guthrie 的博客文章使用 PrincipalPermissionAttributes 为业务或数据层添加授权规则

小结

在本教程中,我们探讨了如何基于用户角色粗略地或精细地制定授权规则。利用ASP.NET 的URL 授权特性,网站开发人员可以设定,允许或拒绝哪些身份的用户访问哪些网页。我们在过去的《 基于用户的授权 》教程中学过,URL 授权规则可以用在逐个用户的基础上。它们也可以用在逐个角色的基础上,正如我们在本教程的步骤1 中所讨论的。

应用精细授权规则,可以通过声明标记,也可以通过编码。在步骤2 中,我们考察了使用 LoginView 控件的 RoleGroups 特性,基于访问用户的角色在网页上呈现不同的输出结果。我们还探讨了如何通过编码确定某用户是否属于某角色,以及如何相应地调整页面上的功能。

快乐编程!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
asp.net2.0安全性(Login系列控件)
基于PDM的图文档管理系统中查询功能的研究与实现
将 ASP.NET UpdatePanel 控件与用户控件一起使用
使用membership(System.Web.Security)来进行角色与权_2009...
asp.net 2.0教程 登录系列控件_阿杜
Asp
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服