基于用户的身份验证 (VB)

作者 :Scott Mitchell

注意

自本文撰写以来,ASP.NET 成员资格提供程序已被 ASP.NET Identity 取代。 强烈建议更新应用以使用 ASP.NET 标识 平台,而不是本文撰写时介绍的成员资格提供程序。 ASP.NET 标识比 ASP.NET 成员身份系统具有许多优势,包括:

  • 性能更好
  • 改进了可扩展性和可测试性
  • 支持 OAuth、OpenID Connect 和双因素身份验证
  • 基于声明的标识支持
  • 更好地与 ASP.Net Core 的互操作性

下载代码下载 PDF

在本教程中,我们将了解如何通过各种技术限制对页面的访问和限制页面级功能。

简介

大多数提供用户帐户的 Web 应用程序这样做在一定程度上是限制某些访问者访问网站中的某些页面。 例如,在大多数在线消息板网站中,所有用户(匿名和经过身份验证)都能够查看消息板的帖子,但只有经过身份验证的用户才能访问网页以创建新帖子。 可能有管理页面只能由特定用户 (或一组特定用户) 访问。 此外,页面级功能可能因用户而异。 查看帖子列表时,经过身份验证的用户会显示一个界面,用于对每个帖子进行评分,而匿名访问者无法使用此界面。

ASP.NET 可以轻松定义基于用户的授权规则。 只需在 中 Web.config添加一些标记,就可以锁定特定网页或整个目录,以便只有指定的用户子集才能访问它们。 可以通过编程和声明性方式基于当前登录的用户打开或关闭页面级功能。

在本教程中,我们将了解如何通过各种技术限制对页面的访问和限制页面级功能。 让我们开始吧!

查看 URL 授权工作流

Forms 身份验证概述 教程中所述,当 ASP.NET 运行时处理对 ASP.NET 资源的请求时,请求在其生命周期内引发大量事件。 HTTP 模块 是托管类,其代码执行以响应请求生命周期中的特定事件。 ASP.NET 附带了许多在后台执行基本任务的 HTTP 模块。

其中一个 HTTP 模块是 FormsAuthenticationModule。 如前面的教程中所述, 的主要功能 FormsAuthenticationModule 是确定当前请求的标识。 这是通过检查表单身份验证票证来实现的,该票证要么位于 Cookie 中,要么嵌入在 URL 中。 此标识在事件期间AuthenticateRequest发生。

