让我们通过编写一个完整的示例程序学习如何配置并使用ASP.NET AJAX的AuthenticationService对象来实现用户登录和注销。
5.2.1 启用身份认证应用程序服务
若想在我们的Web应用程序中使用ASP.NET 2.0内建的身份认证服务,首先就是在web.config文件中启用该服务。在web.config文件的<configuration />\<system.web />节下添加如下一段配置:
<authentication mode="Forms">
<forms cookieless="UseCookies" loginUrl="~/AuthenticationServiceDemo.aspx"/>
</authentication>
这样即为该Web站点启用了身份认证应用程序服务,且设置认证的类型为Forms,即表单认证。loginUrl设置了Web站点的登录页面,这里的~/AuthenticationServiceDemo.aspx就是本示例程序的页面名称,也就是说,若用户的权限不够访问网站内的某个页面,ASP.NET会自动将用户重定向。
参考:更多有关ASP.NET 2.0内建的身份认证服务的内容,请参考这篇MSDN文章:《Authentication in ASP.NET: .NET Security Guidance》(http://msdn2.microsoft.com/en-us/library/ms978378.aspx)。
若想在客户端JavaScript脚本中直接使用AuthenticationService对象实现用户登录和注销,我们还需要对ASP.NET AJAX进行配置。同样是在web.config文件中,在<configuration />节下添加如下配置:
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true" />
</webServices>
</scripting>
</system.web.extensions>
其中将<authenticationService />节中的enabled属性设置为true之后,即可在客户端脚本中直接访问到ASP.NET 2.0的身份认证服务。
提示:更多有关ASP.NET AJAX的配置的介绍,请参考本书第I卷。
5.2.2 配置Membership数据库并创建用户
在ASP.NET 2.0内建的身份认证服务中,用户信息存放在Membership数据库中。在本机的machine.config配置文件(默认位置为C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.config)中,我们可以找到如下一段:
<connectionStrings>
<add name="LocalSqlServer" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient" />
</connectionStrings>
若你希望使用自定义的数据库存放用户信息,可以在本Web站点的web.config文件中<connectionStrings />一节添加相应的配置元素。这里我们出于演示的目的,将使用该默认数据库。
参考:若想了解更多有关Membership数据库的相关内容,请参考这篇MSDN文章:《Configuring an ASP.NET Application to Use Membership》(http://msdn2.microsoft.com/en-us/library/6e9y4s5t.aspx)。
然后,在Visual Studio中选择“Website | ASP.NET Configuration”菜单项,打开ASP.NET Web Site Administration Tool工具,初始界面将如图5-1所示。
图5-1 ASP.NET Web Site Administration Tool的初始界面
在该工具的“Security”选项卡中,我们来为本Web站点添加一个用户,便于稍后使用该用户测试示例程序的登录/注销功能。这里我们创建的用户的用户名为“Dflying”,密码为“p@ssword”。如图5-2所示。
图5-2 使用ASP.NET Web Site Administration Tool创建用户
创建之后,Membership数据库中就包含了一个名为Dflying,密码为p@ssword的用户。请牢记上述登录信息,稍后即将用到。
参考:若想了解使用ASP.NET Web Site Administration Tool创建用户的详细步骤,请参考这篇MSDN文章:《Walkthrough: Creating a Web Site with Membership and User Login (Visual Studio) 》(http://msdn2.microsoft.com/en-us/library/879kf95c(VS.80).aspx)。
参考:ASP.NET Web Site Administration Tool还提供了很多非常有用的管理ASP.NET站点的功能,请参考这篇MSDN文章:《Web Site Administration Tool Overview 》(http://msdn2.microsoft.com/en-us/library/yy40ytx0.aspx)。
5.2.3 创建受保护的目录
为了区分登录用户和匿名用户的差别,我们接下来在该Web站点中创建一个受保护的、其中内容只能被登录用户访问的目录(名为SecuredFolder),并在其中创建一张ASP.NET页面(名为Default.aspx)和相应的web.config文件。图5-3显示了该目录在Visual Studio中的结构。
图5-3 Visual Studio中SecuredFolder目录的结构
为了在阻止匿名用户访问该目录中页面的同时,允许登录用户访问,我们需要按照如下所示修改该目中的web.config文件,注意其中粗体部分:
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<authorization>
<deny users="?"/>
<allow users="*"/>
</authorization>
</system.web>
</configuration>
而受保护的Default.aspx页面则非常简单,仅仅显示出一条说明信息而已:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Secured Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Only authenticated users have access to this page.
</div>
</form>
</body>
</html>
上述配置之后,只有登录用户才能够访问到SecuredFolder/Default.aspx页面,匿名用户若尝试访问的话,将被重定向至~/AuthenticationServiceDemo.aspx页面。
5.2.4 示例程序的UI部分
完成了前面3小节的准备工作之后,我们终于能够开始编写示例程序了。新建一张名为AuthenticationServiceDemo.aspx的ASP.NET页面,让后让我们从程序的UI部分入手——当然,ScriptManager控件总是必不可少的:
<asp:ScriptManager ID="sm" runat="server" />
然后创建一个登录表单,该表单将显示给匿名用户。其中包含两个文本框,分别用来输入用户名和密码,并提供了一个按钮,用来触发用户登录操作。请注意登录表单和用户名/密码文本框的id,将在稍后的程序中用到:
<div id="loginPanel">
<strong>Please Login</strong>
<hr />
<label for="tbUserName">User Name:</label>
<input id="tbUserName" type="text" /><br />
<label for="tbUserName">Password:</label>
<input id="tbPassword" type="password" /><br />
<input id="btnLogin" type="button" value="Login"
onclick="return btnLogin_onclick()" />
</div>
已登录用户不会看到上述表单,而会看到如下的注销面板。其中包含一个按钮,用来触发注销操作。同样要留意该面板的id:
<div id="logoutPanel">
<strong>Welcome!</strong>
<hr />
<input id="btnLogout" type="button" value="Logout"
onclick="return btnLogout_onclick()" />
</div>
接下来添加一个<div />,将用来输出验证过程中的一些信息,包括可能发生的异常:
<div id="result">
</div>
最后添加一个超链接,指向前一小节中创建的受保护目录。已登录用户和匿名用户都可以尝试访问该受保护目录,借此区分二者不同的权限:
<span>
Authenticated users have access to this folder:
<a href="SecuredFolder/">SecuredFolder/</a>
</span>
这样即完成了本示例程序的UI部分。在Visual Studio的设计器中,完成后的程序界面将如图5-4所示。
图5-4 Visual Studio设计器中示例程序的界面
5.2.5 示例程序的JavaScript代码部分
完成了程序的UI部分之后,我们来用JavaScript代码把各个UI元素协调、统一起来,最终完成本示例程序。在页面中ScriptManager的出现位置之后添加一个<script />标签,接下来所有的脚本内容均将置于该标签中:
<script type="text/javascript">
</script>
首先编写pageLoad()函数,该函数将在客户端应用程序加载完毕后被ASP.NET AJAX客户端框架自动调用:
function pageLoad(sender, args) {
// 刷新登录面板和已登录用户面板的显示状态
refreshLoginPanels();
}
在上述pageLoad()函数中,我们调用了另外一个名为refreshLoginPanels()的函数。该函数用来根据当前用户是否已经登录,决定页面中应该显示出两个面板(登录表单和注销面板)中的哪一个。refreshLoginPanels()的函数的代码如下,注意其中使用了AuthenticationService对象的isLoggedIn属性:
function refreshLoginPanels() {
// 用户是否已经登录?
var isLoggedIn = Sys.Services.AuthenticationService.get_isLoggedIn();
// 刷新登录面板和已登录用户面板的显示状态
$get("loginPanel").style.display = isLoggedIn ? "none" : "block";
$get("logoutPanel").style.display = !isLoggedIn ? "none" : "block";
}
接下来设定一下AuthenticationService对象的3个默认回调函数,这3个回调函数分别将在登录完成、注销完成以及调用失败时被调用:
Sys.Services.AuthenticationService.set_defaultLoginCompletedCallback(onLoginCompleted);
Sys.Services.AuthenticationService.set_defaultLogoutCompletedCallback(onLogoutCompleted);
Sys.Services.AuthenticationService.set_defaultFailedCallback(onAuthenticationFailed);
让我们依次分析这3个回调函数的实现。首先是登录完成的回调函数——onLoginCompleted()。其中先根据用户是否成功登录来显示出一段提示信息,然后调用前面给出过的refreshLoginPanels()函数更新两个面板的显示状态:
function onLoginCompleted(validCredentials, userContext, methodName) {
// 根据用户是否成功登录,显示提示信息
$get("result").innerHTML =
validCredentials ? "Logged in!" : "Incorrect UserName/Password!";
// 刷新登录面板和已登录用户面板的显示状态
refreshLoginPanels();
}
注销完成的回调函数——onLogoutCompleted()与前者非常类似。同样是先显示出提示信息,然后更新两个面板的显示状态:
function onLogoutCompleted(result, userContext, methodName) {
// 显示用户注销信息
$get("result").innerHTML = "Logged out!"
// 刷新登录面板和已登录用户面板的显示状态
refreshLoginPanels();
}
调用失败时的回调函数——onAuthenticationFailured()的篇幅较长,不过逻辑上却非常简单——取得异常的详细信息并显示给用户而已:
function onAuthenticationFailed(error, userContext, methodName) {
// 取得异常信息
var stackTrace = error.get_stackTrace();
var message = error.get_message();
var statusCode = error.get_statusCode();
var exceptionType = error.get_exceptionType();
var timeout = error.get_timedOut();
// 显示异常信息
$get("result").innerHTML =
"<strong>Error Occurs: </strong><br/>" +
"<strong>Stack Trace: </strong>" + stackTrace + "<br/>" +
"<strong>Service Error: </strong>" + message + "<br/>" +
"<strong>Status Code: </strong>" + statusCode + "<br/>" +
"<strong>Exception Type: </strong>" + exceptionType + "<br/>" +
"<strong>Is Timeout: </strong>" + timeout;
}
提示:出于演示的目的,本程序将异常的所有细节都毫无保留地显示了出来。而在实际应用程序中,我们完全没有必要如此“诚实”地将把一切都告知给用户。
“Login”按钮的click事件处理函数的代码如下。该事件处理函数中只是简单地调用了AuthenticationService对象的login()方法,并将用户名和密码传递给服务器。注意第三个参数表示用户的登录信息将以Cookie的形式保存下来,即使关闭后重新打开一个浏览器窗口,同样也可以得到上次的登录信息:
function btnLogin_onclick() {
Sys.Services.AuthenticationService.login(
$get("tbUserName").value,
$get("tbPassword").value,
true
);
}
“Logout”按钮的click事件处理函数的代码如下,其中调用了AuthenticationService对象的logout()方法,用来注销当前的用户:
function btnLogout_onclick() {
Sys.Services.AuthenticationService.logout();
}
由于我们在前面已经设定了AuthenticationService对象的3个默认回调函数,所以在调用login()和logout()方法返回之后,AuthenticationService对象将自动根据当前情况选择并调用上述3个回调函数中的某一个。
到此为止,我们也完成了整个示例程序的编写。
5.2.6 运行示例程序
运行该示例程序,其初始界面将如图5-5所示。这时若是直接访问受保护的SecuredFolder目录,页面将被重定向回当前页面。
图5-5 示例程序的初始界面
输入用户名Dflying,密码p@ssword登录,可以看到程序即可以Ajax方式进行异步登录。登录后的界面如图5-6所示,注意登录表单被隐藏,显示出了注销面板。此时若是点击“Logout”按钮,当前的用户将被注销,程序也将返回至图5-5所示的界面。
图5-6 登录后的程序界面
此时再次访问受保护的SecuredFolder目录中的Default.aspx页面,即可浏览到该页面的内容,如图5-7所示。
图5-7 登录用户可以访问受保护的SecuredFolder目录中的Default.aspx页面
若是在进行身份认证时程序抛出了异常,我们也会看到异常的详细信息。比如,修改一下“Login”按钮的click事件处理函数的代码,将login()方法的第三个参数注释掉:
function btnLogin_onclick() {
Sys.Services.AuthenticationService.login(
$get("tbUserName").value,
$get("tbPassword").value//,
// true
);
}
再次尝试登录,我们将看到程序显示出如图5-8所示的异常信息。
图5-8 显示身份认证时程序抛出的异常
通过这样一个完整的示例程序,我们了解了使用ASP.NET AJAX的AuthenticationService对象实现Ajax方式的用户登录和注销的方法,若合理应用的话,定会为我们的Ajax应用程序增色不少,给用户登录/注销这一常用场景带来更加流畅的用户体验。