基于用户的身份验证 (C#)

作者 :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 授权工作流

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

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

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

我们将检查步骤 1 中 URL 授权规则的语法,但首先让我们看一 UrlAuthorizationModule 下 根据请求是否获得授权来做什么。 UrlAuthorizationModule如果 确定请求已授权,则不执行任何工作,并且请求将在其整个生命周期内继续。 但是,如果请求 授权,则会 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.aspx。 将 UrlAuthorizationModule 停止请求生命周期并返回 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 的书籍 专业 IIS 7 和 ASP.NET 集成编程 (ISBN:978-0470152539) 。

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

注意

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

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

UrlAuthorizationModule根据应用程序配置中定义的 URL 授权规则,确定是授予还是拒绝对特定标识的请求资源的访问权限。 授权规则以 和 子元素的形式<allow><deny>在 元素中<authorization>拼出。 每个 <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="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
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 void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        if (Request.IsAuthenticated && !string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]))
        // This is an unauthorized, authenticated request...
        Response.Redirect("~/UnauthorizedAccess.aspx");
    }
}

上述代码将经过身份验证的未经授权的用户重定向到页面 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:一列标题为“视图”,另一列标题为“删除”。 如果单击“View 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 设置为 {0:N0} ,将其 HtmlEncode 属性设置为 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 void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        string appPath = Request.PhysicalApplicationPath;
        DirectoryInfo dirInfo = new DirectoryInfo(appPath);

        FileInfo[] files = dirInfo.GetFiles();

        FilesGrid.DataSource = files;
        FilesGrid.DataBind();
    }
}

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

注意

DirectoryInfoFileInfo 类位于 命名空间中System.IO。 因此,需要用这些类名的命名空间名称开头,或者通过 using 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 void FilesGrid_SelectedIndexChanged(object sender, EventArgs e)
{
    // Open the file and display it
    string fullFileName = FilesGrid.SelectedValue.ToString();
    string contents = File.ReadAllText(fullFileName);
    FileContents.Text = contents;
}

此代码使用 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 void FilesGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    string fullFileName = 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);
}

代码仅显示 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,无法直接访问 TextBox。 相反,我们必须使用 FindControl("controlId") 方法以编程方式引用 控件。 更新 FilesGrid 事件处理程序以引用 TextBox,如下所示:

TextBox FileContentsTextBox = LoginViewForFileContentsTextBox.FindControl("FileContents") as TextBox;
FileContentsTextBox.Text = text;

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

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。 以下标记仅显示经过身份验证的用户的 View 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 所示,最终结果并不那么漂亮,因为“视图”列仍然显示,即使列内的 View LinkButton 处于隐藏状态。 下一部分将介绍如何隐藏整个 GridView 列 (,而不仅仅是 LinkButton) 。

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

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

以编程方式限制功能

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

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

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

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

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

// Is this Tito visiting the page?
string userName = User.Identity.Name;
if (string.Compare(userName, "Tito", true) == 0)
    // 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;

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

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

图 12:当 Tito (以外的人(如 Bruce)访问时,“删除列”未呈现) (单击以查看全尺寸图像)

为 Tito 呈现删除列

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

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

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

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

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

[PrincipalPermission(SecurityAction.Demand, Authenticated=true)]
protected void FilesGrid_SelectedIndexChanged(object sender, EventArgs e)
{
    ...
}

[PrincipalPermission(SecurityAction.Demand, Name="Tito")]
protected void FilesGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    ...
}

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

如果 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。 可以在 上联系 mitchell@4guysfromrolla.com 斯科特,也可以通过他的博客在 联系 http://ScottOnWriting.NET

特别感谢

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