另一个重要的 HTTP 模块是 UrlAuthorizationModule,它是为了响应 AuthorizeRequest 事件) 后 AuthenticateRequest 发生的事件 (引发的。 UrlAuthorizationModule检查 中的Web.config配置标记,以确定当前标识是否有权访问指定页面。 此过程称为 URL 授权

我们将检查步骤 1 中 URL 授权规则的语法,但首先让我们看看 根据请求是否获得授权来做什么 UrlAuthorizationModuleUrlAuthorizationModule如果 确定请求已获得授权,则不执行任何工作,并且请求将一直持续到其生命周期。 但是,如果请求 获授权,则会 UrlAuthorizationModule 中止生命周期,并指示 Response 对象返回 HTTP 401 未授权 状态。 使用表单身份验证时,此 HTTP 401 状态永远不会返回到客户端,因为如果 FormsAuthenticationModule 检测到 HTTP 401 状态,则会将其修改为 HTTP 302 重定向 到登录页。

图 1 演示了 ASP.NET 管道的工作流, FormsAuthenticationModule以及 UrlAuthorizationModule 未经授权的请求到达时的 。 具体而言,图 1 显示了匿名访问者对 ProtectedPage.aspx的请求,该请求是拒绝匿名用户访问的页面。 由于访问者是匿名的, UrlAuthorizationModule 会中止请求并返回 HTTP 401 未授权状态。 然后,将 FormsAuthenticationModule 401 状态转换为 302 重定向到登录页。 通过登录页对用户进行身份验证后,会重定向到 ProtectedPage.aspx。 这一次, FormsAuthenticationModule 根据用户的身份验证票证标识用户。 现在,访问者已经过身份验证, UrlAuthorizationModule 允许访问页面。

表单身份验证和 URL 授权工作流

图 1:窗体身份验证和 URL 授权工作流 (单击以查看全尺寸图像)

图 1 描述了匿名访问者尝试访问匿名用户无法使用的资源时发生的交互。 在这种情况下,匿名访问者将重定向到登录页,其中包含她在 querystring 中指定的尝试访问的页面。 用户成功登录后,将自动重定向回最初尝试查看的资源。

当匿名用户发出未经授权的请求时,此工作流非常简单,并且易于访问者了解所发生的情况和原因。 但请记住, FormsAuthenticationModule 会将 任何 未经授权的用户重定向到登录页,即使请求是由经过身份验证的用户发出的。 如果经过身份验证的用户尝试访问她没有权限的页面,这可能会导致令人困惑的用户体验。

假设网站配置了其 URL 授权规则,使 ASP.NET 页面 OnlyTito.aspx 只能访问 Tito。 现在,假设 Sam 访问站点,登录,然后尝试访问 OnlyTito.aspxUrlAuthorizationModule将停止请求生命周期并返回 HTTP 401 未授权状态,将FormsAuthenticationModule检测到该状态,然后将 Sam 重定向到登录页。 不过,由于 Sam 已经登录,她可能想知道为什么她被发送回登录页面。 她可能会认为她的登录凭据以某种方式丢失,或者输入了无效的凭据。 如果 Sam 从登录页重新输入凭据,她将再次登录 () 并重定向到 OnlyTito.aspx。 将 UrlAuthorizationModule 检测到 Sam 无法访问此页面,并且她将被返回到登录页。

图 2 描述了这种令人困惑的工作流。

默认工作流可能导致混乱的周期

图 2:默认工作流可能导致混乱的周期 (单击以查看全尺寸图像)

图 2 中所示的工作流可能很快就会让最精明的访问者迷惑不解。 我们将在步骤 2 中探讨防止这种混乱循环的方法。

注意

ASP.NET 使用两种机制来确定当前用户是否可以访问特定网页:URL 授权和文件授权。 文件授权由 实现, FileAuthorizationModule后者通过查阅请求的文件 () ACL 来确定颁发机构。 文件授权最常用于 Windows 身份验证,因为 ACL 是应用于 Windows 帐户的权限。 使用表单身份验证时,所有操作系统和文件系统级请求都由同一 Windows 帐户执行,而不管用户访问该网站的用户如何。 由于本教程系列侧重于表单身份验证,因此我们不会讨论文件授权。

URL 授权的范围

UrlAuthorizationModule是属于 ASP.NET 运行时的托管代码。 在 Microsoft 的 Internet Information Services (IIS) Web 服务器版本 7 之前,IIS 的 HTTP 管道与 ASP.NET 运行时管道之间存在明显的障碍。 简言之,在 IIS 6 及更早版本中为 ASP。 UrlAuthorizationModule 仅当请求从 IIS 委托给 ASP.NET 运行时时,NET 才会执行。 默认情况下,IIS 会处理静态内容本身(如 HTML 页面和 CSS、JavaScript 和图像文件),并且仅在请求扩展名 .aspx为 、 .asmx.ashx 的页面时将请求转交给 ASP.NET 运行时。

但是,IIS 7 允许集成 IIS 和 ASP.NET 管道。 通过一些配置设置,可以将 IIS 7 设置为对所有请求调用 UrlAuthorizationModule ,这意味着可以为任何类型的文件定义 URL 授权规则。 此外,IIS 7 包括其自己的 URL 授权引擎。 有关 ASP.NET 集成和 IIS 7 的本机 URL 授权功能的详细信息,请参阅 了解 IIS7 URL 授权。 若要更深入地了解 ASP.NET 与 IIS 7 集成,请获取 Shahram Khosravi 的书籍 Professional IIS 7 and ASP.NET Integrated Programming (ISBN:978-0470152539) 。

简言之,在 IIS 7 之前的版本中,URL 授权规则仅应用于 ASP.NET 运行时处理的资源。 但在 IIS 7 中,可以使用 IIS 的本机 URL 授权功能或集成 ASP。 UrlAuthorizationModule NET 进入 IIS 的 HTTP 管道,从而将此功能扩展到所有请求。

注意

ASP 的方式存在一些微妙但重要的差异。NET 和 UrlAuthorizationModule IIS 7 的 URL 授权功能处理授权规则。 本教程不介绍 IIS 7 的 URL 授权功能,也不介绍与 相比 UrlAuthorizationModule,它分析授权规则的方式的差异。 有关这些主题的详细信息,请参阅 MSDN 上的 IIS 7 文档或 www.iis.net

步骤 1:在 中定义 URL 授权规则Web.config

UrlAuthorizationModule根据应用程序配置中定义的 URL 授权规则,确定是授予还是拒绝对特定标识请求的资源的访问权限。 授权规则在 元素中<authorization>以 和 <deny> 子元素的形式<allow>拼写出来。 每个 <allow><deny> 子元素都可以指定:

  • 特定用户
  • 逗号分隔的用户列表
  • 所有匿名用户,由问号 (?)
  • 所有用户,由星号 (*)

以下标记说明了如何使用 URL 授权规则来允许用户 Tito 和 Scott 并拒绝所有其他用户:

<authorization>
 <allow users="Tito, Scott" />
 <deny users="*" />
</authorization>

元素 <allow> 定义允许的用户-Tito 和 Scott-,而 <deny> 元素指示 拒绝所有用户

注意

<allow><deny> 元素还可以指定角色的授权规则。 我们将在以后的教程中介绍基于角色的授权。

以下设置向 Sam (以外的任何人(包括匿名访问者) )授予访问权限:

<authorization>
 <deny users="Sam" />
</authorization>

若要仅允许经过身份验证的用户,请使用以下配置,这将拒绝所有匿名用户访问:

<authorization>
 <deny users="?" />
</authorization>

授权规则在 中的 Web.config 元素中<system.web>定义,并应用于 Web 应用程序中的所有 ASP.NET 资源。 通常,应用程序对不同部分有不同的授权规则。 例如,在电子商务网站上,所有访问者都可以细读产品、查看产品评论、搜索目录等。 但是,只有经过身份验证的用户才能访问结帐或页面来管理其发货历史记录。 此外,可能有一些网站部分只能由选定用户访问,例如网站管理员。

ASP.NET,可以轻松地为站点中的不同文件和文件夹定义不同的授权规则。 根文件夹 Web.config 文件中指定的授权规则适用于网站中的所有 ASP.NET 资源。 但是,可以通过添加 Web.config<authorization> 节的 来替代特定文件夹的这些默认授权设置。

让我们更新网站,以便只有经过身份验证的用户才能访问 文件夹中的 ASP.NET 页面 Membership 。 为此,我们需要将文件 Web.config 添加到 文件夹, Membership 并将其授权设置设置为拒绝匿名用户。 右键单击Membership解决方案资源管理器中的文件夹,从上下文菜单中选择“添加新项”菜单,然后添加名为 Web.config的新 Web 配置文件。

将Web.config文件添加到成员资格文件夹

图 3:将文件添加到Web.configMembership文件夹 (单击以查看全尺寸图像)

此时,项目应包含两 Web.config 个文件:一个位于根目录中,一个在 Membership 文件夹中。

应用程序现在应包含两个Web.config文件

图 4:应用程序现在应包含两 Web.config 个文件 (单击以查看全尺寸图像)

更新 文件夹中的 Membership 配置文件,使其禁止匿名用户访问。

<?xml version="1.0"?>
<configuration>
 <system.web>
 <authorization>
 <deny users="?" />
 </authorization>
 </system.web>
</configuration>

就是这么简单!

若要测试此更改,请在浏览器中访问主页并确保已注销。由于 ASP.NET 应用程序的默认行为是允许所有访问者,并且由于我们没有对根目录 Web.config 的文件进行任何授权修改,因此我们能够以匿名访问者的身份访问根目录中的文件。

单击左侧列中的“创建用户帐户”链接。 这会将你带到 ~/Membership/CreatingUserAccounts.aspxWeb.config由于 文件夹中的文件Membership定义了禁止匿名访问的授权规则, UrlAuthorizationModule 会中止请求并返回 HTTP 401 未授权状态。 将此 FormsAuthenticationModule 修改为 302 重定向状态,将我们发送到登录页。 请注意,我们尝试访问 (CreatingUserAccounts.aspx) 的页面通过 ReturnUrl querystring 参数传递到登录页。

由于 URL 授权规则禁止匿名访问,因此我们会重定向到登录页

图 5:由于 URL 授权规则禁止匿名访问,因此我们会重定向到登录页 (单击以查看全尺寸图像)

成功登录后,我们会重定向到页面 CreatingUserAccounts.aspx 。 这一次, UrlAuthorizationModule 允许访问页面,因为我们不再匿名。

将 URL 授权规则应用于特定位置

在 的 <system.web> 部分中 Web.config 定义的授权设置将应用于该目录及其子目录中的所有 ASP.NET 资源 (,直到其他 Web.config 文件) 重写为止。 但在某些情况下,我们可能希望给定目录中的所有 ASP.NET 资源都具有特定的授权配置,但一个或两个特定页面除外。 这可以通过在 中添加 Web.config元素<location>来实现,将元素指向授权规则不同的文件,并在其中定义其唯一的授权规则。

为了说明如何使用 <location> 元素替代特定资源的配置设置,让我们自定义授权设置,以便只有 Tito 可以访问 CreatingUserAccounts.aspx。 为此,请将 元素添加到<location>Membership文件夹的 Web.config 文件中,并更新其标记,使其如下所示:

<?xml version="1.0"?>
<configuration>
 <system.web>
 <authorization>
 <deny users="?" />
 </authorization>
 </system.web>

 <location path="CreatingUserAccounts.aspx">
 <system.web>
 <authorization>
 <allow users="Tito" />
 <deny users="*" />
 </authorization>
 </system.web>
 </location>
</configuration>

中的 <authorization><system.web> 元素定义文件夹及其子文件夹中 Membership ASP.NET 资源的默认 URL 授权规则。 元素 <location> 允许我们替代特定资源的这些规则。 在上述标记中, <location> 元素引用 CreatingUserAccounts.aspx 页面并指定其授权规则,例如允许 Tito,但拒绝其他所有人。

若要测试此授权更改,请首先以匿名用户身份访问网站。 如果尝试访问 文件夹中的任何页面 Membership ,例如 UserBasedAuthorization.aspxUrlAuthorizationModule 将拒绝请求,并且会重定向到登录页。 以(例如 Scott)身份登录后,可以访问 文件夹中除 之外CreatingUserAccounts.aspx的任何页面Membership。 尝试以除 Tito 之外的任何用户身份访问 CreatingUserAccounts.aspx 登录将导致未经授权的访问尝试,从而将你重定向回登录页。

注意

元素 <location> 必须出现在配置的 <system.web> 元素之外。 需要对要替代其授权设置的每个资源使用单独的 <location> 元素。

查看如何使用UrlAuthorizationModule授权规则授予或拒绝访问

UrlAuthorizationModule通过一次分析一个 URL 授权规则,从第一个到下一个规则,确定是否为特定 URL 授权特定标识。 一旦找到匹配项,将立即授予或拒绝用户访问权限,具体取决于是否在 或 <deny> 元素中找到<allow>匹配项。 如果未找到匹配项,则向用户授予访问权限。 因此,如果要限制访问,则必须使用 元素作为 URL 授权配置中的最后一个 <deny> 元素。 如果省略<deny>元素,将授予所有用户访问权限。

为了更好地了解 用于 UrlAuthorizationModule 确定颁发机构的过程,请考虑在此步骤前面介绍的示例 URL 授权规则。 第一个规则是允许 <allow> 访问 Tito 和 Scott 的元素。 第二个规则是拒绝 <deny> 所有人访问的元素。 如果匿名用户访问,首先 UrlAuthorizationModule 会询问“是匿名的斯科特还是 Tito? 显然,答案是“否”,因此它继续执行第二条规则。 每个人中都是匿名的吗? 由于此处的答案是“是”,因此规则 <deny> 生效,访问者将重定向到登录页。 同样,如果吉孙来访, UrlAuthorizationModule 首先问,吉孙是斯科特还是蒂托? 既然她不是,就 UrlAuthorizationModule 把第二个问题,吉松在大家的集合中吗? 她,所以她,也被拒绝进入。 最后,如果蒂托访问,提出的第一个问题 UrlAuthorizationModule 是肯定的答案,因此蒂托被授予访问权限。

由于 这些规则 UrlAuthorizationModule 从上到下处理授权规则,因此在任何匹配时停止,因此,在不太具体的规则之前,让更具体的规则出现非常重要。 也就是说,要定义禁止 Jisun 和匿名用户的授权规则,但允许所有其他经过身份验证的用户,应从最具体的规则(影响 Jisun 的规则)开始,然后继续执行不太具体的规则-这些规则允许所有其他经过身份验证的用户,但拒绝所有匿名用户。 以下 URL 授权规则通过首先拒绝 Jisun,然后拒绝任何匿名用户来实现此策略。 除 Jisun 以外的任何经过身份验证的用户都将被授予访问权限,因为这些语句都不匹配 <deny>

<authorization>
 <deny users="Jisun" />
 <deny users="?" />
</authorization>

步骤 2:修复未经授权的、经过身份验证的用户的工作流

如本教程前面在“查看 URL 授权工作流”部分中所述,每当发生未经授权的请求时, 会 UrlAuthorizationModule 中止请求并返回 HTTP 401 未授权状态。 此 401 状态由 FormsAuthenticationModule 修改为将用户发送到登录页的 302 重定向状态。 此工作流发生在任何未经授权的请求上,即使用户已经过身份验证。

将经过身份验证的用户返回到登录页可能会使他们感到困惑,因为他们已经登录到系统。 通过一些工作,我们可以通过将发出未经授权的请求的经过身份验证的用户重定向到说明他们尝试访问受限页面的页面来改进此工作流。

首先,在 Web 应用程序的根文件夹中创建一个名为 UnauthorizedAccess.aspx的新 ASP.NET 页;不要忘记将此页与 Site.master 母版页相关联。 创建此页面后,删除引用 LoginContent ContentPlaceHolder 的 Content 控件,以便显示母版页的默认内容。 接下来,添加一条消息,说明情况,即用户尝试访问受保护的资源。 添加此类消息后, UnauthorizedAccess.aspx 页面的声明性标记应如下所示:

<%@ Page Language="VB" MasterPageFile="~/Site.master" AutoEventWireup="false"
CodeFile="UnauthorizedAccess.aspx.cs" Inherits="UnauthorizedAccess"
Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent"
Runat="Server">
 <h2>Unauthorized Access</h2>
 <p>
 You have attempted to access a page that you are not authorized to view.
 </p>
 <p>
 If you have any questions, please contact the site administrator.
 </p>
</asp:Content>

我们现在需要更改工作流,以便如果未经授权的请求由经过身份验证的用户执行,则会将请求发送到页面 UnauthorizedAccess.aspx 而不是登录页。 将未经授权的请求重定向到登录页的逻辑隐藏在 类的 FormsAuthenticationModule 私有方法中,因此我们无法自定义此行为。 但是,我们可以做的是将自己的逻辑添加到登录页,以便根据需要将用户重定向到 UnauthorizedAccess.aspx

FormsAuthenticationModule当 将未经授权的访问者重定向到登录页时,它会将请求的未授权 URL 追加到名为 ReturnUrl的 querystring 中。 例如,如果未经授权的用户尝试访问 OnlyTito.aspxFormsAuthenticationModule 会将其重定向到 Login.aspx?ReturnUrl=OnlyTito.aspx。 因此,如果经过身份验证的用户使用包含 ReturnUrl 参数的 querystring 访问登录页,则我们知道此未经身份验证的用户只是尝试访问她无权查看的页面。 在这种情况下,我们希望将她重定向到 UnauthorizedAccess.aspx

为此,请将以下代码添加到登录页的 Page_Load 事件处理程序中:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 If Not Page.IsPostBack Then
 If Request.IsAuthenticated AndAlso Not String.IsNullOrEmpty(Request.QueryString("ReturnUrl")) Then
 ' This is an unauthorized, authenticated request...
 Response.Redirect("~/UnauthorizedAccess.aspx")
 End If
 End If
End Sub

上述代码将经过身份验证的未经授权的用户重定向到页面 UnauthorizedAccess.aspx 。 若要查看此逻辑的运行情况,请以匿名访问者身份访问站点,然后单击左侧列中的“创建用户帐户”链接。 这会将你带到 ~/Membership/CreatingUserAccounts.aspx 页面,在步骤 1 中,我们配置为仅允许访问 Tito。 由于禁止匿名用户, FormsAuthenticationModule 会将我们重定向回登录页。

此时,我们是匿名的,因此 Request.IsAuthenticated 返回 False ,我们不会重定向到 UnauthorizedAccess.aspx。 而是显示登录页。 以 Tito 以外的用户身份登录,例如 Bruce。 输入相应的凭据后,登录页会将我们重定向回 ~/Membership/CreatingUserAccounts.aspx。 但是,由于只有 Tito 才能访问此页面,因此我们无权查看它,并会立即返回到登录页。 但是 Request.IsAuthenticated ,这一次返回 True (并且 ReturnUrl querystring 参数) 存在,因此我们将重定向到页面 UnauthorizedAccess.aspx

经过身份验证,未经授权的用户重定向到 UnauthorizedAccess.aspx

图 6:经过身份验证、未经授权的用户被重定向到 UnauthorizedAccess.aspx (单击以查看全尺寸图像)

此自定义工作流通过对图 2 所示的周期进行短路来提供更合理、更直接的用户体验。

步骤 3:基于当前登录的用户限制功能

通过 URL 授权,可以轻松指定粗略的授权规则。 正如我们在步骤 1 中看到的,使用 URL 授权,我们可以简洁地说明允许哪些标识,以及哪些标识被拒绝查看文件夹中的特定页面或所有页面。 但是,在某些情况下,我们可能希望允许所有用户访问某个页面,但会根据访问该页面的用户限制页面的功能。

以电子商务网站为例,该网站允许经过身份验证的访问者查看其产品。 当匿名用户访问产品页面时,他们只会看到产品信息,不会有机会离开评论。 但是,访问同一页面的经过身份验证的用户将看到审阅界面。 如果经过身份验证的用户尚未查看此产品,则界面将允许他们提交评审;否则,它将向他们显示他们以前提交的评论。 若要进一步了解此方案,产品页面可能会显示其他信息,并为电子商务公司工作的用户提供扩展功能。 例如,产品页面可能会列出库存,并在员工访问时包括用于编辑产品价格和说明的选项。

可以通过声明方式或编程方式 (或通过两) 的某种组合来实现此类细粒度授权规则。 在下一部分中,我们将了解如何通过 LoginView 控件实现精细授权。 接下来,我们将探索编程技术。 但是,在了解如何应用精细授权规则之前,我们首先需要创建一个页面,其功能取决于访问它的用户。

让我们创建一个页面,其中列出了 GridView 中特定目录中的文件。 除了列出每个文件的名称、大小和其他信息外,GridView 还将包含两列 LinkButton:一列标题为“视图”,另一列标题为“删除”。 如果单击“查看链接按钮”,将显示所选文件的内容;如果单击“删除 LinkButton”,则将删除该文件。 我们首先创建此页面,以便其查看和删除功能可供所有用户使用。 在“使用 LoginView 控件”和“以编程方式限制功能”部分中,我们将了解如何根据访问页面的用户启用或禁用这些功能。

注意

我们即将生成的 ASP.NET 页使用 GridView 控件显示文件列表。 由于本教程系列侧重于表单身份验证、授权、用户帐户和角色,因此我不想花太多时间讨论 GridView 控件的内部工作原理。 虽然本教程提供了有关设置此页面的具体分步说明,但它不会深入探讨为何做出某些选择,或特定属性对呈现的输出产生的影响的详细信息。 有关 GridView 控件的彻底检查,请参阅我在 ASP.NET 2.0 中使用数据 教程系列。

首先打开 文件夹中的 UserBasedAuthorization.aspx 文件 Membership ,并将 GridView 控件添加到名为 FilesGrid的页面。 在 GridView 的智能标记中,单击“编辑列”链接以启动“字段”对话框。 在此处,取消选中左下角的“自动生成字段”复选框。 接下来,从左上角添加一个“选择”按钮、一个“删除”按钮和两个 BoundField, (“选择”和“删除”按钮可以在 CommandField 类型) 下找到。 将“选择”按钮的 SelectText 属性设置为“视图”,将第一个 BoundField 的 HeaderTextDataField 属性设置为“名称”。 将第二个 BoundField 的 HeaderText 属性设置为 Size(以字节为单位),将其 DataField 属性设置为 Length,将其 DataFormatString 属性设置为 HtmlEncode{0:N0} False。

配置 GridView 的列后,单击“确定”关闭“字段”对话框。 在属性窗口中,将 GridView 的 DataKeyNames 属性设置为 FullName。 此时,GridView 的声明性标记应如下所示:

<asp:GridView ID="FilesGrid" DataKeyNames="FullName" runat="server" AutoGenerateColumns="False">
 <Columns>
 <asp:CommandField SelectText="View" ShowSelectButton="True"/>
 <asp:CommandField ShowDeleteButton="True" />
 <asp:BoundField DataField="Name" HeaderText="Name" />
 <asp:BoundField DataField="Length" DataFormatString="{0:N0}"
 HeaderText="Size in Bytes" HtmlEncode="False" />
 </Columns>
</asp:GridView>

创建 GridView 标记后,我们便可以编写代码来检索特定目录中的文件并将其绑定到 GridView。 将以下代码添加到页面的 Page_Load 事件处理程序:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 If Not Page.IsPostBack Then
 Dim appPath As String = Request.PhysicalApplicationPath
 Dim dirInfo As New DirectoryInfo(appPath)

 Dim files() As FileInfo = dirInfo.GetFiles()

 FilesGrid.DataSource = files
 FilesGrid.DataBind()
 End If
End Sub

上述代码使用 DirectoryInfo 获取应用程序根文件夹中的文件列表。 方法GetFiles()将目录中的所有文件作为 对象的数组FileInfo返回,然后绑定到 GridView。 对象 FileInfo 具有各种属性,例如 NameLengthIsReadOnly等。 从声明性标记中可以看到,GridView 仅 Name 显示 和 Length 属性。

注意

DirectoryInfoFileInfo 类位于 命名空间中System.IO。 因此,需要将这些类名与其命名空间名称开头,或者通过 Imports System.IO) 将命名空间导入到类文件中 (。

请花点时间通过浏览器访问此页面。 它将显示驻留在应用程序的根目录中的文件列表。 单击任何“查看”或“删除链接按钮”将导致回发,但不会发生任何操作,因为我们尚未创建必要的事件处理程序。

GridView 列出 Web 应用程序的根目录中的文件

图 7:GridView 列出 Web 应用程序的根目录中的文件 (单击以查看全尺寸图像)

我们需要一种方法来显示所选文件的内容。 返回到 Visual Studio,并在 GridView 上方添加名为 FileContents 的 TextBox。 将其 TextMode 属性分别设置为 MultiLine ,将其 ColumnsRows 属性分别设置为 95% 和 10。

<asp:TextBox ID="FileContents" runat="server" Rows="10"
TextMode="MultiLine" Width="95%"></asp:TextBox>

接下来,为 GridView 的事件 SelectedIndexChanged 创建事件处理程序,并 添加以下代码:

Protected Sub FilesGrid_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles FilesGrid.SelectedIndexChanged
 ' Open the file and display it
 Dim fullFileName As String = FilesGrid.SelectedValue.ToString()
 Dim contents As String = File.ReadAllText(fullFileName)
 FileContents.Text = contents
End Sub

此代码使用 GridView 的 SelectedValue 属性来确定所选文件的完整文件名。 在内部, DataKeys 引用集合是为了获取 SelectedValue,因此,必须将 GridView 的 DataKeyNames 属性设置为 Name,如此步骤前面所述。 File用于将所选文件的内容读入字符串,然后将该字符串分配给 FileContents TextBox 的 Text 属性,从而在页面上显示所选文件的内容。

所选文件的内容显示在 TextBox 中

图 8:所选文件的内容显示在 TextBox (单击以查看全尺寸图像)

注意

如果查看包含 HTML 标记的文件的内容,然后尝试查看或删除文件,将收到错误 HttpRequestValidationException 。 这是因为在回发时,TextBox 的内容将发送回 Web 服务器。 默认情况下,每当检测到潜在的危险回发内容(如 HTML 标记)时,ASP.NET 将引发 HttpRequestValidationException 错误。 若要禁止发生此错误,请通过将 添加到 ValidateRequest="false"@Page 指令来关闭页面的请求验证。 有关请求验证的好处以及禁用请求验证时应采取的预防措施的详细信息,请阅读 请求验证 - 防止脚本攻击

最后,使用以下代码为 GridView 的事件RowDeleting添加事件处理程序:

Protected Sub FilesGrid_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs)Handles FilesGrid.RowDeleting
 Dim fullFileName As String = FilesGrid.DataKeys(e.RowIndex).Value.ToString()
 FileContents.Text = String.Format("You have opted to delete {0}.", fullFileName)

 ' To actually delete the file, uncomment the following line
 ' File.Delete(fullFileName)
