18.4 ASP.NET 2.0的授权
处理了要访问Web应用程序的用户的注册和身份验证之后,下一步就是授权。用户可以查看什么内容?要承担什么角色?这些对于任何Web应用程序来说都是很重要的问题。下面首先学习如何给验证用户显示某些内容,给未验证用户显示另外一些内容。
18.4.1 使用LoginView服务器控件
LoginView服务器控件可以控制谁查看页面某个部分的什么信息。使用LoginView控件可以指定验证用户查看页面的哪些部分,未验证用户查看页面的哪些部分。程序清单18-26显示了这个控件的一个例子。
程序清单18-26 通过LoginView控件控制显示的信息
<%@ Page Language="VB" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Changing the View</title>
</head>
<body>
<form id="form1" runat="server">
<asp:LoginStatus ID="LoginStatus1" Runat="server" />
<p>
<asp:LoginView ID="LoginView1" Runat="server">
<LoggedInTemplate>
Here is some REALLY important information that you should know
about all those people that are not authenticated!
</LoggedInTemplate>
<AnonymousTemplate>
Here is some basic information for you.
</AnonymousTemplate>
</asp:LoginView><p>
</form>
</body>
</html>
<asp:LoginView>控件是一个模板化控件,它有两个子元素<LoggedInTemplate>和<AnonymousTemplate>。在本例中,在<AnonymousTemplate>块中定义的信息用于未验证用户,如图18-19所示。

图 18-19
验证用户查看的内容在<LoggedInTemplate>块中定义,与未验证用户完全不同,如图18-20所示。

图 18-20
在这两个模板中只放置了简单的ASCII文本,但可以在其中放置其他任何内容,包括其他服务器控件。这表示,在模板化的块中,可以显示页面的所有块,包括窗体。
除了使用LoginView控件的<LoggedInTemplate>和<AnonymousTemplate>之外,还可以激活页面的块或实体的特定内容,该实体是某个角色的一部分,例如某个人是Admin组的一部分。为此,可以使用LoginView控件的<RoleGroups>块,如程序清单18-27所示。
程序清单18-27 为特定的组提供视图
<%@ Page Language="VB" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Changing the View</title>
</head>
<body>
<form id="form1" runat="server">
<asp:LoginStatus ID="LoginStatus1" Runat="server" />
<p>
<asp:LoginView ID="LoginView1" Runat="server">
<LoggedInTemplate>
Here is some REALLY important information that you should know
about all those people that are not authenticated!
</LoggedInTemplate>
<AnonymousTemplate>
Here is some basic information for you.
</AnonymousTemplate>
<RoleGroups>
<asp:RoleGroup Roles="Admins">
<ContentTemplate>
You are an Admin!
</ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="CoolPeople">
<ContentTemplate>
You are cool!
</ContentTemplate>
</asp:RoleGroup>
</RoleGroups>
</asp:LoginView><p>
</form>
</body>
</html>
要给特定用户组显示内容,可以在LoginView控件中添加一个<RoleGroups>元素。<RoleGroups>块可以有一个或多个RoleGroup控件(这个控件不在Visual Studio的工具箱上)。为了使用RoleGroup控件提供要显示的内容,可以提供一个<ContentTemplate>元素,它可以为属于特定角色的实体定义要显示的内容。在<ContentTemplate>块中放置什么内容完全取决于我们。可以放置原始文本(如例子中所示),甚至其他ASP.NET控件。
注意在<RoleGroups>块中放置已定义角色的顺序。用户登录到站点上时,首先要检查他们是否匹配某个已定义的角色。最先匹配的角色是用于LoginView控件的视图,即使用户匹配多个角色,也先考虑该视图。也可以在<asp:RoleGroups>控件的Roles属性中放置多个角色,如下所示:
<asp:RoleGroup Roles="CoolPeople, HappyPeople">
<ContentTemplate>
You are cool or happy (or both)!
</ContentTemplate>
</asp:RoleGroup>
18.4.2 为角色管理建立Web站点
除了前面介绍的成员服务之外,ASP.NET 2.0还提供了终端用户管理服务的另一个方面:ASP.NET角色管理服务。成员服务涵盖了应用程序的验证的所有方面,而角色管理服务涵盖的是授权。成员服务可以使用前面列出的任一数据提供程序,角色管理服务也可以使用SQL Server提供程序(SqlRoleProvider)和任意定制的提供程序。事实上,这个服务与成员服务在许多方面都是兼容的。图18-21显示了角色管理服务的一些特殊之处。

