本文英文原版及代码下载:http://www.asp.net/learn/security/tutorial-09-cs.aspx
Security Tutorials系列文章第九章:Creating and Manageing Roles
导言: 在前面的文章《User-Based Authorization》里我们是针对某些具体的用户运用URL authorization来对要访问的页面进行限制.不过这样处理方式在这些场合下是难以想象的:有很多的用户帐户,且用户的权限(privileges)经常改变.每当用户获得或失去某个授权时,管理员都要对URL authorization规则进行更改.
通常的做法是将用户划分为不同的组或角色,然后以role-by-role的原则执行授权规则.比如,很多的web applications都有一系列的页面只面向“管理员”用户,如果以前面的基于用户的的授权规则的话,我们要添加相应的URL authorization规则,显式或写代码来允许特定的用户帐户执行管理操作.不过如果新添加一个管理员或移除一个管理员,我们也要相应的添加或删除他的“管理员”授权规则,这样就需要重新更新配置文件以及web页面了.不过,利用role的话情况就不同了,我们可以创建一个名为Administrators的role,再将相应的用户帐户分配给该role.接下来,再添加相应的URL authorization规则,显式或写代码允许名为Administrators的role执行相关的管理员操作.这样一来,不管是添加新的管理员或移除现有的管理员,都不需要更改配置文件、或修改代码.
ASP.NET提供了一个Roles framework以定义roles并分配给用户帐户.有了 Roles framework我们就可以创建和删除role、将一个用户帐户添加到某个role或 从某个role里删除该用户、获取属于某个role的所有用户帐户、探测某个用户帐户是否属于某个role等.一旦配置好Roles framework后,我们就可以role-by-role的原则定义URL authorization规则、根据登录用户的role不同而提供或隐藏页面功能.
本文考察了配置Roles framework所必须的步骤,接下来,在文章《Assigning Roles to Users》里考察如何向roles里添加或删除用户帐户.
再在文章《Role-Based Authorization tutorial》里考察如何以role-by-role的原则对页面的访问设限,以及如何根据登录用户的roles不同而提供不同的功能.
第1步:Adding New ASP.NET Pages
本文以及后面的2篇文章将考察与roles相关的内容,我们来为这3篇文章添加页面.
首先创建一个名为Roles的文件夹,再添加4个页面,都要记得运用母版页,如下:
.ManageRoles.aspx.UsersAndRoles.aspx .CreateUserWizardWithRoles.aspx.RoleBasedAuthorization.aspx 此时,你的界面看起来和下面的差不多:
图1
每个页面都应该有2个Content控件,对应母版页的2个ContentPlaceHolders控件: MainContent 以及 LoginContent
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent"Runat="Server"> </asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="LoginContent"Runat="Server"> </asp:Content>
记得LoginContent ContentPlaceHolder的默认界面显示的是一个登录或注销的链接;而这个Content2 Content控件重写了页面的默认代码.正如我们在前面的《An Overview of Forms Authentication》探讨的那样,这样重写再某些情况下是很有用处的. 不过对这4个页面而言,我们想要显示LoginContent ContentPlaceHolder的默认代码,因此移除Content2 Content的声明代码.完成后,每个页面的的声明代码里就只包含一个Content控件了.
最后更新Web.sitemap文件.在<siteMapNode>后添加如下的XML:
<siteMapNode title="Roles"> <siteMapNode url="~/Roles/ManageRoles.aspx" title="Manage Roles"/> <siteMapNode url="~/Roles/UsersAndRoles.aspx" title="Users and Roles" /> <siteMapNode url="~/Roles/CreateUserWizardWithRoles.aspx" title="Create Account (with Roles)" /> <siteMapNode url="~/Roles/RoleBasedAuthorization.aspx" title="Role-Based Authorization" /> </siteMapNode>
在浏览器里登录网站,如图2所示,左边的导航栏就包括这几个页面了.
图2
Step 2: Specifying and Configuring the Roles Framework Provider
和Membership framework类似,Roles framework也是使用的provider model.如在文章《Security Basics and ASP.NET Support》里探讨的那样, .NET Framework内置了3个Roles providers:也就是AuthorizationStoreRoleProvider, WindowsTokenRoleProvider,以及SqlRoleProvider. 本文专注的是SqlRoleProvider,它以Microsoft SQL Server作为role store.
.NET Framework包含一个Roles class,作为Roles framework的API.而Roles class包含了一些static methods,比如: CreateRole, DeleteRole, GetAllRoles, AddUserToRole, IsUserInRole等.当调用这些方法时,Roles class将调用委托给配置好的provider进行处理.SqlRoleProvider要用到与role相关的表(比如:aspnet_Roles and aspnet_UsersInRoles)
要在我们的应用程序里使用SqlRoleProvider provider,我们需要指定使用哪个数据库.而SqlRoleProvider要指定的数据库包含某些数据库表、视图、存储过程等.我们可以利用aspnet_regsql.exe工具来添加这些database objects,不然现在我们的数据库以及拥有了SqlRoleProvider所必需的构架.在前面的文章《 Creating the Membership Schema in SQL Server》里,我们创建了一个名为SecurityTutorials.mdf的数据库,并用aspnet_regsql.exe添加了application services,其中就包含了SqlRoleProvider所必需的database objects。因此,我们只要告诉Roles framework启动role support,且SqlRoleProvider使用SecurityTutorials.mdf作为role store. Roles framework是在Web.config文件里的<roleManager>元素来配置的.默认时,没有启用role support,要启用的话我们要将<roleManager>元素的enabled属性设为true,如下: <?xml version="1.0"?> <configuration> <system.web> ... Additional configuration markup removed for brevity ... <roleManager enabled="true" /> <system.web> </configuration>
默认,所有的web applications都有一个类型为SqlRoleProvider,名称为AspNetSqlRoleProvider的Roles provider.该默认的provider是在machine.config文件(位于%WINDIR%/Microsoft.Net/Framework/v2.0.50727/CONFIG)里注册的,如下:
<roleManager> <providers> <add name="AspNetSqlRoleProvider" connectionStringName="LocalSqlServer" applicationName="/" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/> </providers> </roleManager>
provider的connectionStringName属性指定了要使用的role store.该AspNetSqlRoleProvider provider将其设置为LocalSqlServer——它也是在machine.config里定义的,默认是指向App_Data文件夹里的名为aspnet.mdf的一个SQL Server 2005 Express Edition数据库.
因此,如果我们仅仅启用Roles framework而没有在Web.config文件里指定任何的provider信息,那么应用程序将使用默认的AspNetSqlRoleProvider,如果 ~/App_Data/aspnet.mdf数据库不存在的话,ASP.NET runtime会自动的创建并添加必要的构架。不过我们不使用这些默认的配置,我们要使用我们以及创建好了的SecurityTutorials.mdf.为此,我们可以通过如下的2种方式来配置:
1.在Web.config文件里为LocalSqlServer连接字符串名称指定一个值.通过重写Web.config里的 LocalSqlServer连接字符串名称,我们可使用默认注册的Roles provider(AspNetSqlRoleProvider),且使用SecurityTutorials.mdf数据库.对该方法的更多详情,请参阅Scott Guthrie的博文:《Configuring ASP.NET 2.0 Application Services to Use SQL Server 2000 or SQL Server 2005》
2.添加一个新的类型为SqlRoleProvider的provider,并设置其connectionStringName属性指向SecurityTutorials.mdf数据库.这是我推荐的方法,也是在文章《Creating the Membership Schema in SQL Server》里使用的方法。
在Web.config文件里添加如下的Roles配置代码,它注册了一个名为SecurityTutorialsSqlRoleProvider的新的provider.
<?xml version="1.0"?> <configuration> <connectionStrings> <add name="SecurityTutorialsConnectionString" connectionString="..."/> </connectionStrings> <system.web> ... Additional configuration markup removed for brevity ... <roleManager enabled="true" defaultProvider="SecurityTutorialsSqlRoleProvider"> <providers> <add name="SecurityTutorialsSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" applicationName="SecurityTutorials" connectionStringName="SecurityTutorialsConnectionString" /> </providers> </roleManager> <system.web> </configuration>
上述代码定义了SecurityTutorialsSqlRoleProvider作为默认的provider(通过<roleManager>元素的defaultProvider属性).它也将SecurityTutorialsSqlRoleProvider的applicationName属性设为SecurityTutorials.有一点要指出,还可以在<add>元素里包含一个commandTimeout属性,用于指定数据库的timeout值,单位为秒,默认值为30.
配置好后我们就可以在应用程序里使用role功能了注意: 上述代码演示了<roleManager>元素的enabled和defaultProvider的用法,在后面的《Role-Based Authorization》里,我们将考察其他的设置.
Step 3: Examining the Roles API
Roles class有13个static methods来执行基于role的操作.当我们在第4和第6步骤里考察创建和删除roles的时候,我们将用到CreateRole和DeleteRole方法,这些方法从系统添加或移除role.
要列出属于某个roles的所有用户,要用到GetAllRoles method(第5步)。而RoleExists method返回一个布尔值,指出是否存在某个role.
在下篇文章我们将看到如何将用户和roles关联起来.Roles class的AddUserToRole, AddUserToRoles, AddUsersToRole,以及AddUsersToRoles方法将一个或多个用户添加到一个或多个roles里.要将用户从roles里移除,我们要使用 RemoveUserFromRole, RemoveUserFromRoles, RemoveUsersFromRole, 或RemoveUsersFromRoles方法.
在后面的文章《Role-Based Authorization》里,我们将看到如何根据当前登录用户的role编程显示或隐藏函数功能的方式.为此我们将使用FindUsersInRole, GetRolesForUser, GetUsersInRole,或IsUserInRole方法.注意: 时刻谨记,当调用这些方法时,Roles class将把这些请求委托给我们配置的provider.就我们的程序而言,这就意味着调用将传给SqlRoleProvider.它再执行一些相关的数据库操作.比如Roles.CreateRole("Administrators")示例将会使SqlRoleProvider执行aspnet_Roles_CreateRole存储过程,向aspnet_Roles表添加一个新的名为“Administrators”的记录.
下面我们将考察使用Roles class的CreateRole, GetAllRoles,DeleteRole方法来管理系统里的roles.
Step 4: Creating New Roles
很遗憾的是ASP.NET里没有一个CreateRoleWizard控件,为了向系统添加一个新的roles,我们必须创建一个合适的界面并调用Roles API.可喜的是这样做很容易办到.注意: 虽然没有CreateRoleWizard控件,但我们可以利用ASP.NET Web Site Administration Tool,该工具是设计来查看和管理你自己的应用程序配置的.不过我对它不太感冒。第一,它用起来有点不爽,需要设置很多东西,第二,该工具设计初衷是本地运行,这就意味着如果你不是在本地管理的话你就要构建立自己的role management web页面.因此,在本文以及后面的文章我们都关注在一个web页面里构建必要的role管理工具而不使用ASP.NET Web Site Administration Tool.
打开,Roles文件夹下的ManageRoles.aspx页面,添加一个TextBox,id为RoleName和一个 Button,id和Text分别为CreateRoleButton和Create Role.此时你的声明代码和下面的差不多:<b>Create a New Role: </b> <asp:TextBox ID="RoleName" runat="server"></asp:TextBox> <br /> <asp:Button ID="CreateRoleButton" runat="server" Text="Create Role" />
然后双击CreateRoleButton按钮添加如下的代码:protected void CreateRoleButton_Click(object sender, EventArgs e) { string newRoleName = RoleName.Text.Trim(); if (!Roles.RoleExists(newRoleName)) // Create the role Roles.CreateRole(newRoleName); RoleName.Text = string.Empty; }
代码首先将RoleName TextBox里输入的role名称赋值给变量newRoleName,再用Roles类的RoleExists方法检查系统里是否已经存在该角色,如果不存在就用CreateRole方法创建该角色.最后清除输入的RoleName.注意: 你可能很好奇,如果我们在RoleName TextBox里不输入内容将会怎么样呢?实际上如果传给CreateRole的参数为null或空,那么将会抛出异常.同样,如果你输入的角色名里包含逗号的话,也会抛出异常.因此我们要在页面上添加一个验证控件,我将这个验证留给读者当练习.
我们来创建一个名为“Administrators”的角色。在ManageRoles.aspx页面里,在textbox里键入“Administrators”(如图3),再点击“Create Role”按钮.
图3
如何?发生了页面回传,但没有视觉效果提示我们已经成功创建角色了.我们将在第五步骤里进行改进.现在在SecurityTutorials.mdf数据库里的aspnet_Roles表里我们可以看到刚刚添加进的Administrators角色.
图4
Step 5: Displaying the Roles in the System
让我们对ManageRoles.aspx页面进行扩充,将系统里当前的角色罗列出来.为此,在页面添加一个GridView控件,其id为RoleList,接下来,在后台代码里添加一个名为DisplayRolesInGrid的方法,代码如下:private void DisplayRolesInGrid() { RoleList.DataSource = Roles.GetAllRoles(); RoleList.DataBind(); }
Roles类的GetAllRoles方法将系统当前的所有用户以字符串数组的形式返回,并绑定到GridView.为了在页面首次登录时便进行绑定,我们需要在Page_Load事件处理器里调用DisplayRolesInGrid方法.下面的代码是在页面初次登录而不是页面回传的时候进行绑定.protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) DisplayRolesInGrid(); }
然后在浏览器里进行测试,如图5所示。你将看到一个标记为“Item”的方格.该方格里仅仅有一行,就是我们在第四步骤里添加的角色Administrators.
图5
当用GridView显示数据的时候,我喜欢自己显式的定义,而不喜欢使用GridView默认生成的界面.我们来对GridView的声明代码进行改动,以显式的定义列.
首先将GridView的AutoGenerateColumns属性设置为False,然后添加一个TemplateField,设其HeaderText属性为“Roles”, 再在ItemTemplate里添加一个id为RoleNameLabel的Label控件.将其Text属性绑定为Container.DataItem.
这些属性以及ItemTemplate包含的内容可以通过显式声明的方式或GridView的Fields对话框和Edit Templates界面来实现.要通过Fields对话框的话,点击GridView的智能标签,不要选中“Auto-generate fields” checkbox,以使AutoGenerateColumns属性为False,再往GridView添加一个TemplateField,设其HeaderText属性为“Role”.要定义ItemTemplate的内容,选择Edit Templates”选项,拖一个Label控件到ItemTemplate,设其id为RoleNameLabel,再设置其Text属性绑定到Container.DataItem.
不管你是用的哪种方式,你的声明代码和下面的差不多:
<asp:GridView ID="RoleList" runat="server" AutoGenerateColumns="false"> <Columns> <asp:TemplateField HeaderText="Role"> <ItemTemplate> <asp:Label runat="server" ID="RoleNameLabel" Text='<%# Container.DataItem %>' /> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>
注意: 我们是通过绑定语法<%# Container.DataItem %>来将数组的内容显示出来的.关于该语法的更深入的探讨超出了本文的范畴,你可以参阅文章《Binding a Scalar Array to a Data Web Control》
当前,RoleList GridView仅仅在页面初次登录时将角色信息罗列出来,我们需要在新添加角色时马上对页面进行刷新.为此,对CreateRoleButton按钮的Click事件处理器进行更新,当新角色添加时即调用DisplayRolesInGrid方法.
protected void CreateRoleButton_Click(object sender, EventArgs e) { string newRoleName = RoleName.Text.Trim(); if (!Roles.RoleExists(newRoleName)) { // Create the role Roles.CreateRole(newRoleName); // Refresh the RoleList Grid DisplayRolesInGrid(); } RoleName.Text = string.Empty; }
这样,当我们新添加角色信息时马上就可以显示出结果.来测试,来浏览器里登录ManageRoles.aspx页面.添加一个名为“Supervisors”的角色,点击“Create Role”按钮.页面回传,新添加的Administrators就显示出来了.
图6
Step 6: Deleting Roles
显示用户可以添加新角色,并在ManageRoles.aspx里显示出来,让我们再允许用户删除角色.而Roles.DeleteRole方法有2个重载:
.DeleteRole(roleName)——删除角色roleName,不过如果该角色包含一个或更多的用户,那么将抛出一个异常.
.DeleteRole(roleName, throwOnPopulatedRole)——删除角色roleName,如果throwOnPopulatedRole为true,那么当角色包含一个或多个用户的话将抛出异常,如果throwOnPopulatedRole为false,那么不管角色是否包含用户都删除该角色.在内部,DeleteRole(roleName)方法实际调用的是DeleteRole(roleName, true).
如果roleName为null,或空字符串或roleName里包含逗号,DeleteRole方法都将抛出异常.如系统里没有roleName这个角色,那么DeleteRole方法将执行失败而不会抛出异常.
我们再对ManageRoles.aspx页面进行扩充,以包含一个Delete按钮,当点击该按钮时将删除选中的角色.打开GridView的Fields对话框,添加一个Delete按钮,将其DeleteText属性设置为“Delete Role”.
图7
这样你的GridView的声明代码和下面的差不多:
<asp:GridView ID="RoleList" runat="server" AutoGenerateColumns="False"> <Columns> <asp:CommandField DeleteText="Delete Role" ShowDeleteButton="True"/> <asp:TemplateField HeaderText="Role"> <ItemTemplate> <asp:Label runat="server" ID="RoleNameLabel" Text='<%# Container.DataItem %>' /> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>
再为GridView的RowDeleting事件创建一个事件处理器,这是当点击“Delete Role”按钮时触发的事件,如下:
protected void RoleList_RowDeleting(object sender, GridViewDeleteEventArgs e){ // Get the RoleNameLabel Label RoleNameLabel = RoleList.Rows[e.RowIndex].FindControl("RoleNameLabel") as Label; // Delete the role Roles.DeleteRole(RoleNameLabel.Text, false); // Rebind the data to the RoleList grid DisplayRolesInGrid(); }
当点击某行的“Delete Role”暗示时,首先编程引用该行的id为RoleNameLabel的控件,然后调用Roles.DeleteRole方法,将RoleNameLabel的Text值和false一起传入,这样不管该角色是否有用户都将被删除.最后刷新RoleList GridView,那么刚才删除的角色就显示不出来了.
注意: 当点击“Delete Role”按钮时,在用户删除之前我们并没有要求用户确认.一种最简单的办法是“客户端确认”.更多详情请见《Adding Client-Side Confirmation When Deleting》.
结语: 有了Roles framework,我们可以很容易的创建和管理roles. 本文我们考察了如何配置Roles framework以启用SqlRoleProvider,它是使用一个Microsoft SQL Server来作为role store.我们也创建了一个页面来将系统现有的角色罗列出来,并允许创建新角色,删除现有的角色.下一章我们将看如何将用户分配给角色以及如何执行基于角色的授权.
祝编程愉快!
作者简介:
Scott Mitchell,著有七本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。Scott是个独立的技术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作,《24小时内精通ASP.NET 2.0》。他的联系电邮为mitchell@4guysfromrolla.com,也可以通过他的博客http://ScottOnWriting.NET与他联系。