End Sub

代码仅显示要删除的 TextBox 中的 FileContents 文件的完整名称, 而不会 实际删除该文件。

单击“删除”按钮实际上不会删除文件

图 9:单击“删除”按钮实际上不会删除文件 (单击以查看全尺寸图像)

在步骤 1 中,我们配置了 URL 授权规则,禁止匿名用户查看文件夹中的页面 Membership 。 为了更好地展示精细身份验证,让我们允许匿名用户访问 UserBasedAuthorization.aspx 页面,但功能有限。 若要打开此页以让所有用户访问,请将以下 <location> 元素添加到 Web.config 文件夹中的 Membership 文件中:

<location path="UserBasedAuthorization.aspx">
 <system.web>
 <authorization>
 <allow users="*" />
 </authorization>
 </system.web>
</location>

添加此 <location> 元素后,通过注销网站来测试新的 URL 授权规则。 作为匿名用户,应允许你访问该 UserBasedAuthorization.aspx 页面。

目前,任何经过身份验证或匿名的用户都可以访问页面 UserBasedAuthorization.aspx 并查看或删除文件。 让我们这样做,以便只有经过身份验证的用户可以查看文件的内容,只有 Tito 可以删除文件。 可以通过声明方式、编程方式或通过这两种方法的组合来应用此类细粒度授权规则。 让我们使用声明性方法来限制可以查看文件内容的人员;我们将使用编程方法来限制可以删除文件的人员。

