本文英文原版及代码下载:
http://aspnet.4guysfromrolla.com/articles/060706-1.aspx
考察ASP.NET 2.0中的Membership, Roles, and Profile - Part 5
导言:
我们知道ASP.NET 2.0通过membership, roles,profile systems来创建和管理用户帐户。要为用户提供登录页面的话,我们只需要拖一个Login Web控件到页面即可.但如果我们想做一些用户定制呢?我们可以重新配置Login控件,再另外添加一些内容;或者出除了用户名和密码外,我们还希望用户提供email地址等,或者包含一个CAPTCHA(一些box,其text的背景为图片).可以通过多种方式来对Login Web控件进行定制.比如,是否显示"Remember me next time", 颜色、字体等设置;我们还可以将控件转换成一个模板(template);我们还可以为Authenticate event事件创建处理器,自定义认证逻辑,甚至使用CAPTCHA作为认证的一部分。
本文,我们将考察如何通过Login控件的属性、通过模板来对其进行定制,通过Authentication事件处理器定制认证逻辑.
通过属性定制Login控件
首先我们要创建一个登录页面,如果用户尝试访问其未被授权的页面或点击LoginStatus控件的Login按钮时就会自动的重新定位到该页面。默认情况下,该页面必须命名为Login.aspx,并放在根目录下.当然如果你喜欢的话也可以使用其它的登录URL,但是你需要在Web.config文件里的<authentication>节点里更新<forms>元素:
<?xml version="1.0"?>
<configuration xmlns="
http://schemas.microsoft.com/.NetConfiguration/v2.0
">
<system.web>
...
<authentication mode="Forms">
<forms loginUrl="LoginPath" />
</authentication>
...
</system.web>
</configuration>
从工具栏拖该Login控件到我们创建的登录页面上,如下图所示,Login控件默认包含如下的内容:
. 一个User Name TextBox
. 一个Password TextBox
. 一个"Remember me next time" CheckBox
. 一个"Log In" Button
此外,Login控件还有RequiredFieldValidators控件,以确保用户输入的User Name和Password不为空。另外,还有一个Literal控件用来显示Login控件的FailureText属性的值.
可以通过各种与style相关的属性来美好Login控件的外观。比如Font, BackColor, Width, Height等等.另外还可以通过TextBoxStyle, CheckBoxStyle,和LoginButtonStyle来设置Login控件内部的TextBoxes, CheckBox,和Button Web控件.
我们还可以改变其属性。比如,想将 "User Name:"文本换成别的吗?改动UserNameLabelText属性即可;想在添加说明吗?在InstructionText属性设置即可;通过LoginButtonText属性改变"Log In" Button的文本.通过HelpPageText 和 HelpPageUrl属性来添加一个定位到帮助页面的链接.而Orientation属性决定了Login控件如何布局.下图以及下载内容包含的Login控件演示了如何改变相关属性的值来改变其外观。
图2
以上只是进行了一定程度的定制,要想进行更大程度的定制,我们必须将其转变成一个模板。为此,切换到设计模式,在智能标签里点"Convert to Template" 项。这将在Login控件的声明代码里添加一个 <LayoutTemplate>,包含一个HTML <table> ,其构造复制了默认的Login控件的布局.你想怎么调整布局都行,包括添加额外的内容和Web控件.
有一点很重要要记住,在模板里的某些登录控件必须保留其原有的ID值。具体来说,<LayoutTemplate>里有2个控件的ID值为UserName和 Password,它们构成了ITextControl界面.另外,你可以使用你个人的自定义服务器控件或User Control,只要它们也可以执行这种界面.另外,你需要一个Button, LinkButton, 或ImageButton,其CommandName要设置为Login控件的LoginButtonCommandName静态域(默认为"Login"),但其ID却可以是任意值.Login控件转变成模板时自动生成的其它控件都是可选的.
下面的声明代码是我对Login控件的<LayoutTemplate>调整后的布局:
<asp:Login ID="Login1" runat="server" BackColor="#F7F7DE" BorderColor="#CCCC99"
BorderStyle="Solid" BorderWidth="1px" Font-Names="Verdana" Font-Size="10pt"
RememberMeSet="True">
<TitleTextStyle BackColor="#6B696B" Font-Bold="True" ForeColor="White" />
<LayoutTemplate>
<table border="0" cellpadding="1" cellspacing="0" style="border-collapse: collapse">
<tr>
<td align="center" rowspan="3" style="padding:15px; font-weight: bold; color:
white; background-color: #6b696b">
Log In
</td>
<td style="width:8px;"> </td>
<td align="right">
<asp:Label ID="UserNameLabel" runat="server"
AssociatedControlID="UserName">User Name:</asp:Label></td>
<td>
<asp:TextBox ID="UserName" runat="server" TabIndex="1"></asp:TextBox>
<asp:RequiredFieldValidator ID="UserNameRequired" runat="server"
ControlToValidate="UserName"
ErrorMessage="User Name is required." ToolTip="User Name is required."
ValidationGroup="Login1">*</asp:RequiredFieldValidator>
</td>
<td align="center" rowspan="3" style="padding:15px;">
<asp:Button ID="LoginButton" runat="server" CommandName="Login" Text="Log In"
ValidationGroup="Login1" TabIndex="4" />
</td>
</tr>
<tr>
<td style="width:8px;"> </td>
<td align="right">
<asp:Label ID="PasswordLabel" runat="server"
AssociatedControlID="Password">Password:</asp:Label></td>
<td>
<asp:TextBox ID="Password" runat="server" TextMode="Password"
TabIndex="2"></asp:TextBox>
<asp:RequiredFieldValidator ID="PasswordRequired" runat="server"
ControlToValidate="Password"
ErrorMessage="Password is required." ToolTip="Password is required."
ValidationGroup="Login1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td style="width:8px;"> </td>
<td colspan="2">
<asp:CheckBox ID="RememberMe" runat="server" Text="Remember me next time."
TabIndex="3" />
</td>
</tr>
</table>
<asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowMessageBox="True"
ValidationGroup="Login1"
ShowSummary="False" />
</LayoutTemplate>
</asp:Login>
心细的读者可能已经注意到上面的布局忽略了FailureText Literal控件,该控件在用户登录失败时显示Login控件FailureText属性的值。我决定在一个客户端alert box里显示该文本。为此,我将为Login控件的 LoginError事件创建处理器,编写必要的JavaScript来展示该alert box。(在Part 4我们探讨过该事件)
Protected Sub Login1_LoginError(ByVal sender As Object, ByVal e As System.EventArgs) Handles Login1.LoginError
'Display the failure message in a client-side alert box
ClientScript.RegisterStartupScript(Me.GetType(), "LoginError", _
String.Format("alert('{0}');", Login1.FailureText.Replace("'", "\'")), True)
End Sub
最后,请注意<table>标签后的ValidationSummary控件的内容,我将ShowSummary 和 ShowMessageBox属性做上述设置后,一旦输入有误(也就是说没有输入username 或password)时将显示一个客户端的alert box.下面的一个图,alert box显示用户必须输入username;而第二个图片,用户尝试登录,但输入有误.
图3
定制认证逻辑
当用户输入完并点击"Log In"按钮时,紧接着发生如下的步骤:
1.发生页面回传
2.Login控件的LoggingIn事件触发
3.Login控件的Authenticate事件触发;它调用一个事件处理器以执行必要的逻辑,判断用户认证是否有效,若有 效则该事件处理器将传入的AuthenticateEventArgs对象的Authenticated属性设为True.
4.如果用户认证失败将引发LoginError事件,反之将引发LoggedIn事件
默认,Login控件在内部处理Authenticate事件,通过调用Membership class类的ValidateUser(username, password)方法来进行验证。然而,我们可以对Login控件使用的验证逻辑进行定制,我们可以自己创建该事件的事件处理器,如果通过验证就将e.Authenticated属性设为True;而未通过就设为False.
本例,我们不仅要验证username 和 password,而且还有其email地址,以及一个CAPTCHA.我们知道CAPTCHA是一种人可以感知(而机器人程序无法感知)的技术.CAPTCHA通常为在一个图片上显示一些扭曲的文本,让用户输入这些文本.计算机程序不能分析该图片以及破解其中的文本,而只有人才可以轻松的做到。我用到的该CAPTCHA是eff Atwood的免费且优秀ASP.NET CAPTCHA server control控件.
要创建一个用户自定义验证逻辑示例,我们先将一个标准的Login控件转变为一个模板,并添加一个名为Email的TextBox(附带一个RequiredFieldValidator控件),以及一个Jeff的CAPTCHA控件:
<asp:Login ID="Login1" ...>
<LayoutTemplate>
<table border="0" cellpadding="1" cellspacing="0" style="border-collapse: collapse">
<tr>
<td align="center" rowspan="5" style="padding:15px; font-weight: bold; color:
white; background-color: #6b696b">
Log In
</td>
<td style="width:8px;"> </td>
<td align="right">
<asp:Label Font-Bold="True" ID="UserNameLabel" runat="server"
AssociatedControlID="UserName">User Name:</asp:Label></td>
<td>
<asp:TextBox ID="UserName" runat="server" TabIndex="1"></asp:TextBox>
<asp:RequiredFieldValidator ID="UserNameRequired" runat="server"
ControlToValidate="UserName"
ErrorMessage="User Name is required." ToolTip="User Name is required."
ValidationGroup="Login1">*</asp:RequiredFieldValidator>
</td>
<td align="center" rowspan="5" style="padding:15px;">
<asp:Button ID="LoginButton" runat="server" CommandName="Login" Text="Log In"
ValidationGroup="Login1" TabIndex="5" />
</td>
</tr>
<tr>
<td style="width:8px;"> </td>
<td align="right">
<asp:Label Font-Bold="True" ID="PasswordLabel" runat="server"
AssociatedControlID="Password">Password:</asp:Label></td>
<td>
<asp:TextBox ID="Password" runat="server" TextMode="Password"
TabIndex="2"></asp:TextBox>
<asp:RequiredFieldValidator ID="PasswordRequired" runat="server"
ControlToValidate="Password"
ErrorMessage="Password is required." ToolTip="Password is required."
ValidationGroup="Login1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td style="width:8px;"> </td>
<td align="right">
<asp:Label Font-Bold="True" ID="EmailLabel" runat="server"
AssociatedControlID="Email">Email:</asp:Label></td>
<td>
<asp:TextBox ID="Email" runat="server" TabIndex="3"></asp:TextBox>
<asp:RequiredFieldValidator ID="EmailRequired" runat="server"
ControlToValidate="Email"
ErrorMessage="Email is required." ToolTip="Email is required."
ValidationGroup="Login1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td style="width:8px;"> </td>
<td align="right">
<b>Verification:</b>
</td>
<td>
<cc1:CaptchaControl id="CAPTCHA" runat="server" ShowSubmitButton="False"
TabIndex="3" LayoutStyle="Vertical"></cc1:CaptchaControl>
</td>
</tr>
<tr>
<td style="width:8px;"> </td>
<td colspan="2">
<asp:CheckBox ID="RememberMe" runat="server" Text="Remember me next time."
TabIndex="4" />
</td>
</tr>
</table>
<asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowMessageBox="True"
ValidationGroup="Login1"
ShowSummary="False" />
</LayoutTemplate>
</asp:Login>
接下来,我们将为Authenticate事件创建一个事件处理器.我们先检查CAPTCHA是否有效,再用Membership.ValidateUser方法来确保用户的username 和 password是有效的;再调用Membership.GetUser(username)方法,将Email属性与用户提供的做对比;如果有任何一项出错,则将 e.Authenticated设为False,反之则设为True. 我对Login控件的FailureText属性进行了更新,显示用户无法登录的更具体的信息.
注意我们在<LayoutTemplate>里用LoginControl.FindControl("ID")方法来访问控件(比如Email TextBox 或 CAPTCHA控件).
Protected Sub Login1_Authenticate(ByVal sender As Object, ByVal e As
System.Web.UI.WebControls.AuthenticateEventArgs) Handles Login1.Authenticate
'We need to determine if the user is authenticated and set e.Authenticated accordingly
'Get the values entered by the user
Dim loginUsername As String = Login1.UserName
Dim loginPassword As String = Login1.Password
'To get a custom Web control, must use FindControl
Dim loginEmail As String = CType(Login1.FindControl("Email"), TextBox).Text
Dim loginCAPTCHA As WebControlCaptcha.CaptchaControl = CType(Login1.FindControl("CAPTCHA"),
WebControlCaptcha.CaptchaControl)
'First, check if CAPTCHA matches up
If Not loginCAPTCHA.UserValidated Then
'CAPTCHA invalid
Login1.FailureText = "The code you entered did not match up with the image provided;
please try again with this new image."
e.Authenticated = False
Else
'Next, determine if the user's username/password are valid
If Membership.ValidateUser(loginUsername, loginPassword) Then
'See if email is valid
Dim userInfo As MembershipUser = Membership.GetUser(loginUsername)
If String.Compare(userInfo.Email, loginEmail, True) <> 0 Then
'Email doesn't match up
e.Authenticated = False
Login1.FailureText = "The email address you provided is not the email address on
file."
Else
'Only set e.Authenticated to True if ALL checks pass
e.Authenticated = True
End If
Else
e.Authenticated = False
Login1.FailureText = "Your username and/or password are invalid."
End If
End If
End Sub
做完这些修改后,该Login控件现在包含了用户的email以及一个CAPTCHA,如下所示,只有当用户提供正确的username, password, email address,CAPTCHA后才能正确登录.
结语:
本文,我们看到了如何通过改变Login控件的配置或将其转变为一个模板,来定制其外观和布局.当转变为模板后我们可以添加额外的Web控件,比如一个CAPTCHA.再为Login控件的Authenticate事件创建一个事件处理器,我们可以自定义验证逻辑.
祝编程快乐!
附录:
名词解释:CAPTCHA
(Completely Automated Public Turing test to tell Computers and Humans Apart ,全自动区分计算机和人类的图灵测试)
CAPTCHA ——用以区分计算机和人类,在人机差别非常小的网络上非常有效。它会生成一个随机的图片显示给用户。这个图片含有一个不容易被计算机识别(OCR)的字符串,同时这个字符串在页面的代码及其他计算机可以获取的地方被使用。如果表单提交的时候并不含有正确的字符串,系统就能够确信输入人员录入错误或者不是一个真正的人在进行录入。
沿着上述思路发展,除了图象外,目前又出现了语音形式的验证方法。