图 18-21
1. 修改<roleManager>块
使用角色管理服务的第一步是在machine.config.comments或web.config文件中修改角色管理提供程序的行为。在machine.config.comments文件中,有一个块专门用于处理角色管理服务,如程序清单18-28所示。
程序清单18-28 machine.config.comments文件中的角色管理提供程序设置
<roleManager
enabled="false"
cacheRolesInCookie="false"
cookieName=".ASPXROLES"
cookieTimeout="30"
cookiePath="/"
cookieRequireSSL="false"
cookieSlidingExpiration="true"
cookieProtection="All"
defaultProvider="AspNetSqlRoleProvider"
createPersistentCookie="false"
maxCachedResults="25">
<providers>
<clear />
<add connectionStringName="LocalSqlServer" applicationName="/"
name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider,
System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
<add applicationName="/" name="AspNetWindowsTokenRoleProvider"
type="System.Web.Security.WindowsTokenRoleProvider, System.Web,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</roleManager>
角色管理服务在machine.config.comments文件中定义其设置,如上面的程序清单所示。可以直接在machine.config.comments文件中修改这些设置,也可以在web.config文件中重写它们(这些修改只用于当前的应用程序)。
主要设置在<roleManager>元素中定义。<roleManager>元素的一些属性如表18-2所示。
表 18-2
|
属 性 |
说 明 |
|
enabled |
定义是否为应用程序激活角色管理服务。这个属性带一个布尔值,默认设置为False。也就是说,角色管理服务默认为禁用。这可以避免用户从ASP.NET 1.0/1.1迁移到ASP.NET 2.0时出现的变化。因此,必须先在machine.config或web.config文件中把这个值改为True |
|
cacheRolesInCookie |
定义用户的角色是否可以在客户机的cookie中存储。这个属性带一个布尔值,默认设置为True。这是很理想的,因为从cookie中提取角色,可避免ASP.NET通过角色管理提供程序查找用户的角色。如果要为所有的实例通过提供程序提取角色,就可以把它设置为False |
|
cookieName |
定义用于cookie的名称,该cookie会发送给终端用户,用于存储角色管理信息。这个cookie默认命名为.ASPXROLES,不能改这个名称 |
|
cookieTimeout |
定义cookie过期后的时间(分钟),默认值是30分钟 |
|
cookieRequireSSL |
确定是要求角色管理信息通过加密线路(SSL)发送,还是作为明文发送。默认值是False |
|
cookieSlidingExpiration |
指定cookie的超时是否在可变的范围内,其默认值是True。这表示,在应用程序的上一个请求发出30分钟(或在cookieTimeout属性中指定的时间)后,终端用户的cookie就会过期。如果cookieSlidingExpiration属性的值设置为False,cookie就会在第一个请求发出30分钟后过期 |
|
createPersistentCookie |
指定cookie是会过期还是永远处于激活状态,其默认值是False,因为安全原因,永久的cookie并不总是很合适 |
|
cookieProtection |
指定要应用于cookie的保护级别,该cookie存储在终端用户的机器上,用于管理信息。其设置可以是All、None、Encryption和Validation,应总是使用All |
|
DefaultProvider |
定义用于角色管理服务的提供程序,它默认设置为AspNetSqlRole Provider |
2. 修改web.config文件
下一步是配置Web.config文件,使之可以使用角色管理服务。应用程序的某些页面或子块可能只能由特定角色的人访问。为了管理这种访问,应在web.config文件中定义访问权限。需要进行的修改如程序清单18-29所示。
程序清单18-29 修改web.config文件
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<roleManager enabled="true"/>
<authentication mode="Forms" />
<authorization>
<deny users="?" />
</authorization>
</system.web>
<location path="AdminPage.aspx">
<system.web>
<authorization>
<allow roles="AdminPageRights" />
<deny users="*" />
</authorization>
</system.web>
</location>
</configuration>
这个web.config文件做了两件事:首先,第一个<system.web>块的功能与本章前面的成员服务没有区别。<deny>元素拒绝所有未验证用户的访问。
web.config文件的第二部分相当有趣。<location>元素用于定义应用程序中某个页面(AdminPage.aspx)的访问权限。在本例中,只有包含在AdminPageRights角色中的用户才能访问该页面,而其他用户,无论他们是否已通过身份验证,都不能访问该页面。使用星号(*)作为<deny>元素的users属性值时,就表示所有的用户(无论他们是否已通过身份验证)都不能访问指定的资源。但使用<allow>元素可以改变这个拒绝访问权限,该元素允许包含在特定角色中的用户进行访问。
18.4.3 添加和检索应用程序角色
有了machine.config.comments和web.config文件后,就可以给角色管理服务添加角色。角色管理服务与成员服务一样,都使用数据库存储用户的信息。这些例子主要使用Microsoft SQL Server Express Edition作为提供程序,因为它是默认的提供程序。
角色管理服务和成员服务的一个主要区别是,角色管理服务没有使用服务器控件。管理应用程序的角色和用户的角色信息是使用新的Roles API或ASP.NET 2.0提供的Web Site Administration Tool实现的。程序清单18-30说明了如何使用新方法给服务添加角色。
程序清单18-30 给应用程序添加角色
VB
<%@ Page Language="VB" %>
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
If Not Page.IsPostBack Then
ListBoxDataBind()
End If
End Sub
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Roles.CreateRole(TextBox1.Text)
ListBoxDataBind()
End Sub
Protected Sub ListBoxDataBind()
ListBox1.DataSource = Roles.GetAllRoles()
ListBox1.DataBind()
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Role Manager</title>
</head>
<body>
<form id="form1" runat="server">
<h1>Role Manager</h1>
Add Role:<br />
<asp:TextBox ID="TextBox1" Runat="server"></asp:TextBox>
<p><asp:Button ID="Button1" Runat="server" Text="Add Role to Application"
OnClick="Button1_Click" /></p>
Roles Defined:<br />
<asp:ListBox ID="ListBox1" Runat="server">
</asp:ListBox>
</form>
</body>
</html>
C#
<%@ Page Language="C#" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
If (!Page.IsPostBack)
{
ListBoxDataBind();
}
}
protected void Button1_Click(object sender, EventArgs e)
{
Roles.CreateRole(TextBox1.Text.ToString());
ListBoxDataBind();
}
protected void ListBoxDataBind()
{
ListBox1.DataSource = Roles.GetAllRoles();
ListBox1.DataBind();
}
</script>
这个例子允许在文本框中输入角色,再把它们提交给角色管理服务。角色管理服务包含的角色会显示在列表框中,如图18-22所示。