使用 LoginView 控件

正如我们在以前的教程中看到的那样,LoginView 控件可用于显示经过身份验证的用户和匿名用户的不同界面,并提供一种简单的方法来隐藏匿名用户无法访问的功能。 由于匿名用户无法查看或删除文件,因此只需在 FileContents 经过身份验证的用户访问页面时显示 TextBox。 为此,请将 LoginView 控件添加到页面,将其 LoginViewForFileContentsTextBox命名为 ,并将 TextBox 的声明性标记移动到 FileContents LoginView 控件的 LoggedInTemplate中。

<asp:LoginView ID=" LoginViewForFileContentsTextBox " runat="server">
 <LoggedInTemplate>
 <p>
 <asp:TextBox ID="FileContents" runat="server" Rows="10"
 TextMode="MultiLine" Width="95%"></asp:TextBox>
 </p>
 </LoggedInTemplate>
</asp:LoginView>

LoginView 的模板中的 Web 控件不再可直接从代码隐藏类进行访问。 例如, FilesGrid GridView 的 SelectedIndexChangedRowDeleting 事件处理程序当前使用如下代码引用 FileContents TextBox 控件:

FileContents.Text = text

但是,此代码不再有效。 通过将 TextBox 移动到 FileContentsLoggedInTemplate TextBox 中,无法直接访问。 相反,我们必须使用 FindControl("controlId") 方法以编程方式引用控件。 更新 FilesGrid 事件处理程序以引用 TextBox,如下所示:

Dim FileContentsTextBox As TextBox = CType(LoginViewForFileContentsTextBox.FindControl("FileContents"),TextBox)
FileContentsTextBox.Text = text

将 TextBox 移动到 LoginView 的 LoggedInTemplate 并更新页面的代码以使用 FindControl("controlId") 模式引用 TextBox 后,以匿名用户身份访问页面。 如图 10 所示, FileContents 未显示 TextBox。 但是,仍会显示“查看链接按钮”。

LoginView 控件仅呈现经过身份验证的用户的 FileContents TextBox

图 10:LoginView 控件仅呈现 FileContents 经过身份验证的用户的 TextBox (单击以查看全尺寸图像)

隐藏匿名用户的“视图”按钮的一种方法是将 GridView 字段转换为 TemplateField。 这将生成一个模板,其中包含 View LinkButton 的声明性标记。 然后,我们可以将 LoginView 控件添加到 TemplateField,并将 LinkButton 置于 LoginView 的 LoggedInTemplate中,从而对匿名访问者隐藏“查看”按钮。 为此,请单击 GridView 的智能标记中的“编辑列”链接以启动“字段”对话框。 接下来,从左下角的列表中选择“选择”按钮,然后单击“将此字段转换为 TemplateField”链接。 这样做将修改字段的声明性标记:

<asp:CommandField SelectText="View" ShowSelectButton="True"/>

到:

<asp:TemplateField ShowHeader="False">
 <ItemTemplate>
 <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False"
 CommandName="Select" Text="View"></asp:LinkButton>
 </ItemTemplate>
</asp:TemplateField>

此时,我们可以将 LoginView 添加到 TemplateField。 以下标记仅为经过身份验证的用户显示“查看 LinkButton”。

<asp:TemplateField ShowHeader="False">
 <ItemTemplate>
 <asp:LoginView ID="LoginView1" runat="server">
 <LoggedInTemplate>
 <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False"
 CommandName="Select" Text="View"></asp:LinkButton>
 </LoggedInTemplate>
 </asp:LoginView>
 </ItemTemplate>
</asp:TemplateField>

如图 11 所示,最终结果并不那么漂亮,因为“视图”列仍显示,即使列内的“视图链接按钮”处于隐藏状态。 下一部分将介绍如何隐藏整个 GridView 列 (,而不仅仅是 LinkButton) 。

LoginView 控件隐藏匿名访问者的视图链接按钮

图 11:LoginView 控件隐藏匿名访问者的视图链接按钮 (单击以查看全尺寸图像)

以编程方式限制功能

在某些情况下,声明性技术不足以将功能限制为页面。 例如,某些页面功能的可用性可能取决于访问页面的用户是匿名用户还是经过身份验证的条件。 在这种情况下,可以通过编程方式显示或隐藏各种用户界面元素。

