作者 :斯科特·米切尔
注释
自本文撰写以来,ASP.NET 成员身份提供程序已被 ASP.NET 标识取代。 强烈建议更新应用以使用 ASP.NET 标识 平台,而不是本文撰写时精选的成员资格提供程序。 ASP.NET 标识在 ASP.NET 成员身份系统中具有许多优势,包括:
- 性能更好
- 改进了可扩展性和可测试性
- 支持 OAuth、OpenID Connect 和双因素身份验证
- 基于声明的标识支持
- 与 ASP.Net Core 更好的互操作性
在本教程中,我们将从单纯的讨论转向实现;具体而言,我们将了解如何实现 Forms 身份验证。 我们在本教程中开始构建的 Web 应用程序将在后续教程中继续构建,因为我们从简单的表单身份验证转向成员身份和角色。
有关此主题的详细信息,请观看此视频: 在 ASP.NET 中使用基本表单身份验证。
介绍
在 前面的教程 中,我们讨论了 ASP.NET 提供的各种身份验证、授权和用户帐户选项。 在本教程中,我们将从单纯的讨论转向实现;具体而言,我们将了解如何实现 Forms 身份验证。 我们在本教程中开始构建的 Web 应用程序将在后续教程中继续构建,因为我们从简单的表单身份验证转向成员身份和角色。
本教程首先深入介绍表单身份验证工作流,这是我们在上一教程中涉及的主题。 接下来,我们将创建一个 ASP.NET 网站,通过该网站演示表单身份验证的概念。 接下来,我们将配置站点以使用表单身份验证,创建一个简单的登录页面,并了解如何在代码中确定用户是否经过身份验证,如果是,则确定他们登录时使用的用户名。
了解表单身份验证工作流、在 Web 应用程序中启用它以及创建登录和注销页面都是构建支持用户帐户并通过网页对用户进行身份验证的 ASP.NET 应用程序的重要步骤。 因此,由于这些教程彼此相辅相成,因此,我鼓励您在继续下一个教程之前完整完成本教程,即使您已经在过去的项目中具有配置表单身份验证的经验。
了解 Forms 身份验证工作流
当 ASP.NET 运行时处理对 ASP.NET 资源(如 ASP.NET 页或 ASP.NET Web 服务)的请求时,该请求在其生命周期内会引发许多事件。 有些事件在请求的开头和最后引发,有些事件在对请求进行身份验证和授权时引发,有些事件在出现未经处理的异常时引发,等等。 要查看事件的完整列表,请参阅 HttpApplication 对象的事件。
HTTP 模块 是托管类,其代码的执行是为了响应请求生命周期中的特定事件。 ASP.NET 附带了许多 HTTP 模块,这些模块在后台执行基本任务。 与我们的讨论特别相关的两个内置 HTTP 模块是:
-
FormsAuthenticationModule
– 通过检查表单身份验证票证对用户进行身份验证,该票证通常包含在用户的 Cookie 集合中。 如果不存在表单身份验证票证,则用户是匿名的。 -
UrlAuthorizationModule
– 确定当前用户是否有权访问请求的 URL。 此模块通过查阅应用程序配置文件中指定的授权规则来确定颁发机构。 ASP.NET 还包括FileAuthorizationModule
通过查阅请求的文件 ACL 来确定权限的 ACL。
FormsAuthenticationModule
在 (和 FileAuthorizationModule
) 执行之前UrlAuthorizationModule
尝试对用户进行身份验证。 如果发出请求的用户无权访问请求的资源,则授权模块将终止请求并返回 HTTP 401 Unauthorized 状态。 在 Windows 身份验证场景中,HTTP 401 状态会返回给浏览器。 此状态代码会导致浏览器通过模式对话框提示用户输入其凭据。 但是,使用表单身份验证时,HTTP 401 未授权状态永远不会发送到浏览器,因为 FormsAuthenticationModule 会检测到此状态并对其进行修改,以改为将用户重定向到登录页(通过 HTTP 302 重定向 状态)。
登录页面的责任是确定用户的凭据是否有效,如果有效,则创建表单身份验证票证并将用户重定向回他们尝试访问的页面。 身份验证票证包含在对网站页面的后续请求中, FormsAuthenticationModule
用于识别用户。
图 1:Forms 身份验证工作流
在页面访问中记住身份验证票证
登录后,必须在每个请求上将表单身份验证票证发送回 Web 服务器,以便用户在浏览网站时保持登录状态。 这通常是通过将身份验证票证放在用户的 cookie 集合中来实现的。 Cookie 是驻留在用户计算机上的小型文本文件,并在每个请求的 HTTP 标头中传输到创建 Cookie 的网站。 因此,创建表单身份验证票证并将其存储在浏览器的 Cookie 中后,对该网站的每次后续访问都会将身份验证票证与请求一起发送,从而识别用户。
Cookie 的一个方面是它们的过期时间,即浏览器丢弃 Cookie 的日期和时间。 当表单身份验证 Cookie 过期时,用户将无法再进行身份验证,因此将变为匿名。 当用户从公共终端访问时,他们很可能希望其身份验证票证在关闭浏览器时过期。 但是,当从家中访问时,同一用户可能希望在浏览器重新启动时记住身份验证票证,这样他们就不必在每次访问站点时都重新登录。 此决定通常由用户以登录页面上的 “Remember me” 复选框的形式做出。 在第 3 步中,我们将研究如何在登录页面中实现 “Remember me” 复选框。 以下教程详细介绍了身份验证票证超时设置。
注释
用于登录网站的用户代理可能不支持 cookie。 在这种情况下,ASP.NET 可以使用无 Cookie 表单身份验证票证。 在此模式下,身份验证票证被编码到 URL 中。 在下一个教程中,我们将了解何时使用无 Cookie 身份验证票证,以及如何创建和管理它们。
Forms 身份验证的范围
是 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 版本仅支持网站项目。 我将使用 Web Site Project 模型。 如果您使用的是非 Express 版本,并且希望改用 Web 应用程序项目模型 ,请随意执行此作,但请注意,您在屏幕上看到的内容与必须执行的步骤与这些教程中显示的屏幕截图和说明之间可能存在一些差异。
图 2:System-Based 网站创建新文件 (单击以查看全尺寸图像)
添加母版页
接下来,将新的母版页添加到名为 Site.master 的根目录中的网站。 母版页 使页面开发人员能够定义可应用于 ASP.NET 页面的站点范围模板。 母版页的主要优点是可以在单个位置定义网站的整体外观,从而可以轻松更新或调整网站的布局。
图 3:将名为 Site.master 的母版页添加到网站 (单击以查看全尺寸图像)
在母版页中在此处定义网站范围的页面布局。 可以使用“设计”视图并添加所需的任何布局或 Web 控件,也可以手动在源视图中手动添加标记。 我构建了母版页的布局,以模拟我在 ASP.NET 2.0 中处理数据 教程系列中使用的布局(参见图 4)。 母版页使用 级联样式表 进行定位,并使用文件Style.css中定义的 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),但母版页可能具有多个 ContentPlaceHolders。
输入上述标记后,切换到“设计”视图会显示母版页的布局。 使用此母版页的任何 ASP.NET 页面都将具有此统一布局,并能够指定 MainContent
区域的标记。
图 4:母版页,通过设计视图查看时(单击以查看全尺寸图像)
创建内容页面
此时,我们的网站中有一个Default.aspx页面,但它不使用我们刚刚创建的母版页。 虽然可以作网页的声明性标记以使用母版页,但如果该页不包含任何内容,则删除该页并将其重新添加到项目中,并指定要使用的母版页会更容易。 因此,请先从项目中删除Default.aspx。
接下来,右键单击 Solution Explorer 中的项目名称,然后选择添加名为 Default.aspx 的新 Web 表单。 这一次,选中“选择母版页”复选框,然后从列表中选择 Site.master 母版页。
图 5:添加新的 Default.aspx 页,选择选择母版页 (单击以查看全尺寸图像)
图 6:使用 Site.master 母版页
注释
如果使用的是 Web 应用程序项目模型,则“添加新项”对话框不包含“选择母版页”复选框。 相反,您需要添加“Web 内容表单”类型的项目。在选择 “Web Content Form” 选项并单击 Add 后,Visual Studio 将显示如图 6 所示的 Select a Master 对话框。
新Default.aspx页的声明性标记仅 @Page 包括一个指定母版页文件路径的指令和母版页的 MainContent ContentPlaceHolder 的内容控件。
<%@ 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:启用 Forms 身份验证
创建 ASP.NET 网站后,我们的下一个任务是启用表单身份验证。 应用程序的身份验证配置是通过 <authentication>
Web.config中的元素指定的。该<authentication>
元素包含一个名为 mode 的属性,该属性指定应用程序使用的身份验证模型。 此属性可以具有以下四个值之一:
- Windows – 如前面的教程所述,当应用程序使用 Windows 身份验证时,Web 服务器负责对访客进行身份验证,这通常通过基本、摘要或集成 Windows 身份验证完成。
- 表单 – 用户通过网页上的表单进行身份验证。
- Passport– 使用 Microsoft 的 Passport Network 对用户进行身份验证。
- None— 不使用身份验证模型;所有访客都是匿名的。
默认情况下,ASP.NET 应用程序使用 Windows 身份验证。 要将身份验证类型更改为 Forms authentication,我们需要将 <authentication>
元素的 mode 属性修改为 Forms。
如果您的项目尚未包含 Web.config 文件,请立即添加一个文件,方法是右键单击解决方案资源管理器中的项目名称,选择 Add New Item(添加新项),然后添加 Web 配置文件。
图 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>
child 元素。
第 3 步:构建登录页面
为了支持表单身份验证,我们的网站需要一个登录页面。 如“了解 Forms 身份验证工作流”部分所述,如果用户尝试访问他们无权查看的页面,则 FormsAuthenticationModule
会自动将用户重定向到登录页面。 还有 ASP.NET Web 控件,它们将向匿名用户显示指向登录页面的链接。 这就引出了一个问题,“登录页面的 URL 是什么?
默认情况下,表单身份验证系统要求将登录页命名为 Login.aspx 并放置在 Web 应用程序的根目录中。 如果要使用不同的登录页面 URL,可以通过在 Web.config中指定该 URL 来实现。我们将在后续教程中了解如何执行此作。
登录页面有三个职责:
- 提供允许访客输入其凭据的界面。
- 确定提交的凭证是否有效。
- 通过创建表单身份验证票证来“登录”用户。
创建登录页面的用户界面
让我们从第一个任务开始。 将一个新的 ASP.NET 页添加到名为 Login.aspx 的站点根目录,并将其与 Site.master 母版页关联。
图 8:添加名为 Login.aspx 的新 ASP.NET 页 (单击以查看全尺寸图像)
典型的登录页面界面由两个文本框组成 - 一个用于用户名,一个用于其密码 - 和一个用于提交表单的按钮。 网站通常包含一个“记住我”复选框,如果选中该复选框,则在浏览器重新启动后仍保留生成的身份验证票证。
添加两个 TextBox to Login.aspx 并将其 ID
属性设置为 UserName 和 Password。 此外,将 Password 的属性设置为 TextMode
Password。 接下来,添加一个 CheckBox 控件,将其 ID
属性设置为 RememberMe,并将其 Text
属性设置为“Remember Me”。 然后,添加一个名为 LoginButton 的 Button,其 Text
属性设置为 “Login”。 最后,添加一个 Label Web 控件,并将其 ID
属性设置为 InvalidCredentialsMessage,将其 Text
属性设置为“您的用户名或密码无效。 Please try again.“,将其 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>
图 9:登录页包含两个 TextBox、一个 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 提供程序,验证用户的凭据就像调用 Membership 类的 ValidateUser(username, password) 方法一样简单,该方法返回一个布尔值,该值指示用户名和密码组合的有效性。 由于我们还没有实现 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”)。 该代码遍历 users 和 passwords 数组,查找有效的用户名和密码匹配项。 如果用户名和密码都有效,我们需要登录用户,然后将他们重定向到相应的页面。 如果凭证无效,则显示 InvalidCredentialsMessage 标签。
当用户输入有效凭证时,我提到他们随后会被重定向到 “适当的页面”。不过,什么是合适的页面呢? 回想一下,当用户访问他们无权查看的页面时,FormsAuthenticationModule 会自动将其重定向到登录页面。 在此过程中,它会通过 ReturnUrl 参数在查询字符串中包含请求的 URL。 也就是说,如果用户尝试访问 ProtectedPage.aspx,但他们没有获得授权,则 FormsAuthenticationModule 会将他们重定向到:
Login.aspx?ReturnUrl=ProtectedPage.aspx
成功登录后,用户应重定向回 ProtectedPage.aspx。 或者,用户可以自行访问登录页面。 在这种情况下,登录用户后,应将他们发送到根文件夹的 Default.aspx 页。
登录用户
假设提供的凭据有效,我们需要创建表单身份验证票证,从而将用户登录到网站。 System.Web.Security 命名空间中的 FormsAuthentication 类提供了通过表单身份验证系统登录和注销用户的各种方法。 虽然 FormsAuthentication 类中有多种方法,但目前我们感兴趣的三种方法是:
- GetAuthCookie(username, persistCookie) – 为提供的名称 username 创建表单身份验证票证。 接下来,此方法创建并返回一个 HttpCookie 对象,该对象包含身份验证票证的内容。 如果 persistCookie 为 true,则创建持久性 Cookie。
- SetAuthCookie(username, persistCookie) – 调用 GetAuthCookie(username, persistCookie) 方法以生成表单身份验证 Cookie。 然后,此方法将 GetAuthCookie 返回的 Cookie 添加到 Cookies 集合中(假设正在使用基于 Cookie 的表单身份验证;否则,此方法将调用处理无 Cookie 票证逻辑的内部类)。
- RedirectFromLoginPage(username, persistCookie) – 此方法调用 SetAuthCookie(username, persistCookie),然后将用户重定向到相应的页面。
当您需要在将 Cookie 写入 Cookie 集合之前修改身份验证票证时,GetAuthCookie 非常方便。 如果要创建表单身份验证票证并将其添加到 Cookies 集合,但不想将用户重定向到相应的页面,则 SetAuthCookie 非常有用。 也许你想把它们保留在登录页面上,或者把它们发送到某个备用页面。
由于我们想要登录用户并将其重定向到相应的页面,因此让我们使用 RedirectFromLoginPage。 更新 LoginButton 的 Click 事件处理程序,将两行带注释的 TODO 替换为以下代码行:
FormsAuthentication.RedirectFromLoginPage(用户名.文本,RememberMe.Checked);
创建表单身份验证票证时,我们将 UserName TextBox 的 Text 属性用于表单身份验证票证 用户名 参数,并将 RememberMe CheckBox 的选中状态用于 persistCookie 参数。
要测试登录页面,请在浏览器中访问它。 首先输入无效的凭据,例如用户名 “Nope” 和密码 “wrong”。 单击 Login 按钮后,将发生回发,并显示 InvalidCredentialsMessage 标签。
图 10:输入无效凭据时显示 InvalidCredentialsMessage 标签 (单击以查看全尺寸图像)
接下来,输入有效的凭据,然后单击 Login 按钮。 这一次,当回发发生时,将创建表单身份验证票证,并自动将您重定向回 Default.aspx。 此时您已经登录到网站,尽管没有视觉提示表明您当前已登录。 在第 4 步中,我们将了解如何以编程方式确定用户是否已登录,以及如何识别访问该页面的用户。
步骤 5 检查将用户从网站注销的技术。
保护登录页面
当用户输入其凭证并提交登录页面表单时,凭证(包括她的密码)将通过 Internet 以 纯文本形式传输到 Web 服务器。 这意味着任何嗅探网络流量的黑客都可以看到用户名和密码。 为防止这种情况,必须使用 安全套接字层 (SSL) 加密网络流量。 这将确保凭据(以及整个页面的 HTML 标记)从离开浏览器的那一刻起就被加密,直到 Web 服务器收到它们。
除非您的网站包含敏感信息,否则您只需在登录页面和其他页面上使用 SSL,否则用户的密码将通过网络以纯文本形式发送。 您无需担心表单身份验证票证的安全问题,因为默认情况下,它既经过加密又经过数字签名(以防止篡改)。 以下教程中对表单身份验证票证安全性进行了更深入的讨论。
注释
许多金融和医疗网站都配置为在经过身份验证的用户可访问 的所有 页面上使用 SSL。 如果要构建此类网站,则可以配置表单身份验证系统,以便仅通过安全连接传输表单身份验证票证。
第 4 步:检测经过身份验证的访客并确定其身份
此时,我们已经启用了表单身份验证并创建了一个基本的登录页面,但我们尚未研究如何确定用户是经过身份验证的还是匿名的。 在某些情况下,我们可能希望显示不同的数据或信息,具体取决于经过身份验证的用户或匿名用户是否正在访问该页面。 此外,我们经常需要知道经过身份验证的用户的身份。
让我们扩充现有的 Default.aspx 页面来说明这些技术。 在 Default.aspx 中添加两个 Panel 控件,一个名为 AuthenticatedMessagePanel,另一个名为 AnonymousMessagePanel。 在第一个 Panel 中添加名为 WelcomeBackMessage 的 Label 控件。 在第二个面板中,添加一个 HyperLink 控件,将其 Text 属性设置为“Log In”,将其 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,但这次页面显示 “Welcome back!” 消息(参见图 12)。
图 11:匿名访问时,会显示一个 Log In 链接
图 12:经过身份验证的用户显示“欢迎回来!消息
我们可以通过 HttpContext 对象的User 属性来确定当前登录用户的身份。 HttpContext 对象表示有关当前请求的信息,并且是 Response、Request 和 Session 等常见 ASP.NET 对象的主。 User 属性表示当前 HTTP 请求的安全上下文,并实现 IPrincipal 接口。
User 属性由 FormsAuthenticationModule 设置。 具体而言,当 FormsAuthenticationModule 在传入请求中找到表单身份验证票证时,它会创建一个新的 GenericPrincipal 对象并将其分配给 User 属性。
Principal 对象(如 GenericPrincipal)提供有关用户身份及其所属角色的信息。 IPrincipal 接口定义了两个成员:
- IsInRole(roleName) – 返回布尔值的方法,该值指示委托人是否属于指定角色。
- Identity – 一个属性,它返回实现 IIdentity 接口的对象。 IIdentity 接口定义三个属性: AuthenticationType、 IsAuthenticated 和 Name。
我们可以使用以下代码确定当前访客的姓名:
字符串 currentUsersName = User.Identity.Name;
使用表单身份验证时,将为 GenericPrincipal 的 Identity 属性创建一个 FormsIdentity 对象 。 FormsIdentity 类的 AuthenticationType 属性始终返回字符串“Forms”,而 IsAuthenticated 属性则返回 true。 Name 属性返回在创建表单身份验证票证时指定的用户名。 除了这三个属性之外,FormsIdentity 还包括通过其 Ticket 属性访问基础身份验证票证。 Ticket 属性返回 FormsAuthenticationTicket 类型的对象,该对象具有 Expiration、IsPersistent、IssueDate、Name 等属性。
此处要指出的要点是,在 FormsAuthentication.GetAuthCookie(username, persistCookie)、FormsAuthentication.SetAuthCookie(username, persistCookie) 和 FormsAuthentication.RedirectFromLoginPage(username, persistCookie) 方法中指定的 username 参数与 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 = “欢迎回来,” + 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: Menu will go here...”的正上方。发短信。
<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 和其他与登录相关的控件位于 Toolbox 的 Login 选项卡中。
图 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 的设计器中,展开 LoginView 的智能标记,该标记在下拉列表中列出了配置的模板。 在 AnonymousTemplate 中输入文本 “Hello, stranger”;接下来,添加一个 HyperLink 控件,并将其 Text 和 NavigateUrl 属性分别设置为“Log In”和“~/Login.aspx”。
配置 AnonymousTemplate 后,切换到 LoggedInTemplate 并输入文本 “Welcome back, ”。 然后将 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的内容区域中(通过 Panel 控件和编程逻辑)。
图 15:LoginView 控件显示“欢迎回来,Jisun”。
由于我们将 LoginView 添加到母版页,因此它可以显示在我们网站的每个页面中。 但是,我们可能不希望在某些网页上显示此消息。 其中一个页面是登录页面,因为指向登录页面的链接似乎不合适。 由于我们将 LoginView 控件放置在母版页的 ContentPlaceHolder 中,因此我们可以在内容页中重写此默认标记。 打开 Login.aspx 并转到 Designer。 由于我们尚未在母版页中为 LoginContent ContentPlaceHolder 在 Login.aspx 中显式定义内容控件,因此登录页将显示此 ContentPlaceHolder 的母版页的默认标记。 您可以通过设计器查看这一点 – LoginContent ContentPlaceHolder 显示默认标记(LoginView 控件)。
图 16:登录页显示母版页的 LoginContent ContentPlaceHolder 的默认内容 (单击以查看全尺寸图像)
要覆盖 LoginContent ContentPlaceHolder 的默认标记,只需右键单击 Designer 中的区域,然后从上下文菜单中选择 Create Custom Content 选项即可。 (使用 Visual Studio 2008 时,ContentPlaceHolder 包括一个智能标记,选中该标记后,将提供相同的选项。这会向页面的标记添加新的 Content 控件,从而允许我们为此页面定义自定义内容。 您可以在此处添加自定义消息,例如 “Please log in...”,但让我们将其留空。
注释
在 Visual Studio 2005 中,创建自定义内容会在 ASP.NET 页中创建一个空的 Content 控件。 但是,在 Visual Studio 2008 中,创建自定义内容会将母版页的默认内容复制到新创建的 Content 控件中。 如果您使用的是 Visual Studio 2008,则在创建新的 Content 控件后,请确保清除从母版页复制的内容。
图 17 显示了在进行此更改后从浏览器访问时Login.aspx页面。 请注意,左侧导航 <div> 中没有“Hello, stranger”或“Welcome back, username”消息,就像访问 Default.aspx 时那样。
图 17:登录页隐藏默认 LoginContent ContentPlaceHolder 的标记 (单击以查看全尺寸图像)
第 5 步:注销
在第 3 步中,我们研究了如何构建一个登录页面来使用户登录到网站,但我们还没有看到如何让用户注销。除了用于登录用户的方法外,FormsAuthentication 类还提供 SignOut 方法。 SignOut 方法只是销毁表单身份验证票证,从而将用户从网站中注销。
提供注销链接是一项常见的功能,ASP.NET 包括一个专门用于注销用户的控件。 LoginStatus 控件 显示“Login”LinkButton 或“Logout”LinkButton,具体取决于用户的身份验证状态。 “登录”LinkButton 是为匿名用户呈现的,而“注销”LinkButton 则向经过身份验证的用户显示。 “Login” 和 “Logout” LinkButton 的文本可以通过 LoginStatus 的 LoginText 和 LogoutText 属性进行配置。
单击 “Login” LinkButton 会导致回发,从中向登录页面发出重定向。 单击“Logout”LinkButton 会导致 LoginStatus 控件调用 FormsAuthentication.SignOff 方法,然后将用户重定向到页面。 已注销用户重定向到的页面取决于 LogoutAction 属性,该属性可以分配给以下三个值之一:
- Refresh ― 默认值;将用户重定向到他们刚刚访问的页面。 如果他们刚刚访问的页面不允许匿名用户,则 FormsAuthenticationModule 会自动将用户重定向到登录页面。
您可能很好奇为什么在这里执行重定向。 如果用户希望保持在同一页面上,为什么需要显式重定向? 这是因为当单击“注销”LinkButton 时,用户的 cookie 集合中仍具有表单身份验证票证。 因此,回发请求是经过身份验证的请求。 LoginStatus 控件调用 SignOut 方法,但这发生在 FormsAuthenticationModule 对用户进行身份验证之后。 因此,显式重定向会导致浏览器重新请求该页面。 当浏览器重新请求页面时,表单身份验证票证已被删除,因此传入请求是匿名的。
- Redirect – 用户被重定向到 LoginStatus 的 LogoutPageUrl 属性指定的 URL。
- RedirectToLoginPage – 用户被重定向到登录页面。
让我们将 LoginStatus 控件添加到母版页,并将其配置为使用 Redirect 选项将用户发送到一个页面,该页面显示一条消息,确认他们已注销。首先,在根目录中创建一个名为 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 中的 “Log In” HyperLink 是多余的,因此请将其删除。
图 18 显示了 Jisun 访问时Default.aspx。 请注意,左列显示消息“欢迎回来,Jisun”以及注销链接。单击注销 LinkButton 会导致回发,将 Jisun 从系统中注销,然后将她重定向到 Logout.aspx。 如图 19 所示,当 Jisun 到达 Logout.aspx 时,她已经注销,因此是匿名的。 因此,左列显示文本 “Welcome, stranger” 和指向登录页面的链接。
图 18:Default.aspx显示“欢迎回来,Jisun”以及“注销”LinkButton (单击以查看全尺寸图像)
图 19:Logout.aspx显示“欢迎,陌生人”以及“登录”LinkButton (单击以查看全尺寸图像)
注释
我鼓励您自定义 Logout.aspx 页以隐藏母版页的 LoginContent ContentPlaceHolder(就像我们在步骤 4 中对 Login.aspx 所做的那样)。 这是因为 LoginStatus 控件呈现的“Login”LinkButton(“Hello, stranger”下面的控件)将用户发送到登录页,并在 ReturnUrl 查询字符串参数中传递当前 URL。 简而言之,如果一个已经注销的用户点击了这个 LoginStatus 的 “Login” LinkButton,然后登录,他们将被重定向回 Logout.aspx,这很容易使用户感到困惑。
概要
在本教程中,我们首先检查了表单身份验证工作流,然后转向在 ASP.NET 应用程序中实现表单身份验证。 表单身份验证由 FormsAuthenticationModule 提供支持,该模块有两个职责:根据用户的表单身份验证票证识别用户,以及将未经授权的用户重定向到登录页。
.NET Framework 的 FormsAuthentication 类包括用于创建、检查和删除表单身份验证票证的方法。 Request.IsAuthenticated 属性和 User 对象提供额外的编程支持,用于确定请求是否经过身份验证以及有关用户身份的信息。 还有 LoginView、LoginStatus 和 LoginName Web 控件,它们为开发人员提供了一种快速、无代码的方式来执行许多常见的与登录相关的任务。 在将来的教程中,我们将更详细地介绍这些控件和其他与登录相关的 Web 控件。
本教程提供了表单身份验证的粗略概述。 我们没有检查各种配置选项,没有查看无 Cookie 表单身份验证票证的工作原理,也没有探索 ASP.NET 如何保护表单身份验证票证的内容。
快乐编程!
深入阅读
有关本教程中讨论的主题的详细信息,请参阅以下资源:
- IIS6 和 IIS7 安全性之间的更改
- 登录 ASP.NET 控件
- 专业 ASP.NET 2.0 安全、成员资格和角色管理 (ISBN:978-0-7645-9698-8)
-
元素
<authentication>
-
元素
<forms>
<authentication>
本教程中包含的主题视频培训
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。
特别感谢...
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者包括 Alicja Maziarz、John Suru 和 Teresa Murphy。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com