图 18-22
要把角色输入到管理服务中,只需使用Roles类的CreateRole方法。与Membership类一样,不需要实例化Roles类。要把角色添加到角色管理服务中,可使用CreateRole方法,它只带一个字符串参数:角色名。
Roles.CreateRole(rolename As String)
使用这个方法可以创建任意多个角色,但每个角色必须是唯一的,否则就会抛出一个异常。
要在应用程序的角色管理服务(如上述例子的列表框中显示的一组角色)中提取角色,可以使用Roles类的GetAllRoles方法。这个方法返回服务中所有角色的String集合。
Roles.GetAllRoles()
18.4.4 删除角色
我们一直在给服务添加角色,但有时也需要从服务中删除角色。在角色管理服务中删除角色与添加角色一样简单。要删除一个角色,可以使用DeleteRole方法的一个签名。DeleteRole方法的第一个签名带一个字符串参数:角色名。第二个签名带角色名和一个布尔值,该布尔值表示在某个角色中包含一个或多个成员时是否抛出一个异常(这样就不会在角色中有用户时删除该角色了)。
Roles.DeleteRole(rolename As String)
Roles.DeleteRole(rolename As String, throwOnPopulatedRole As Boolean)
程序清单18-31是程序清单18-30中的一部分代码示例。对于这个例子,再添加一个按钮,它会触发第二个按钮单击事件,该事件从服务中删除角色。
程序清单18-31 从应用程序中删除角色
VB
Protected Sub DeleteButton_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
For Each li As ListItem In ListBox1.Items
If li.Selected = True Then
Roles.DeleteRole(li.ToString())
End If
Next
ListBoxDataBind()
End Sub
C#
protected void DeleteButton_Click(object sender, EventArgs e)
{
foreach (ListItem li in ListBox1.Items) {
if (li.Selected == true) {
Roles.DeleteRole(li.ToString());
}
}
ListBoxDataBind();
}
这个例子从ListBox控件中删除了选中的项。如果选择了多个选项(表示在ListBox控件中设置了属性SelectionMode=“Multiple”),则会在For Each循环中依次删除每个角色。我们使用Roles.DeleteRole(li.ToString())删除角色,还可以使用Roles.DeleteRole(li.ToString(), True),以确保在角色包含成员时不删除角色。
18.4.5 给角色添加用户
现在有了角色,并且可以在需要时删除这些角色,下一步就是给所创建的角色添加用户。如果角色没有与之相关的用户,这个角色就没有什么用。要把一个用户添加到一个角色中,可以使用下面的构建代码:
Roles.AddUserToRole(username As String, rolename As String)
要把一个用户同时添加到多个角色中,可以使用下面的构建代码:
Roles.AddUserToRoles(username As String, rolenames() As String)
要把多个用户添加到一个角色中,可以使用下面的构建代码:
Roles.AddUsersToRole(usernames() As String, rolename As String)
最后,要把多个用户添加到多个角色中,可以使用下面的构建代码:
Roles.AddUsersToRoles(usernames() As String, rolenames() As String)
参数可以是集合。无论参数是usernames()还是rolenames(),在方法中都是String数组。
18.4.6 获取某个角色的所有用户
在角色管理服务中查找信息都是很容易的。可以确定某个角色中包含哪些用户,还可以确定某个用户包含在哪些角色中。
这两种情况都可以使用具体的方法实现。首先,看看如何确定包含在某个角色中的所有用户,如程序清单18-32所示。
程序清单18-32 查找某个角色中的用户
VB
<%@ Page Language="VB" %>
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
If Not Page.IsPostBack Then
DropDownDataBind()
End If
End Sub
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
GridView1.DataSource = Roles.GetUsersInRole(DropDownList1.SelectedValue)
GridView1.DataBind()
DropDownDataBind()
End Sub
Protected Sub DropDownDataBind()
DropDownList1.DataSource = Roles.GetAllRoles()
DropDownList1.DataBind()
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Role Manager</title>
</head>
<body>
<form id="form1" runat="server">
Roles:
<asp:DropDownList ID="DropDownList1" Runat="server">
</asp:DropDownList>
<asp:Button ID="Button1" Runat="server" Text="Get Users In Role"
OnClick="Button1_Click" />
<br />
<br />
<asp:GridView ID="GridView1" Runat="server">
</asp:GridView>
</form>
</body>
</html>
C#
<%@ Page Language="C#" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
If (!Page.IsPostBack)
{
DropDownDataBind();
}
}
protected void Button1_Click(object sender, EventArgs e)
{
GridView1.DataSource = Roles.GetUsersInRole(DropDownList1.SelectedValue);
GridView1.DataBind();
DropDownDataBind();
}
protected void DropDownDataBind()
{
DropDownList1.DataSource = Roles.GetAllRoles();
DropDownList1.DataBind();
}
</script>
这个页面创建了一个下拉列表,其中包含应用程序的所有角色。单击按钮会显示所选角色的所有用户。某个角色的用户是使用GetUsersInRole方法确定的。这个方法带有一个字符串参数,它表示角色的名称。
Roles.GetUsersInRole(rolename As String)
运行代码,生成的页面如图18-23所示。