为了以编程方式限制功能,我们需要执行两个任务:

  1. 确定访问页面的用户是否可以访问该功能,以及
  2. 根据用户是否有权访问有关功能,以编程方式修改用户界面。

为了演示这两个任务的应用,让我们只允许 Tito 从 GridView 中删除文件。 然后,我们的第一个任务是确定是否是 Tito 访问页面。 确定后,我们需要隐藏 (或显示 gridView 的“删除”列) 。 GridView 的列可通过其 Columns 属性进行访问;仅当 Visible 列的 属性设置为 True (默认) 时,才会呈现该列。

将数据绑定到 GridView 之前, Page_Load 将以下代码添加到事件处理程序:

' Is this Tito visiting the page?
Dim userName As String = User.Identity.Name
If String.Compare(userName, "Tito", True) = 0 Then
 ' This is Tito, SHOW the Delete column
 FilesGrid.Columns(1).Visible = True
Else
 ' This is NOT Tito, HIDE the Delete column
 FilesGrid.Columns(1).Visible = False
End If

正如我们在 Forms 身份验证概述 教程中所述, User.Identity.Name 返回标识的名称。 这对应于在登录控件中输入的用户名。 如果它是 Tito 访问页面,GridView 的第二列的 Visible 属性设置为 True;否则,它设置为 False。 最终结果是,当 Tito 以外的人访问页面时,无论是另一个经过身份验证的用户还是匿名用户,“删除”列不会呈现 (见图 12) ;但是,当 Tito 访问页面时,“删除”列 (见图 13) 。

