表单身份验证 (C#) 概述

作者 :Scott Mitchell

注意

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

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

下载代码下载 PDF

在本教程中,我们将从单纯的讨论转向实现;具体而言,我们将介绍如何实现表单身份验证。 本教程中开始构造的 Web 应用程序将继续在后续教程中构建,因为我们会从简单的表单身份验证转向成员身份和角色。

有关本主题的详细信息,请参阅此视频: 在 ASP.NET 中使用基本窗体身份验证

简介

前面的教程中 ,我们讨论了 ASP.NET 提供的各种身份验证、授权和用户帐户选项。 在本教程中,我们将从单纯的讨论转向实现;具体而言,我们将介绍如何实现表单身份验证。 本教程中开始构造的 Web 应用程序将继续在后续教程中构建,因为我们会从简单的表单身份验证转向成员身份和角色。

本教程首先深入探讨表单身份验证工作流,这是我们在上一教程中涉及的主题。 之后,我们将创建一个 ASP.NET 网站,通过该网站演示表单身份验证的概念。 接下来,我们将配置站点以使用表单身份验证,创建一个简单的登录页,并了解如何在代码中确定用户是否经过身份验证,如果是,则确定他们登录时使用的用户名。

了解表单身份验证工作流、在 Web 应用程序中启用它以及创建登录和注销页面都是构建支持用户帐户并通过网页对用户进行身份验证的 ASP.NET 应用程序的重要步骤。 因此,由于这些教程相互构建,因此,即使你在过去项目中已具有配置表单身份验证的经验,在继续下一个教程之前,我仍建议你完整完成本教程。

了解表单身份验证工作流

当 ASP.NET 运行时处理对 ASP.NET 资源(例如 ASP.NET 页或 ASP.NET Web 服务)的请求时,该请求在其生命周期内引发大量事件。 在请求最开始和最结束时引发的事件、在对请求进行身份验证和授权时引发的事件、在未经处理的异常中引发的事件,等等。 若要查看事件的完整列表,请参阅 HttpApplication 对象的事件

HTTP 模块 是托管类,其代码执行以响应请求生命周期中的特定事件。 ASP.NET 附带了许多在后台执行基本任务的 HTTP 模块。 与讨论特别相关的两个内置 HTTP 模块是:

  • FormsAuthenticationModule – 通过检查表单身份验证票证对用户进行身份验证,该票证通常包含在用户的 Cookie 集合中。 如果不存在表单身份验证票证,则用户是匿名的。
  • UrlAuthorizationModule – 确定是否授权当前用户访问请求的 URL。 此模块通过查阅应用程序配置文件中指定的授权规则来确定颁发机构。 ASP.NET 还包括 FileAuthorizationModule ,它通过查阅请求的文件 () ACL 来确定颁发机构。

尝试 FormsAuthenticationModule 在 (之前对用户进行身份验证, UrlAuthorizationModuleFileAuthorizationModule) 执行。 如果发出请求的用户无权访问请求的资源,授权模块将终止请求并返回 HTTP 401 未授权 状态。 在Windows 身份验证方案中,HTTP 401 状态将返回到浏览器。 此状态代码会导致浏览器通过模式对话框提示用户输入其凭据。 但是,使用表单身份验证时,HTTP 401“未授权”状态永远不会发送到浏览器,因为 FormsAuthenticationModule 会检测此状态并将其修改为将用户重定向到登录页,而是通过 HTTP 302 重定向 状态) (。

登录页的职责是确定用户的凭据是否有效,如果是,则创建表单身份验证票证并将用户重定向回他们尝试访问的页面。 身份验证票证包含在对网站上页面的后续请求中,用于 FormsAuthenticationModule 标识用户。

表单身份验证工作流

图 1:表单身份验证工作流

记住跨页面访问的身份验证票证

登录后,必须在每个请求中将表单身份验证票证发送回 Web 服务器,以便用户在浏览站点时保持登录状态。 这通常是通过将身份验证票证放入用户的 Cookie 集合来实现的。 Cookie 是驻留在用户计算机上的小型文本文件,在创建 Cookie 的网站的每个请求中以 HTTP 标头传输。 因此,创建表单身份验证票证并将其存储在浏览器的 Cookie 中后,每次后续访问该站点都会发送身份验证票证以及请求,从而标识用户。

Cookie 的一个方面是其过期时间,即浏览器放弃 Cookie 的日期和时间。 当表单身份验证 Cookie 过期时,用户将无法再进行身份验证,因此成为匿名用户。 当用户从公共终端访问时,他们很可能希望其身份验证票证在关闭浏览器时过期。 但是,当从家访问时,同一用户可能希望在浏览器重启后记住身份验证票证,这样他们就不必在每次访问站点时重新登录。 此决定通常由用户在登录页上以“记住我”复选框的形式做出。 在步骤 3 中,我们将检查如何在登录页中实现“记住我”复选框。 以下教程详细介绍了身份验证票证超时设置。

注意

用于登录网站的用户代理可能不支持 Cookie。 在这种情况下,ASP.NET 可以使用无 Cookie forms 身份验证票证。 在此模式下,身份验证票证将编码为 URL。 我们将在下一教程中介绍何时使用无 Cookie 身份验证票证以及如何创建和管理这些票证。

表单身份验证的范围

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

但是,IIS 7 允许集成 IIS 和 ASP.NET 管道。 通过一些配置设置,可以设置 IIS 7 以 对所有 请求调用 FormsAuthenticationModule。 此外,使用 IIS 7,可以为任何类型的文件定义 URL 授权规则。 有关详细信息,请参阅 IIS6 和 IIS7 安全性之间的更改Web 平台安全性和了解 IIS7 URL 授权

长话短说,在 IIS 7 之前的版本中,只能使用表单身份验证来保护 ASP.NET 运行时处理的资源。 同样,URL 授权规则仅应用于 ASP.NET 运行时处理的资源。 但在 IIS 7 中,可以将 FormsAuthenticationModule 和 UrlAuthorizationModule 集成到 IIS 的 HTTP 管道中,从而将此功能扩展到所有请求。

步骤 1:为本教程系列创建 ASP.NET 网站

为了吸引尽可能广泛的受众,我们将在本系列中构建 ASP.NET 网站将使用 Microsoft 的免费版 Visual Studio 2008 Visual Web Developer 2008 创建。 我们将在 Microsoft SQL Server 2005 Express Edition 数据库中实现SqlMembershipProvider用户存储。 如果使用 Visual Studio 2005 或其他版本的 Visual Studio 2008 或 SQL Server,请不要担心 - 步骤几乎相同,并且会指出任何不起眼的差异。

注意

每个教程中使用的演示 Web 应用程序都以下载方式提供。 此可下载应用程序是使用 Visual Web Developer 2008 创建的,该版本面向.NET Framework版本 3.5。 由于应用程序面向 .NET 3.5,因此其 Web.config 文件包含其他特定于 3.5 的配置元素。 简而言之,如果尚未在计算机上安装 .NET 3.5,则无需先从 Web.config 中删除 3.5 特定标记,可下载的 Web 应用程序将无法正常工作。

在配置表单身份验证之前,我们首先需要一个 ASP.NET 网站。 首先创建一个新的基于文件系统 ASP.NET 网站。 若要完成此操作,请启动 Visual Web Developer,然后转到“文件”菜单并选择“新建网站”,显示“新建网站”对话框。 选择“ASP.NET 网站”模板,将“位置”下拉列表设置为“文件系统”,选择要放置网站的文件夹,并将语言设置为 C#。 这将创建一个包含Default.aspx ASP.NET 页、App_Data文件夹和 Web.config 文件的新网站。

注意

Visual Studio 支持两种项目管理模式:网站项目和 Web 应用程序项目。 网站项目缺少项目文件,而 Web 应用程序项目模拟 Visual Studio .NET 2002/2003 中的项目体系结构 - 它们包含项目文件并将项目的源代码编译为单个程序集,该程序集位于 /bin 文件夹中。 Visual Studio 2005 最初仅支持网站项目,但 Web 应用程序项目模型已随 Service Pack 1 重新引入;Visual Studio 2008 提供这两种项目模型。 但是,Visual Web Developer 2005 和 2008 版本仅支持网站项目。 我将使用网站项目模型。 如果你使用的是非 Express 版本,并且想要改用 Web 应用程序项目模型 ,请随意执行此操作,但请注意,你在屏幕上看到的内容与必须执行的步骤与这些教程中所示的屏幕截图和提供的说明之间可能存在一些差异。

System-Based 网站创建新文件

图 2:System-Based 网站创建新文件 (单击以查看全尺寸图像)

添加母版页

接下来,将新的母版页添加到名为 Site.master 的根目录中的网站。 母版页 使页面开发人员能够定义可应用于 ASP.NET 页面的网站范围模板。 母版页main好处是,网站的整体外观可以在单个位置定义,从而可以轻松更新或调整网站的布局。

将名为 Site.master 的母版页添加到网站

图 3:将名为 Site.master 的母版页添加到网站 (单击以查看全尺寸图像)

在母版页中定义网站范围的页面布局。 可以使用“设计”视图并添加所需的任何布局或 Web 控件,也可以在“源”视图中手动添加标记。 我构建了母版页的布局,以模拟 在 ASP.NET 2.0 中处理数据 教程系列中使用的布局 (请参阅图 4) 。 母版页使用 级联样式表 来定位和样式,这些设置在Style.css (包含在本教程的关联下载) 中。 虽然无法从下面所示的标记中判断,但 CSS 规则的定义是,导航 <div> 的内容绝对定位,使其显示在左侧,并且具有 200 像素的固定宽度。

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inherits="Site" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Forms Authentication, Authorization, and User Accounts</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div id="wrapper">
        <form id="form1" runat="server">
        
            <div id="header">
                <span class="title">User Account Tutorials</span>
            </div>
        
            <div id="content">
                <asp:contentplaceholder id="MainContent" runat="server">
                  <!-- Page-specific content will go here... -->
                </asp:contentplaceholder>
            </div>
            
            <div id="navigation">
                TODO: Menu will go here...
            </div>
        </form>
    </div>
</body>
</html>

母版页定义静态页面布局和可由使用母版页的 ASP.NET 页编辑的区域。 这些内容可编辑区域由 ContentPlaceHolder 控件指示,可在内容 <div> 中查看。 母版页有一 ContentPlaceHolder 个 (MainContent) ,但母版页可能有多个 ContentPlaceHolder。

使用上面输入的标记,切换到“设计”视图会显示母版页的布局。 使用此母版页的任何 ASP.NET 页面都将具有此统一布局,并且能够指定 MainContent 区域的标记。

通过设计视图查看母版页

图 4:通过设计视图查看母版页 (单击以查看全尺寸图像)

创建内容页

此时,我们网站中有一个Default.aspx页面,但它不使用刚刚创建的母版页。 虽然可以操作网页的声明性标记以使用母版页,但如果页面不包含任何内容,则只需删除页面并将其重新添加到项目,并指定要使用的母版页会更容易。 因此,首先从项目中删除Default.aspx。

接下来,右键单击解决方案资源管理器中的项目名称,然后选择添加名为 Default.aspx 的新 Web 窗体。 这一次,检查“选择母版页”复选框,然后从列表中选择 Site.master 母版页。

添加新Default.aspx页 选择母版页

图 5:添加新Default.aspx页 选择选择母版页 (单击以查看全尺寸图像)

使用 Site.master 母版页

图 6:使用 Site.master 母版页

注意

如果使用 Web 应用程序项目模型,“添加新项”对话框不包含“选择母版页”复选框。 而是需要添加类型为“Web 内容表单”的项。选择“Web 内容窗体”选项并单击“添加”后,Visual Studio 将显示如图 6 所示的“选择主控形状”对话框。

新的Default.aspx页的声明性标记仅包括一个 @Page 指令,该指令指定母版页文件的路径,以及母版页的 MainContent ContentPlaceHolder 的 Content 控件。

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
</asp:Content>

现在,请将Default.aspx留空。 本教程稍后将返回到它以添加内容。

注意

母版页包含菜单或其他导航界面的分区。 我们将在将来的教程中创建这样的接口。

步骤 2:启用表单身份验证

创建 ASP.NET 网站后,下一个任务是启用表单身份验证。 应用程序的身份验证配置是通过 <authentication> Web.config 中的 元素指定的。元素<authentication>包含一个名为 mode 的属性,该属性指定应用程序使用的身份验证模型。 此属性可以具有以下四个值之一:

  • Windows – 如前面的教程中所述,当应用程序使用 Windows 身份验证 Web 服务器负责对访问者进行身份验证,这通常通过基本、摘要或集成Windows 身份验证完成。
  • 表单 - 用户通过网页上的表单进行身份验证。
  • Passport - 使用 Microsoft 的 Passport 网络对用户进行身份验证。
  • – 不使用身份验证模型;所有访问者都是匿名的。

默认情况下,ASP.NET 应用程序使用 Windows 身份验证。 若要将身份验证类型更改为 forms 身份验证,需要将 <authentication> 元素的 mode 属性修改为 Forms。

如果项目尚不包含 Web.config 文件,请立即添加一个,方法是右键单击解决方案资源管理器中的项目名称,选择“添加新项”,然后添加 Web 配置文件。

如果项目尚未包含 Web.config,请立即添加

图 7:如果项目尚不包含 Web.config,请立即添加它 (单击以查看全尺寸图像)

接下来,找到 元素 <authentication> 并将其更新为使用表单身份验证。 进行此更改后,Web.config 文件的标记应如下所示:

<configuration>
    <system.web>
        ... Unrelated configuration settings and comments removed for brevity ...
        <!--
            The <authentication> section enables configuration 
            of the security authentication mode used by 
            ASP.NET to identify an incoming user. 
        -->
        <authentication mode="Forms" />
    </system.web>
</configuration>

注意

由于 Web.config 是 XML 文件,因此大小写非常重要。 请确保将 mode 属性设置为 Forms,大写为“F”。 如果使用其他大小写(如“forms”),则通过浏览器访问站点时将收到配置错误。

元素 <authentication> 可以选择包含包含 <forms> 表单身份验证特定设置的子元素。 现在,我们只需使用默认表单身份验证设置。 我们将在下一教程中更详细地探讨 <forms> 子元素。

步骤 3:生成登录页

为了支持表单身份验证,网站需要一个登录页面。 如“了解表单身份验证工作流”部分所述, FormsAuthenticationModule 如果用户尝试访问无权查看的页面,将自动将用户重定向到登录页。 还有 ASP.NET Web 控件,这些控件将向匿名用户显示指向登录页的链接。 这引出了一个问题,“登录页的 URL 是什么?”

默认情况下,表单身份验证系统要求登录页Login.aspx命名并放置在 Web 应用程序的根目录中。 如果要使用不同的登录页 URL,可以在 Web.config 中指定它。我们将在后续教程中了解如何执行此操作。

登录页有三个职责:

  1. 提供允许访问者输入其凭据的接口。
  2. 确定提交的凭据是否有效。
  3. 通过创建表单身份验证票证“登录”用户。

创建登录页的用户界面

让我们开始执行第一个任务。 将名为 Login.aspx 的新 ASP.NET 页添加到网站的根目录,并将其与 Site.master 母版页相关联。

添加新 ASP.NET 页,名为 Login.aspx

图 8:添加新 ASP.NET 页Login.aspx (单击以查看全尺寸图像)

典型的登录页界面包含两个文本框(一个用于用户名,一个用于用户密码)和一个用于提交表单的按钮。 网站通常包含“记住我”复选框,如果选中该复选框,则会在浏览器重启时保留生成的身份验证票证。

将两个 TextBox 添加到 Login.aspx,并分别将其 ID 属性设置为 UserName 和 Password。 此外,将 Password 的 TextMode 属性设置为 Password。 接下来,添加 CheckBox 控件,将其 ID 属性设置为 RememberMe,将其 Text 属性设置为“记住我”。 之后,添加一个名为 LoginButton 的 Button,其 Text 属性设置为“Login”。 最后,添加标签 Web 控件并将其 ID 属性设置为 InvalidCredentialsMessage,其 Text 属性设置为“用户名或密码无效。 请重试。“,其 ForeColor 属性为 Red,其 Visible 属性为 False。

此时,屏幕应类似于图 9 中的屏幕截图,并且页面的声明性语法应如下所示:

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
    <h1>
        Login</h1>
    <p>
        Username:
        <asp:TextBox ID="UserName" runat="server"></asp:TextBox></p>
    <p>
        Password:
        <asp:TextBox ID="Password" runat="server" TextMode="Password"></asp:TextBox></p>
    <p>
        <asp:CheckBox ID="RememberMe" runat="server" Text="Remember Me" /> </p>
    <p>
        <asp:Button ID="LoginButton" runat="server" Text="Login" OnClick="LoginButton_Click" /> </p>
    <p>
        <asp:Label ID="InvalidCredentialsMessage" runat="server" ForeColor="Red" Text="Your username or password is invalid. Please try again."
            Visible="False"></asp:Label> </p>
</asp:Content>

登录页包含两个文本框、一个 CheckBox、一个按钮和一个标签

图 9:登录页包含两个文本框、一个 CheckBox、一个按钮和一个标签 (单击以查看全尺寸图像)

最后,为 LoginButton 的 Click 事件创建事件处理程序。 在Designer中,只需双击 Button 控件即可创建此事件处理程序。

确定提供的凭据是否有效

我们现在需要在 Button 的 Click 事件处理程序中实现任务 2 - 确定提供的凭据是否有效。 为此,需要有一个保存所有用户凭据的用户存储区,以便我们可以确定提供的凭据是否与任何已知凭据匹配。

在 ASP.NET 2.0 之前,开发人员负责实现自己的用户存储,并编写代码以针对存储验证提供的凭据。 大多数开发人员会在数据库中实现用户存储,创建名为 Users 的表,其中包含 UserName、Password、Email、LastLoginDate 等列。 因此,此表将为每个用户帐户提供一条记录。 验证用户提供的凭据涉及在数据库中查询匹配的用户名,然后确保数据库中的密码与提供的密码相对应。

使用 ASP.NET 2.0,开发人员应使用成员资格提供程序之一来管理用户存储。 在本教程系列中,我们将使用 SqlMembershipProvider,它为用户存储使用 SQL Server 数据库。 使用 SqlMembershipProvider 时,需要实现特定的数据库架构,其中包括提供程序所需的表、视图和存储过程。 我们将在创建成员资格架构SQL Server教程中了解如何实现此架构。 使用成员资格提供程序后,验证用户凭据非常简单,只需调用 Membership 类ValidateUser (用户名密码) 方法,该方法将返回一个布尔值,指示用户名和密码组合的有效性。 由于尚未实现 SqlMembershipProvider 的用户存储,因此目前无法使用 Membership 类的 ValidateUser 方法。

与其花时间生成自己的自定义 Users 数据库表 (实现 SqlMembershipProvider) 后会过时,不如在登录页本身中对有效凭据进行硬编码。 在 LoginButton 的 Click 事件处理程序中,添加以下代码:

protected void LoginButton_Click(object sender, EventArgs e)
{
    // Three valid username/password pairs: Scott/password, Jisun/password, and Sam/password.
    string[] users = { "Scott", "Jisun", "Sam" };
    string[] passwords = { "password", "password", "password" };
    for (int i = 0; i < users.Length; i++)
    {
        bool validUsername = (string.Compare(UserName.Text, users[i], true) == 0);
        bool validPassword = (string.Compare(Password.Text, passwords[i], false) == 0);
        if (validUsername && validPassword)
        {
            // TODO: Log in the user...
            // TODO: Redirect them to the appropriate page
        }
    }
    // If we reach here, the user's credentials were invalid
    InvalidCredentialsMessage.Visible = true;
}

如你所看到的,有三个有效的用户帐户 - Scott、Jisun 和 Sam – 这三个帐户具有相同的密码 (“password”) 。 代码遍历用户和密码数组,查找有效的用户名和密码匹配项。 如果用户名和密码都有效,我们需要登录用户,然后将其重定向到相应的页面。 如果凭据无效,则显示 InvalidCredentialsMessage 标签。

当用户输入有效凭据时,我提到他们随后会重定向到“适当的页面”。不过,适当的页面是什么? 回想一下,当用户访问无权查看的页面时,FormsAuthenticationModule 会自动将他们重定向到登录页面。 这样做时,它通过 ReturnUrl 参数将请求的 URL 包含在 querystring 中。 也就是说,如果用户尝试访问ProtectedPage.aspx,但无权访问,则 FormsAuthenticationModule 会将他们重定向到:

Login.aspx?ReturnUrl=ProtectedPage.aspx

成功登录后,用户应重定向回ProtectedPage.aspx。 或者,用户可以自行访问登录页面。 在这种情况下,在用户中登录后,应将其发送到根文件夹的“Default.aspx”页。

登录用户

假设提供的凭据有效,我们需要创建表单身份验证票证,从而在用户中登录到站点。 System.Web.Security 命名空间中的 FormsAuthentication 类提供了各种方法,用于通过表单身份验证系统登录和注销用户。 虽然 FormsAuthentication 类中有几种方法,但我们在此时刻感兴趣的三种方法是:

  • GetAuthCookie (用户名persistCookie) – 为提供的名称 用户名创建表单身份验证票证。 接下来,此方法创建并返回一个包含身份验证票证内容的 HttpCookie 对象。 如果 persistCookie 为 true,则会创建永久性 Cookie。
  • SetAuthCookie (用户名persistCookie) – 调用 GetAuthCookie (用户名persistCookie) 方法来生成表单身份验证 Cookie。 然后,此方法会将 GetAuthCookie 返回的 Cookie 添加到 Cookie 集合, (假设正在使用基于 Cookie 的表单身份验证;否则,此方法调用处理无 Cookie 票证逻辑的内部类) 。
  • RedirectFromLoginPage (用户名persistCookie) – 此方法调用 SetAuthCookie (用户名persistCookie) ,然后将用户重定向到相应的页面。

在将 Cookie 写入 Cookie 集合之前需要修改身份验证票证时,GetAuthCookie 非常方便。 如果要创建表单身份验证票证并将其添加到 Cookies 集合,但不希望将用户重定向到相应的页面,SetAuthCookie 非常有用。 也许你想要将它们保留在登录页上,或者将它们发送到某个备用页面。

由于我们希望登录用户并将其重定向到相应的页面,因此请使用 RedirectFromLoginPage。 更新 LoginButton 的 Click 事件处理程序,将两个注释的 TODO 行替换为以下代码行:

FormsAuthentication.RedirectFromLoginPage (UserName.Text、RememberMe.Checked) ;

创建表单身份验证票证时,我们将 UserName TextBox 的 Text 属性用于表单身份验证票证 用户名 参数,并将 RememberMe CheckBox 的选中状态用于 persistCookie 参数。

若要测试登录页,请在浏览器中访问它。 首先输入无效凭据,例如用户名“Nope”和密码“错误”。 单击“登录”按钮时,将发生回发,并显示 InvalidCredentialsMessage 标签。

输入无效凭据时显示 InvalidCredentialsMessage 标签

图 10:输入无效凭据时显示 InvalidCredentialsMessage 标签 (单击以查看全尺寸图像)

接下来,输入有效凭据并单击“登录”按钮。 此时,当回发时,会创建表单身份验证票证,并自动重定向回Default.aspx。 此时,你已登录到网站,但没有视觉提示指示你当前已登录。 在步骤 4 中,我们将了解如何以编程方式确定用户是否已登录,以及如何标识访问页面的用户。

步骤 5 检查将用户从网站中注销的技术。

保护登录页

当用户输入凭据并提交登录页表单时,凭据(包括她的密码)通过 Internet 以 纯文本形式传输到 Web 服务器。 这意味着任何嗅探网络流量的黑客都可以看到用户名和密码。 若要防止出现这种情况,必须使用 安全套接字层 (SSL) 来加密网络流量。 这将确保凭据 (以及整个页面的 HTML 标记) 从它们离开浏览器的那一刻起加密,直到 Web 服务器收到它们。

除非您的网站包含敏感信息,否则只需在登录页面和其他页面上使用 SSL,否则用户密码将通过网络以纯文本形式发送。 无需担心表单身份验证票证的安全,因为默认情况下,表单身份验证票证经过加密和数字签名 (以防止篡改) 。 以下教程介绍了有关表单身份验证票证安全性的更全面讨论。

注意

许多金融和医疗网站配置为在经过身份验证 的用户可访问的所有 页面上使用 SSL。 如果要构建此类网站,则可以配置表单身份验证系统,以便表单身份验证票证仅通过安全连接传输。

步骤 4:检测经过身份验证的访问者并确定其身份

此时,我们已启用表单身份验证并创建了一个基本的登录页面,但尚未研究如何确定用户是经过身份验证还是匿名。 在某些情况下,我们可能需要显示不同的数据或信息,具体取决于经过身份验证的用户还是匿名用户访问页面。 此外,我们经常需要知道经过身份验证的用户的身份。

让我们扩充现有的Default.aspx页来说明这些技术。 在Default.aspx添加两个 Panel 控件,一个名为 AuthenticatedMessagePanel,另一个名为 AnonymousMessagePanel。 在第一个面板中添加名为 WelcomeBackMessage 的 Label 控件。 在第二个 Panel 中添加 HyperLink 控件,将其 Text 属性设置为“登录”,将其 NavigateUrl 属性设置为“~/Login.aspx”。 此时,Default.aspx的声明性标记应如下所示:

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
    <asp:Panel runat="server" ID="AuthenticatedMessagePanel">
        <asp:Label runat="server" ID="WelcomeBackMessage"></asp:Label>
    </asp:Panel>
    
    <asp:Panel runat="Server" ID="AnonymousMessagePanel">
        <asp:HyperLink runat="server" ID="lnkLogin" Text="Log In" NavigateUrl="~/Login.aspx"></asp:HyperLink>
    </asp:Panel>
</asp:Content>

正如你可能已经猜到的那样,此处的想法是仅向经过身份验证的访问者显示 AuthenticatedMessagePanel,向匿名访问者仅显示 AnonymousMessagePanel。 为此,我们需要根据用户是否登录来设置这些面板的 Visible 属性。

Request.IsAuthenticated 属性返回一个布尔值,该值指示是否已对请求进行身份验证。 在Page_Load事件处理程序代码中输入以下代码:

protected void Page_Load(object sender, EventArgs e)
{
    if (Request.IsAuthenticated)
    {
        WelcomeBackMessage.Text = "Welcome back!";
    
        AuthenticatedMessagePanel.Visible = true;
        AnonymousMessagePanel.Visible = false;
    }
    else
    {
        AuthenticatedMessagePanel.Visible = false;
        AnonymousMessagePanel.Visible = true;
    }
}

完成此代码后,通过浏览器访问 Default.aspx。 假设你尚未登录,你将看到一个指向登录页的链接, (请参阅图 11) 。 单击此链接并登录到站点。 正如我们在步骤 3 中看到的,输入凭据后,你将返回到Default.aspx,但这次页面显示“欢迎回来!”消息 (见图 12) 。

匿名访问时,会显示登录链接

图 11:匿名访问时,会显示登录链接

“经过身份验证的用户”显示

图 12:经过身份验证的用户显示为“欢迎回来!”消息

可以通过 HttpContext 对象的User 属性确定当前登录的用户标识。 HttpContext 对象表示有关当前请求的信息,是响应、请求和会话等常见 ASP.NET 对象的主页。 User 属性表示当前 HTTP 请求的安全上下文,并实现 IPrincipal 接口

User 属性由 FormsAuthenticationModule 设置。 具体而言,当 FormsAuthenticationModule 在传入请求中找到表单身份验证票证时,它会创建一个新的 GenericPrincipal 对象并将其分配给 User 属性。

泛型Principal 等 (主体对象) 提供有关用户标识及其所属角色的信息。 IPrincipal 接口定义两个成员:

我们可以使用以下代码确定当前访问者的名称:

string currentUsersName = User.Identity.Name;

使用表单身份验证时,会为 GenericPrincipal 的 Identity 属性创建 FormsIdentity 对象 。 FormsIdentity 类始终为其 AuthenticationType 属性返回字符串“Forms”,为其 IsAuthenticated 属性返回 true。 Name 属性返回创建表单身份验证票证时指定的用户名。 除了这三个属性,FormsIdentity 还包括通过其 Ticket 属性访问基础身份验证票证。 Ticket 属性返回 FormsAuthenticationTicket 类型的对象,该对象具有 Expiration、IsPersistent、IssueDate、Name 等属性。

此处要忽略的重要一点是,FormsAuthentication.GetAuthCookie 中指定的 用户名 参数 (用户名persistCookie) 、FormsAuthentication.SetAuthCookie (用户名persistCookie) 和 FormsAuthentication.RedirectFromLoginPage (用户名persistCookie) 方法与 User.Identity.Name 返回的值相同。 此外,可以通过将 User.Identity 强制转换为 FormsIdentity 对象,然后访问 Ticket 属性来使用这些方法创建的身份验证票证:

FormsIdentity ident = User.Identity as FormsIdentity;
FormsAuthenticationTicket authTicket = ident.Ticket;

让我们在 Default.aspx 中提供更个性化的消息。 更新Page_Load事件处理程序,以便为 WelcomeBackMessage 标签的 Text 属性分配字符串“Welcome back, username!”

WelcomeBackMessage.Text = “Welcome back, ” + User.Identity.Name + “!”;

图 13 显示了以用户 Scott) 身份登录时此修改 (的效果。

欢迎消息包括当前登录用户的名称

图 13:欢迎消息包含当前登录用户的名称

使用 LoginView 和 LoginName 控件

向经过身份验证的用户和匿名用户显示不同的内容是一项常见要求;显示当前登录用户的名称。 因此,ASP.NET 包含两个 Web 控件,这些控件提供如图 13 所示的相同功能,但无需编写一行代码。

LoginView 控件是基于模板的 Web 控件,它可以轻松地向经过身份验证的用户和匿名用户显示不同的数据。 LoginView 包括两个预定义的模板:

  • AnonymousTemplate – 添加到此模板的任何标记仅显示给匿名访问者。
  • LoggedInTemplate – 此模板的标记仅显示给经过身份验证的用户。

让我们将 LoginView 控件添加到网站的母版页 Site.master。 不过,与其只添加 LoginView 控件,不如添加一个新的 ContentPlaceHolder 控件,然后将 LoginView 控件放在该新的 ContentPlaceHolder 中。 这一决定的理由不久将变得明显。

注意

除了 AnonymousTemplate 和 LoggedInTemplate 之外,LoginView 控件还可以包括特定于角色的模板。 特定于角色的模板仅向属于指定角色的用户显示标记。 我们将在未来的教程中介绍 LoginView 控件的基于角色的功能。

首先,将名为 LoginContent 的 ContentPlaceHolder 添加到导航 <div> 元素内的母版页。 只需将 ContentPlaceHolder 控件从“工具箱”拖动到“源”视图中,将生成的标记置于“TODO:菜单将转到此处...”的正上方。文本。

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

接下来,在 LoginContent ContentPlaceHolder 中添加 LoginView 控件。 放置在母版页的 ContentPlaceHolder 控件中的内容被视为 ContentPlaceHolder 的默认内容 。 也就是说,使用此母版页 ASP.NET 页可以为每个 ContentPlaceHolder 指定其自己的内容,也可以使用母版页的默认内容。

LoginView 和其他与登录相关的控件位于工具箱的“登录名”选项卡中。

工具箱中的 LoginView 控件

图 14:工具箱中的 LoginView 控件

接下来,紧接在 LoginView 控件后面添加两 <个 br /> 元素,但仍在 ContentPlaceHolder 中。 此时,导航 <div> 元素的标记应如下所示:

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
        <asp:LoginView ID="LoginView1" runat="server">
        </asp:LoginView>
        <br /><br />
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

可以从Designer或声明性标记定义 LoginView 的模板。 在 Visual Studio 的Designer中,展开 LoginView 的智能标记,该标记在下拉列表中列出配置的模板。 在 AnonymousTemplate 中键入文本“你好,陌生人”;接下来,添加 HyperLink 控件,并将其 Text 和 NavigateUrl 属性分别设置为“登录”和“~/Login.aspx”。

配置 AnonymousTemplate 后,切换到 LoggedInTemplate 并输入文本“欢迎返回”。 然后将一个 LoginName 控件从“工具箱”拖到 LoggedInTemplate 中,将其紧跟在“欢迎返回”文本之后。 顾名思义, LoginName 控件显示当前登录用户的名称。 在内部,LoginName 控件仅输出 User.Identity.Name 属性

向 LoginView 的模板添加这些内容后,标记应如下所示:

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
        <asp:LoginView ID="LoginView1" runat="server">
            <LoggedInTemplate>
                Welcome back,
                <asp:LoginName ID="LoginName1" runat="server" />.
            </LoggedInTemplate>
            <AnonymousTemplate>
                Hello, stranger.
                <asp:HyperLink ID="lnkLogin" runat="server" NavigateUrl="~/Login.aspx">Log In</asp:HyperLink>
            </AnonymousTemplate>
        </asp:LoginView>
        
        <br /><br />
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

将此添加到 Site.master 母版页后,我们网站中的每个页面将显示不同的消息,具体取决于用户是否经过身份验证。 图 15 显示了用户 Jisun 通过浏览器访问时Default.aspx页面。 “欢迎回来,Jisun”消息重复两次:一次通过我们刚刚添加的 LoginView 控件在左侧 (母版页的导航部分中,一次通过面板控件和编程逻辑) ) (Default.aspx的内容区域中。

LoginView 控件显示

图 15:LoginView 控件显示“欢迎返回,Jisun”。

由于我们已将 LoginView 添加到母版页,因此它可以显示在我们网站上的每个页面中。 但是,可能存在不希望显示此消息的网页。 其中一个页面是登录页,因为指向登录页的链接似乎不合时宜。 由于我们在母版页的 ContentPlaceHolder 中放置了 LoginView 控件,因此可以在内容页中替代此默认标记。 打开Login.aspx并转到Designer。 由于我们没有在母版页中为 LoginContent ContentPlaceHolder 在 Login.aspx 中显式定义 Content 控件,因此登录页将显示此 ContentPlaceHolder 的母版页的默认标记。 可以通过Designer来查看这一点 – LoginContent ContentPlaceHolder 显示 loginView 控件) (默认标记。

登录页显示母版页的 LoginContent ContentPlaceHolder 的默认内容

图 16:登录页显示母版页的 LoginContent ContentPlaceHolder 的默认内容 (单击以查看全尺寸图像)

若要替代 LoginContent ContentPlaceHolder 的默认标记,只需右键单击Designer中的区域,然后从上下文菜单中选择“创建自定义内容”选项。 (使用 Visual Studio 2008 时,ContentPlaceHolder 包含一个智能标记,选中该标记时将提供相同的选项。) 这将向页面的标记添加新的 Content 控件,从而使我们能够定义此页面的自定义内容。 可以在此处添加自定义消息,例如“请登录...”,但让我们将其留空。

注意

在 Visual Studio 2005 中,创建自定义内容会在 ASP.NET 页中创建一个空的 Content 控件。 但是,在 Visual Studio 2008 中,创建自定义内容会将母版页的默认内容复制到新创建的 Content 控件中。 如果使用 Visual Studio 2008,则在创建新的 Content 控件后,请确保清除从母版页复制的内容。

图 17 显示了在进行此更改后从浏览器访问时Login.aspx页。 请注意,左侧导航 <div> 中没有“你好,陌生人”或“欢迎回来,用户名”消息,就像访问Default.aspx时一样。

登录页隐藏默认 LoginContent ContentPlaceHolder 的标记

图 17:登录页隐藏默认 LoginContent ContentPlaceHolder 的标记 (单击以查看全尺寸图像)

步骤 5:注销

在步骤 3 中,我们查看了如何生成登录页以将用户登录到站点,但我们尚未了解如何注销用户。除了用于将用户登录的方法外,FormsAuthentication 类还提供 SignOut 方法。 SignOut 方法只是销毁表单身份验证票证,从而将用户从站点中注销。

提供注销链接是一项常见功能,ASP.NET 包含专门用于注销用户的控件。 LoginStatus 控件 显示“登录名”LinkButton 或“注销”LinkButton,具体取决于用户的身份验证状态。 为匿名用户呈现“登录”LinkButton,而向经过身份验证的用户显示“注销”LinkButton。 可以通过 LoginStatus 的 LoginText 和 LogoutText 属性配置“Login”和“Logout”LinkButton 的文本。

单击“登录名”LinkButton 会导致回发,从中向登录页发出重定向。 单击“注销”LinkButton 会导致 LoginStatus 控件调用 FormsAuthentication.SignOff 方法,然后将用户重定向到页面。 注销用户重定向到的页面取决于 LogoutAction 属性,该属性可分配给以下三个值之一:

  • 刷新 – 默认值;将用户重定向到他们刚刚访问的页面。 如果他们刚刚访问的页面不允许匿名用户,则 FormsAuthenticationModule 将自动将用户重定向到登录页。

你可能会好奇为什么在此处执行重定向。 如果用户想要保留在同一页上,为什么需要显式重定向? 原因是单击“注销”LinkButton 时,用户在其 Cookie 集合中仍具有表单身份验证票证。 因此,回发请求是经过身份验证的请求。 LoginStatus 控件调用 SignOut 方法,但这种情况发生在 FormsAuthenticationModule 对用户进行身份验证之后。 因此,显式重定向会导致浏览器重新请求页面。 当浏览器重新请求页面时,表单身份验证票证已被删除,因此传入请求是匿名的。

  • 重定向 – 用户重定向到 LoginStatus 的 LogoutPageUrl 属性指定的 URL。
  • RedirectToLoginPage – 用户重定向到登录页。

让我们向母版页添加一个 LoginStatus 控件,并将其配置为使用“重定向”选项将用户发送到显示确认用户已注销的消息的页面。首先在根目录中创建一个名为 Logout.aspx 的页面。 不要忘记将此页面与 Site.master 母版页相关联。 接下来,在页面的标记中输入一条消息,向用户说明他们已注销。

接下来,返回到 Site.master 母版页,并在 LoginContent ContentPlaceHolder 的 LoginView 下面添加一个 LoginStatus 控件。 将 LoginStatus 控件的 LogoutAction 属性设置为 Redirect,将其 LogoutPageUrl 属性设置为“~/Logout.aspx”。

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
        <asp:LoginView ID="LoginView1" runat="server">
            <LoggedInTemplate>
                Welcome back,
                <asp:LoginName ID="LoginName1" runat="server" />.
            </LoggedInTemplate>
            <AnonymousTemplate>
                Hello, stranger.
                <asp:HyperLink ID="lnkLogin" runat="server" NavigateUrl="~/Login.aspx">Log In</asp:HyperLink>
            </AnonymousTemplate>
        </asp:LoginView>
        <br />
        <asp:LoginStatus ID="LoginStatus1" runat="server" LogoutAction="Redirect" LogoutPageUrl="~/Logout.aspx" />
        
        <br /><br />
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

由于 LoginStatus 在 LoginView 控件之外,因此它将同时为匿名用户和经过身份验证的用户显示,但没关系,因为 LoginStatus 将正确显示“登录”或“注销”LinkButton。 添加 LoginStatus 控件后,AnonymousTemplate 中的“登录”HyperLink 是多余的,因此请将其删除。

图 18 显示了 Jisun 访问时的Default.aspx。 请注意,左列显示消息“欢迎回来,Jisun”以及用于注销的链接。单击注销 LinkButton 会导致回发,将 Jisun 从系统注销,然后将她重定向到Logout.aspx。 如图 19 所示,当 Jisun 到达Logout.aspx她已经注销,因此是匿名的。 因此,左列显示文本“欢迎,陌生人”和登录页的链接。

Default.aspx放映

图 18:Default.aspx显示“欢迎返回,Jisun”以及“注销”LinkButton (单击以查看全尺寸图像)

Logout.aspx放映

图 19:Logout.aspx显示“欢迎,陌生人”以及“登录名”LinkButton (单击以查看全尺寸图像)

注意

建议自定义Logout.aspx页以隐藏母版页的 LoginContent ContentPlaceHolder (,就像我们在步骤 4) 中对Login.aspx所做的那样。 原因是 LoginStatus 控件呈现的“Login”LinkButton (“Hello, stranger”下的 LinkButton ) 将用户发送到在 ReturnUrl querystring 参数中传递当前 URL 的登录页。 简言之,如果已注销的用户单击此 LoginStatus 的“登录名”LinkButton,然后登录,他们将被重定向回Logout.aspx,这很容易使用户感到困惑。

总结

在本教程中,我们从检查表单身份验证工作流开始,然后转向在 ASP.NET 应用程序中实现表单身份验证。 Forms 身份验证由 FormsAuthenticationModule 提供支持,该模块有两个职责:根据表单身份验证票证识别用户,以及将未经授权的用户重定向到登录页。

.NET Framework的 FormsAuthentication 类包括用于创建、检查和删除表单身份验证票证的方法。 Request.IsAuthenticated 属性和 User 对象提供额外的编程支持,用于确定是否对请求进行身份验证以及有关用户标识的信息。 还有 LoginView、LoginStatus 和 LoginName Web 控件,这些控件为开发人员提供了一种快速、无代码的方式来执行许多与登录相关的常见任务。 我们将在将来的教程中更详细地研究这些和其他与登录相关的 Web 控件。

本教程简要概述了表单身份验证。 我们没有检查各种配置选项,没有查看无 Cookie 表单身份验证票证的工作原理,也没有探讨 ASP.NET 如何保护表单身份验证票证的内容。

编程快乐!

深入阅读

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

本教程中包含的主题视频培训

关于作者

斯科特·米切尔是七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。

特别感谢...

本教程系列由许多有用的审阅者审阅。 本教程的首席审阅者是本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者包括 Alicja Maziarz、John Suru 和 Teresa Murphy。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。