图 18-23
18.4.7 获取包含某个用户的所有角色
为了确定包含某个用户的所有角色,可创建一个页面,其中只包含一个文本框和一个按钮。在文本框中输入用户的名称,单击按钮就会提取角色,并填充到GridView控件中。按钮单击事件(所有的操作都在这个事件中进行)如程序清单18-33所示。
程序清单18-33 获取包含某个用户的所有角色
VB
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
GridView1.DataSource = Roles.GetRolesForUser(TextBox1.Text)
GridView1.DataBind()
End Sub
C#
protected void Button1_Click(object sender, EventArgs e)
{
GridView1.DataSource = Roles.GetRolesForUser(TextBox1.Text.ToString());
GridView1.DataBind();
}
上面的代码会生成如图18-24所示的结果。

图 18-24
要获得包含某个用户的角色,可以使用GetRolesForUser方法。这个方法有两个签名。第一个签名在上面的例子中演示过了,它带有一个字符串参数,表示用户名。另一个签名不带任何参数,返回登录到成员服务上的用户所属的角色。
18.4.8 从角色中删除用户
除了给角色添加用户外,还可以从角色中删除用户。要从一个角色中删除一个用户,可以使用下面的构建代码:
Roles.RemoveUserFromRole(username As String, rolename As String)
要从多个角色中同时删除一个用户,可以使用下面的构建代码:
Roles.RemoveUserFromRoles(username As String, rolenames() As String)
要从一个角色中删除多个用户,可以使用下面的构建代码:
Roles.RemoveUsersFromRole(usernames() As String, rolename As String)
最后,要从多个角色中删除多个用户,可以使用下面的构建代码:
Roles.RemoveUsersFromRoles(usernames() As String, rolenames() As String)
参数显示为集合,无论是usernames()还是rolenames(),这些参数在方法中都是String数组。
18.4.9 检查角色中的用户
最后一个可以执行的操作是检查某个用户是否在角色中。这有两种方式。第一种方式是使用IsUserInRole方法。IsUserInRole方法带两个参数—— 用户名和角色名:
Roles.IsUserInRole(username As String, rolename As String)
这个方法返回一个布尔值,表示用户的状态,它的用法见程序清单18-34。
程序清单18-34 检查用户的角色状态
VB
If (Roles.IsUserInRole(TextBox1.Text, "AdminPageRights")) Then
' perform action here
End If
C#
If (Roles.IsUserInRole(TextBox1.Text.ToString(), "AdminPageRights"))
{
// perform action here
}
除了IsUserInRole方法之外,另一种方式是使用FindUsersInRole方法。这个方法可以在某个角色中根据一个名称搜索所有的用户。FindUsersInRole方法带有两个参数:角色名和用户名,它们都是String类型的值。
Roles.FindUsersInRole(rolename As String, username As String)
程序清单18-35是这个方法的一个示例。
程序清单18-35 在某个角色中查找特定的用户
VB
<%@ Page Language="VB" %>
<script runat="server">
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
GridView1.DataSource = _
Roles.FindUsersInRole("AdminPageRights", TextBox1.Text)
GridView1.DataBind()
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Role Manager</title>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox ID="TextBox1" Runat="server"></asp:TextBox>
<asp:Button ID="Button1" Runat="server" Text="Button"
OnClick="Button1_Click" />
<p><asp:GridView ID="GridView1" Runat="server">
</asp:GridView></p>
</form>
</body>
</html>
C#
<%@ Page Language="C#" %>
<script runat="server">
protected void Button1_Click(object sender, EventArgs e)
{
GridView1.DataSource =
Roles.FindUsersInRole("AdminPageRights", TextBox1.Text.ToString());
GridView1.DataBind();
}
</script>
18.4.10 角色的高速缓存方式
默认情况下,从角色管理服务底层的数据库中提取用户的角色后,就可以在客户机上把这些角色作为一个cookie存储。这样每次应用程序需要用户的角色状态时,就不必访问数据库了。使用cookie总是有一点儿风险,因为终端用户可以操纵cookie,来访问信息或应用程序中拒绝某个用户访问的部分。
尽管角色可以高速缓存在cookie中,但在默认情况下,它们一次只能高速缓存30分钟。可以用几种方式处理这个角色cookie,其中一些方式有助于更好地保护应用程序。
对应用程序的一种保护是在终端用户登录到站点上时,使用Role API的DeleteCookie方法删除这个角色cookie,如程序清单18-36所示。
程序清单18-36 在验证终端用户的身份时删除其角色cookie
VB
If Membership.ValidateUser(TextBox1.Text, TextBox2.Text) Then
Roles.DeleteCookie()
FormsAuthentication.RedirectFromLoginPage(TextBox1.Text, False)
Else
Label1.Text = "You are not registered with the site."
End If
C#
if (Membership.ValidateUser(TextBox1.Text.ToString(), TextBox2.Text.ToString()) {
Roles.DeleteCookie();
FormsAuthentication.RedirectFromLoginPage(TextBox1.Text.ToString(), false);
}
else {
Label1.Text = "You are not registered with the site.";
}
使用Roles.DeleteCookie方法可以从客户机中删除用于定义用户角色的cookie。如果终端用户再次登录到站点上,在应用程序中重新验证他的角色是不会出问题的,不需要依赖cookie的内容。这一步为站点提供了更多的保护。