当 Tito ((如 Bruce) )以外的人访问时,“删除”列不会呈现

图 12:当 T (ito 以外的其他人(如 Bruce)访问时,“删除列”不会呈现) (单击以查看全尺寸图像)

为 Tito 呈现删除列

图 13:为 Tito 呈现删除列 (单击以查看全尺寸图像)

步骤 4:将授权规则应用于类和方法

在步骤 3 中,我们禁止匿名用户查看文件的内容,并禁止除 Tito 之外的所有用户删除文件。 这是通过声明性和编程技术为未经授权的访问者隐藏关联的用户界面元素来实现的。 在我们的简单示例中,正确隐藏用户界面元素非常简单,但是对于更复杂的网站,可能有许多不同的方法来执行相同的功能呢? 将此功能限制为未经授权的用户时,如果我们忘记隐藏或禁用所有适用的用户界面元素,会发生什么情况?

确保未经授权的用户访问特定功能块的一种简单方法是使用 PrincipalPermission 属性修饰该类或方法。 当 .NET 运行时使用类或执行其方法之一时,它会检查以确保当前安全上下文有权使用该类或执行方法。 属性 PrincipalPermission 提供了一种机制,我们可以通过该机制来定义这些规则。

让我们演示如何在 PrincipalPermission GridView 的 SelectedIndexChangedRowDeleting 事件处理程序上使用 属性来分别禁止匿名用户和 Tito 以外的用户执行。 只需在每个函数定义上添加相应的属性:

<PrincipalPermission(SecurityAction.Demand, Authenticated:=True)> _
Protected Sub FilesGrid_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles FilesGrid.SelectedIndexChanged
 ...
End Sub

<PrincipalPermission(SecurityAction.Demand, Name:="Tito")> _
Protected Sub FilesGrid_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles FilesGrid.RowDeleting
 ...
End Sub

事件处理程序的 SelectedIndexChanged 属性指示只有经过身份验证的用户才能执行事件处理程序,其中 作为 事件处理程序上的 RowDeleting 属性将执行限制为 Tito。

注意

特性可以应用于类、方法、属性或事件。 添加特性时,它必须是类、方法、属性或事件声明语句的一部分。 由于 Visual Basic 使用换行符作为语句分隔符,因此属性必须出现在声明所在的同一行中,或者直接显示在声明上方,并带有行继续符 (下划线) 。 在上面的代码片段中,行延续字符用于将属性放在一行上,将方法声明放在另一行上。

如果 Tito 以外的用户尝试执行 RowDeleting 事件处理程序或未经身份验证的用户尝试执行 SelectedIndexChanged 事件处理程序,则 .NET 运行时将引发 SecurityException

如果安全上下文无权执行方法,则会引发 SecurityException

图 14:如果安全上下文无权执行该方法, SecurityException 则会引发 (单击以查看全尺寸图像)

注意

若要允许多个安全上下文访问一个类或方法,请使用每个安全上下文的属性 PrincipalPermission 修饰类或方法。 也就是说,若要允许 Tito 和 Bruce 执行 RowDeleting 事件处理程序,请添加 两个PrincipalPermission 属性:

<PrincipalPermission(SecurityAction.Demand, Name:="Tito")> _

<PrincipalPermission(SecurityAction.Demand, Name:="Bruce")> _

除了 ASP.NET 页外,许多应用程序还具有包含各种层的体系结构,例如业务逻辑和数据访问层。 这些层通常作为类库实现,并提供用于执行业务逻辑和数据相关功能的类和方法。 属性 PrincipalPermission 可用于将授权规则应用于这些层。

有关使用 PrincipalPermission 属性定义类和方法的授权规则的详细信息,请参阅 Scott Guthrie 的博客文章 使用 将授权规则添加到业务和数据层 PrincipalPermissionAttributes

摘要

本教程介绍了如何应用基于用户的授权规则。 我们从 ASP 开始。NET 的 URL 授权框架。 在每个请求中,ASP.NET 引擎的 UrlAuthorizationModule 检查应用程序配置中定义的 URL 授权规则,以确定标识是否有权访问请求的资源。 简而言之,通过 URL 授权,可以轻松地为特定页面或特定目录中的所有页面指定授权规则。

URL 授权框架逐页应用授权规则。 使用 URL 授权,请求标识有权访问特定资源或无权访问。 但是,许多方案需要更精细的授权规则。 我们可能需要让每个人都访问页面,而不是定义允许谁访问页面,而是显示不同的数据或提供不同的功能,具体取决于访问页面的用户。 页面级授权通常涉及隐藏特定的用户界面元素,以防止未经授权的用户访问禁止的功能。 此外,还可以使用特性来限制对类的访问,并限制某些用户执行其方法。

编程愉快!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

Scott Mitchell 是多本 ASP/ASP.NET 书籍的作者,4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可在 上或通过他的博客http://ScottOnWriting.NET联系 mitchell@4guysfromrolla.com Scott。

特别感谢

本教程系列由许多有用的审阅者查看。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com