原文:ASP.NET 成员资格 Part.2(使用安全控件 Login)
准备好提供程序以及用户信息的存储,就可以开始构建验证用户、注册用户或者让用户能够重置密码的用户界面了。ASP.NET 提供了一些控件,用于简化创建登录页面以及其他相关页面的过程。
这些安全控件依赖于底层的表单验证和成员资格 API 框架。下表简单描述了这些控件并总结了典型的应用场景。不过,这只是推荐用法,你可以在 Web 应用程序的任意 ASP.NET 页面里使用这些控件。
ASP.NET 安全控件
Login | 这是一个复合控件,解决了基于表单验证的最常见的任务:登录。它会自动使用默认的成员资格提供程序来验证用户 |
LoginStatus | 用来确认当前会话的验证状态。如果用户没有验证,会提供重定向。如果用户已验证,会显示一个退出按钮。此控件封装了在所有页面上都可用的行为,因此较适合放置在母板页 |
LoginView | 允许你为已验证和未验证的用户展示不同的界面,或为不同角色的用户显示不同的控件 |
PasswordRecovery | 允许用户通过注册的 Email,并在正确回答了密码问题后重新取回密码 |
ChangePassword | 用户提供旧密码,可输入新密码进行密码的更改 |
CreateUserWizard | 是引导用户完成创建过程的完整向导 |
所有的这些控件都和成员资格 API 一同工作。一旦你捕获这些控件提供的事件,那你就要负责完成这个任务。例如,Login 支持 Authenticate 事件,你不捕获事件,它自动使用成员资格 API。你捕获了这个事件,你必需自己负责验证用户凭证。
Login 控件
Login 控件提供一个现成的界面,接收用户输入的用户名和密码,并提供登录按钮。幕后,它封装了通过成员资格 API 验证用户身份并封装基本的表单验证功能。换句话说,它封装了 Membership.ValidateUser() 或者 FormsAuthentication.RedirectFromLoginPage() 之类的功能,你不必再编写这类代码。
无论用户何时单击“登录”按钮,它都会调用 Membership.ValidateUser() 验证用户名和密码;验证成功它会调用 FormsAuthentication.RedirectFromLoginPage();如果选中“下次记住我”,它会把 RedirectFromLoginPage 方法中的 createPersistentCookie 参数的值设为 true 从而创建一个持久 cookie。
实际上,Login 只是一个 ASP.NET 复合控件,它完全可以扩展,你可以覆盖任何的布局样式和属性,还可以自己捕获控件抛出的事件来覆盖它的默认行为。最简单的的 Login 形式如下:
<form id="form1" runat="server">
<div>
<asp:Login ID="Login1" runat="server">
</asp:Login>
</div>
</form>
可以使用几个属性来修改控件的外观。你可以使用由 Login 控件提供的不同样式(自动套用格式)设置:
<form id="form1" runat="server">
<div>
<asp:Login ID="Login1" runat="server" BackColor="#F7F7DE" BorderColor="#CCCC99"
BorderStyle="Solid" BorderWidth="1px" Font-Names="Verdana" Font-Size="10pt">
<TitleTextStyle BackColor="#6B696B" Font-Bold="True" ForeColor="#FFFFFF" />
</asp:Login>
</div>
</form>
还可以使用 CSS 类来定制 Login 控件的外观,Login 控件提供的每一个样式属性都包含一个 CssClass 属性。假设你在项目中添加了一个名为 MyStyles.css 的样式表文件:
.MyLoginTextBoxStyle
{
cursor: crosshair;
background-color: Yellow;
text-align: center;
border-left-color: Black;
border-bottom-color: Black;
border-top-style: dotted;
border-top-color: Black;
border-right-style: dotted;
border-left-style: dotted;
border-right-color: Black;
border-bottom-style: dotted;
font-family: Verdana;
vertical-align: middle;
}
你可以如下使用 Login 控件:
<head runat="server">
<title>Your Login Page</title>
<link href="." rel="Stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div style="text-align: center">
<asp:Login ID="Login1" runat="server" BackColor="AliceBlue" BorderColor="Black" BorderStyle="Double">
<TextBoxStyle CssClass="" />
<TitleTextStyle Font-Italic="true" Font-Bold="true" Font-Names="Verdana" />
</asp:Login>
</div>
</form>
</body>
注意:如果 CSS 文件位于禁止匿名用户访问的目录中,这些样式对 Login 控件不会起作用,因为 CSS 文件被 ASP.NET 运行时所保护(因为它的扩展名被映射到了 ASP.NET)。如果你想让 Login 控件使用 CSS 文件(用户肯定是匿名用户),你或者要将 CSS 放置在匿名用户可以访问的目录下,或者在 web.config 文件中为 CSS 文件添加下面的配置:
<location path="MyStyles.css">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
一般,我比较喜欢将公共可访问的资源放在一个单独的文件夹中,并限制对 Web 应用程序的其他任何目录的访问。
Login 样式篇
Login 控件提供的每一个样式都以相同的方式工作。你可以直接设置颜色字体,也可以使用 CssClass 属性设定一个 CSS 类。
Login 控件提供的样式
CheckBoxStyle | “记住此次登录”复选框的样式 |
FailureTextStyle | 登录失败时所显示文本的样式 |
HyperLinkStyle | 若干种类型的超链接的外观 |
InstructionTextStyle | Login 控件中显示的帮助文本的外观 |
LabelStyle | UserName 和 Password 标签的样式 |
LoginButtonStyle | “登录”按钮的外观 |
TextBoxStyle | UserName 和 Password 文本框的样式 |
TitleTextStyle | 标题的样式 |
ValidatorTextStyle | 检验用户名和密码的验证控件的样式 |
如果想在 Login 控件中包含一些额外的链接,可以按照下面的方式修改控件:
<asp:Login ID="Login1" runat="server" BackColor="AliceBlue" BorderColor="Black" BorderStyle="Double"
CreateUserText="Register" CreateUserUrl="Register.aspx" HelpPageText="Additional Help"
HelpPageUrl="HelpMe.htm" InstructionText="Please enter your user name and password.">
<TextBoxStyle CssClass="MyLoginTextBoxStyle" />
<TitleTextStyle Font-Italic="true" Font-Bold="true" Font-Names="Verdana" />
</asp:Login>
Login 控件的相关定制属性
TitleText | 控件标题的文本 |
InstructionText | 显示在 Login 标题下面的文本 |
FailureText | 登录失败时提示的文本 |
UserNameLabelText | 用户名文本框前面显示的文本 |
PasswordLabelText | 密码文本框前显示的文本 |
UserName | 用户名文本框的初始值 |
UsernameRequiredErrorMessage | 用户没有输入用户名时显示的错误信息 |
PasswordRequiredErrorMessage | 用户没有输入密码时显示的错误信息 |
LoginButtonText | 登录按钮的显示文本 |
LoginButtonType | 登录按钮可以显示为链接、按钮、图片,可选值:Link、Button、Image |
LoginButtonImageUrl | 如果设置为图片,需提供图片的 Url |
DestinationPageUrl | 登录成功后重定向的页面地址,默认为空。默认时,它使用表单验证架构重定向到原先请求页面,或者定向到表单验证所配置的 defaultUrl |
DisplayRememberMe | 是否显示“记住此次登录”复选框 |
FailureAction | 登录失败后执行的动作。Refresh:刷新当前页面;RedirectToLoginPage:重回登录页面,适用于你的登录控件并不在登录页面 |
RememberMeSet | 设置“记住此次登录”复选框的值,默认 false |
VisibleWhenLoggedIn | 如果设为 false,若用户已登录,此控件自动隐藏。如果设为 true(默认),Login 控件会一直显示,即使用户已登录 |
CreateUserUrl | 定义一个去创建用户页面的 URL |
CreateUserText | 定义为 CreateUserUrl 显示的文本 |
CreateUserIconUrl | 定义了一个图标的 URL ,和上一文本一起显示 |
HelpPageUrl | 定义帮助页面 URL |
HelpPageText | 上一地址的显示文本 |
HelpPageIconUrl | 定义了一个图标的 URL ,和上一文本一起显示 |
PasswordRecoveryUrl | 重定向用户到恢复密码页面 |
PasswordRecoveryText | 上一 URL 显示的文本 |
PasswordRecoveryIconUrl | 定义了一个图标的 URL ,和上一文本一起显示 |
Login 模板篇
Login 控件几乎完全可以通过这些属性定制,但你也可能看到了,几乎没有办法定义用于验证客户输入的验证表达式。当然,你可以在服务器端 Login 控件提供的事件函数里执行检验。但通常想为 Login 控件添加任何控件,就不能再使用前面介绍的属性了。比如,你有一个额外带有验证码或者某些政府页面上所使用的用户访问密钥的强验证文本框,那该如何呢?
Login 控件支持模板,你可以不受任何限制的自定义 Login 控件的内容。
<asp:Login ID="LoginCtrl" runat="server" BackColor="aliceblue" BorderColor="Black"
BorderStyle="double" OnLoginError="LoginCtrl_LoginError" PasswordRecoveryUrl="~/pwdrecover.aspx"
OnAuthenticate="LoginCtrl_Authenticate" OnLoggingIn="LoginCtrl_LoggingIn">
<LayoutTemplate>
<h4>
Log-In to the System</h4>
<table>
<tr>
<td>
Username:
</td>
<td>
<asp:TextBox ID="UserName" runat="server" />
<asp:RequiredFieldValidator ID="UserNameRequired" runat="server" ControlToValidate="UserName"
ErrorMessage="*" ValidationGroup="Login1" />
<asp:RegularExpressionValidator ID="UsernameValidator" runat="server" ControlToValidate="UserName"
ValidationExpression="[\w| ]*" ErrorMessage="Invalid Username" ValidationGroup="Login1" />
</td>
</tr>
<tr>
<td>
Password:
</td>
<td>
<asp:TextBox ID="Password" runat="server" TextMode="Password" />
<asp:RequiredFieldValidator ID="PasswordRequired" runat="server" ControlToValidate="Password"
ErrorMessage="*" ValidationGroup="Login1" />
<asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server" ControlToValidate="Password"
ValidationExpression='[\w| !"@§$%&/()=\-?\*]*' ErrorMessage="Invalid Password"
ValidationGroup="Login1" />
</td>
</tr>
</table>
<asp:CheckBox ID="RememberMe" runat="server" Text="Remember Me" /><br />
<asp:Literal ID="FailureText" runat="server" /><br />
<asp:Button ID="Login" CommandName="Login" runat="server" Text="Login" ValidationGroup="Login1" />
</LayoutTemplate>
</asp:Login>
但这会引发一个问题:必需编写如此之多的 UI 代码,那么为什么不编写一个不使用 Login 控件的自定义登录页面呢?
不过,正如本文开始所解释的,UI 只是 Login 控件的一部分。实际上,无论用户何时单击“登录”,Login 控件都包含了自动通过成员资格 API 存储验证用户并通过表单验证框架把用户重定向到原始请求页面的全部代码。因此,它还是节省了编写这段代码的时间。
Login 模板的特殊规定
控件 ID |
控件类型 |
必需 |
UserName | TextBox | 是 |
Password | TextBox | 是 |
RememberMe | CheckBox | 否 |
FailureText | Literal | 否 |
Login | 支持事件冒泡和某个 CommandName 的任意控件 | 否 |
ID 为 Login 的控件可以是任何支持事件冒泡和 CommandName 属性的控件。为“登录”按钮设置 CommandName 属性是非常重要的。不这样做,Login 在处理事件的过程中就无法识别它。如果你没有设置 CommandName,就必需自己处理事件,编写代码检验用户名和密码、将用户跳转回原始页面。
使用 Login 的模板 LayoutTemplate 时,原先 Login 控件提供的许多属性就不起作用了,这点要注意。
Login 编程篇
Login 控件提供了一系列的事件和属性,你可以用它们定制控件的行为。这样,你就可以完全定制 Login 控件(模板、自定义样式、自定义方法)。
Login 控件支持的事件
LoggingIn | 在用户被控件验证之前触发 |
LoggedIn | 在用户被控件验证之后触发 |
LoginError | 登录失败时触发 |
Authenticate | 触发此事件来验证用户。如果你捕获了这个事件,就必需自己验证客户 |
比如,你可以在用户尝试制定次数之后使用 LoginError 事件自动将用户转到密码恢复页面:
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
ViewState["LoginErrors"] = 0;
}
}
protected void Login1_LoginError(object sender, EventArgs e)
{
if (ViewState["LoginErrors"] == null)
{
ViewState["LoginErrors"] = 0;
}
else
{
int errorCount = (int)ViewState["LoginErrors"] + 1;
ViewState["LoginErrors"] = errorCount;
if (errorCount > 3 && Login1.PasswordRecoveryUrl != string.Empty)
{
Response.Redirect(Login1.PasswordRecoveryUrl);
}
}
}
正如上面所述,如果你自己捕获事件,就必须自己添加检验用户名和密码的代码。Authenticate 事件接收 AuthenticateEventArgs 的实例作为参数。这个事件参数类支持 Authenticated 属性。如果将这个属性设为 true,Login 控件就认为验证成功,并触发 LoggedIn 事件;如果设为 false,则显示 FailureText,并触发 LoginError 事件。
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
if (YourValidationFunction(Login1.UserName, Login1.Password))
{
e.Authenticated = true;
}
else
{
e.Authenticated = false;
}
}
可以直接通过 UserName 和 Password 属性访问用户在文本框里输入的值。但如果正在使用模板控件,并且除了 ID 为 UserName 和 Password 的控件之外,当你需要访问其他控件的值,需要使用 FindControl 方法获得这个控件并转换成适当类型才能读取值。
<asp:Login ID="Login1" runat="server" OnLoginError="Login1_LoginError" PasswordRecoveryUrl="PasswordRecovery.aspx"
OnAuthenticate="Login1_Authenticate">
<LayoutTemplate>
<div style="font-family:Courier New">
User Name:<asp:TextBox runat="server" ID="UserName" /><br />
Password: <asp:TextBox runat="server" ID="Password" TextMode="Password" /><br />
Userskey:<asp:TextBox runat="server" ID="AccessKey" /><br />
<asp:Button runat="server" ID="Login" CommandName="Login" Text="Login" />
</div>
<asp:TextBox ID="txtKey" runat="server"></asp:TextBox>
</LayoutTemplate>
</asp:Login>
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
TextBox AccessKey = Login1.FindControl("AccessKey") as TextBox;
if (YourValidation(AccessKey.Text, Login1.UserName, Login1.Password))
{
e.Authenticated = true;
}
else
{
e.Authenticated = false;
}
}