考察ASP.NET 2.0Membership,Roles,Profile - Part 1 導言:
很多網(wǎng)站都有一個(gè)共同點(diǎn):提供用戶(hù)賬號(user accounts),這些網(wǎng)站允許(或需要)訪(fǎng)問(wèn)者創(chuàng )建賬號以使用特定的功能.比如ASPMessageboard.com網(wǎng)站,匿名用戶(hù)和注冊用戶(hù)都可以使用搜索功能,但如果要發(fā)表文章或回復消息的話(huà),訪(fǎng)問(wèn)者就必須獲得用戶(hù)賬號并登錄網(wǎng)站.
一個(gè)支持用戶(hù)賬號的網(wǎng)站,都要包括這些相同的步驟:創(chuàng )建一個(gè)數據庫表來(lái)存儲用戶(hù)賬號信息;創(chuàng )建一個(gè)登錄頁(yè)面;定義一種系統,當頁(yè)面回傳時(shí)仍可保存注冊用戶(hù)的登錄狀態(tài);指出哪些頁(yè)面只對注冊用戶(hù)可見(jiàn);創(chuàng )建一個(gè)新頁(yè)面供新用戶(hù)注冊;創(chuàng )建一個(gè)頁(yè)面便于網(wǎng)站管理員管理用戶(hù)賬號等等.在A(yíng)SP.NET之前,開(kāi)發(fā)者必須決定如何貫徹這些方面。ASP.NET引入了forms-based authentication的概念,二樓提供了一個(gè)FormsAuthentication class類(lèi)來(lái)便于在網(wǎng)站登錄或注銷(xiāo).同樣的還有authentication ticket,在頁(yè)面請求時(shí)保存用戶(hù)的登錄狀態(tài)。(關(guān)于A(yíng)SP.NET的基于窗體的識別功能,請參閱文章《Using Forms Authentication in ASP.NET 》和《Dissecting Forms Authentication》)
即便有了基于窗體的識別功能,ASP.NET開(kāi)發(fā)者仍然要定義并構建相應的結構來(lái)存儲用戶(hù)賬號信息;創(chuàng )建登錄和注銷(xiāo)的web頁(yè)面;還有便于訪(fǎng)問(wèn)者創(chuàng )建新賬號和便于管理員管理賬號的頁(yè)面等.感謝ASP.NET 2.0,利用其membership system 和 security Web控件,可以減輕開(kāi)發(fā)者的負擔.簡(jiǎn)單的說(shuō),membership是一個(gè)API,可以編程訪(fǎng)問(wèn)與用戶(hù)帳戶(hù)相關(guān)的任務(wù).比如:有專(zhuān)門(mén)的方法創(chuàng )建一個(gè)新的用戶(hù)帳戶(hù),識別用戶(hù)身份,刪除用戶(hù),返回所有的用戶(hù)信息等.此外,還有很多建立在該API基礎上的security Web控件.
本系列文章將考察2.0版本中的membership, roles,pofile系統,以及各種security Web控件.本文我們考察membership的基本原理并配置與使用內置的SqlMembershipProvider.
基于窗口的認證(Authentication)
在A(yíng)SP.NET之前,web開(kāi)發(fā)者必須自己定義所有的認證.一個(gè)問(wèn)題是當頁(yè)面請求時(shí)如何記住用戶(hù)的登錄狀態(tài),也就是說(shuō)當用戶(hù)輸入用戶(hù)名和密碼并成功登錄后,當用戶(hù)訪(fǎng)問(wèn)其它頁(yè)面時(shí),網(wǎng)站如何記得該用戶(hù)是否成功登錄了?另一個(gè)問(wèn)題是如何防止頁(yè)面被未授權的用戶(hù)訪(fǎng)問(wèn),也就是說(shuō)如何設置一個(gè)頁(yè)面只能被一部分或被授權的用戶(hù)訪(fǎng)問(wèn)?在經(jīng)典ASP里我們通過(guò)使用Session變量來(lái)解決這些問(wèn)題.每個(gè)頁(yè)面檢查該Session變量一確定訪(fǎng)問(wèn)者的身份,以及是否允許他們訪(fǎng)問(wèn).
為此,ASP.NET 1.0版提供了對forms-based authentication的支持,同時(shí)可以在Web.config文件里指定authorization rules角色.forms-based authentication提供了一種方法將authentication ticket作為一個(gè)cookie存儲在瀏覽器里,在發(fā)生web請求的時(shí)候記住用戶(hù)的登錄狀態(tài).而FormsAuthentication class類(lèi)包含了很多方法來(lái)處理這些authentication ticket??梢詣?chuàng )建它也可以刪除它.
不幸的是forms-based authentication依然留給開(kāi)發(fā)者很多工作.開(kāi)發(fā)者仍然必須決定如何傳遞用戶(hù)帳戶(hù)信息;仍然必須構建登錄頁(yè)面并編寫(xiě)使用FormsAuthentication class類(lèi)的代碼;仍然必須構建注銷(xiāo)頁(yè)面;構建注冊帳戶(hù)頁(yè)面;構建管理帳戶(hù)的頁(yè)面等等。ASP.NET 1.0版本引入forms-based authentication的初衷是好的,只是貫徹起來(lái)有難度.
在Forms-Based Authentication之上構建ASP.NET 2.0的Membership
Forms-based authentication依然存在于A(yíng)SP.NET 2.0版本,使用方法與 1.x版本一樣。同時(shí)在Web.config文件里也有authorization設置. ASP.NET 2.0版本增加的是membership API以及security Web控件.
membership API是通過(guò)provider模式來(lái)執行的.那就意味著(zhù)當定義好界面后可以對實(shí)際的執行過(guò)程進(jìn)行定制.而.NET Framework包含了Membership class類(lèi),該類(lèi)包含可很多方法,比如 CreateUser(), GetAllUsers(), ValidateUser()等等. 然而,當通過(guò)一個(gè)ASP.NET web應用程序來(lái)使用API的時(shí)候實(shí)際調用的類(lèi)取決于應用程序的配置.你可以定制你的用戶(hù)帳戶(hù)邏輯,方法是創(chuàng )建一個(gè)執行定義好的membership API的類(lèi),然后配置web應用程序來(lái)使用你定義的類(lèi).當然,你也用不著(zhù)定義一個(gè)自定義的類(lèi)——ASP.NET包含了2個(gè)內置的membership providers,一個(gè)將用戶(hù)帳戶(hù)信息存儲在一個(gè)SQL Server數據庫;另一個(gè)使用的是實(shí)際的目錄.因此membership system 和 security Web控件都可以被這2個(gè)內置的providers使用.如果你確實(shí)自定義了用戶(hù)數據,你也可以創(chuàng )建一個(gè)自定義的provider通過(guò)相同的API 和 security控件來(lái)使用這些自定義的用戶(hù)數據.關(guān)于provider模式的更多信息,請參閱文章《A Look at ASP.NET 2.0's Provider Model》(
http://aspnet.4guysfromrolla.com/articles/101905-1.aspx)
在本系列的后續文章里,我們將考察創(chuàng )建用戶(hù)自定義的membership provider的詳細步驟.
SqlMembershipProvider——將用戶(hù)帳戶(hù)數據存儲在一個(gè)SQL Server數據庫
ASP.NET 2.0 里的SqlMembershipProvider provider使用一個(gè)數據庫來(lái)存儲認證信息.為了使用該provider,你必須創(chuàng )建一個(gè)相應的數據庫構架(schema).可以通過(guò)2種方法來(lái)完成:
1.使用ASP.NET網(wǎng)站管理工具(這將會(huì )在一個(gè)SQL Server 2005數據庫文件ASPNETDB.mdf里創(chuàng )建構架,且位于A(yíng)pp_Data文件夾)
2.使用ASP.NET SQL Server注冊工具——使用該工具在一個(gè)SQL Server 2000 或 2005 數據庫里創(chuàng )建構架.
要使用ASP.NET網(wǎng)站管理工具的話(huà),首先在Visual Studio的Website菜單,選擇“ASP.NET Configuration”項,然后在Security項里,將authentication類(lèi)型改為"From the internet" 。具體方法是:要么點(diǎn)"Select authentication type"鏈接,要么點(diǎn)" "Use the security Setup Wizard to configure security step by step"鏈接.這樣將自動(dòng)在A(yíng)pp_Data文件夾里創(chuàng )建一個(gè)名為ASPNETDB.mdf的數據庫(我們馬上將考察該數據庫的構架).關(guān)于使用網(wǎng)站管理工具的更多詳情請參閱文章《Website Administration Tool Overview》(
http://msdn2.microsoft.com/en-us/library/yy40ytx0.aspx)
如果你大算將用戶(hù)賬號信息存儲在其它地方——比如App_Data文件夾之外的一個(gè)SQL Server 2000 或SQL Server 2005數據庫. 那么你就需要使用ASP.NET SQL Server注冊工具(aspnet_regsql.exe).該工具具有圖像界面,當然你也可以通過(guò)命令行來(lái)使用它.你可以借助于圖像界面指定在什么地方添加所需要的表.關(guān)于使用該工具的更多詳情,請參閱技術(shù)文檔(
<authentication mode="Forms" />
如果你通過(guò)ASP.NET SQL Server注冊工具來(lái)創(chuàng )建數據庫構架的話(huà),你必須手工向Web.config文件添加在行。此外,沒(méi)有在A(yíng)pp_Data文件夾里的ASPNETDB.mdf數據庫里創(chuàng )建構架,而是在其它數據庫創(chuàng )建的,那么你必須要在Web.config文件里定制membership配置,并指定連接數據庫的連接字符串.
SqlMembershipProvider將用戶(hù)帳戶(hù)信息存儲在下面2個(gè)表里:
.aspnet_Users -每一個(gè)用戶(hù)帳戶(hù)一條記錄,存儲最基本的信息.其UserId列唯一的標識用戶(hù)身份.
.aspnet_Membership—該表的UserId列將該表與aspnet_Users表里的某條特定記錄對應起來(lái).aspnet_Membership表存儲的是與用戶(hù)賬號相關(guān)的數據: Email, Password, 安全提示問(wèn)題以及答案等等.
定制SqlMembershipProvider
如果你希望使用SqlMembershipProvider的默認配置(那就意味著(zhù)用戶(hù)帳戶(hù)信息存儲在A(yíng)pp_Data文件夾里的ASPNETDB.mdf數據庫里),那么你除了在Web.config文件里指定使用到的窗體認證和授權角色(authorization rules)外,不需要做其它的更改.(對于窗體認證和授權角色問(wèn)題,可以通過(guò)ASP.NET網(wǎng)站管理工具來(lái)指定。關(guān)于在Web.config文件里指定authorization配置的更多信息請參閱文章《 Authentication and Authorization》(http://samples.gotdotnet.com/quickstart/aspplus/doc/authandauth.aspx)和文章《Authorizing Users and Roles》(
http://samples.gotdotnet.com/quickstart/aspplus/doc/authorization.aspx)
如果你打算使用另外一個(gè)數據庫,或者說(shuō)改變membership的某些配置(比如:email地址是否是唯一的;密碼的最短長(cháng)度; 密碼是純文本還是經(jīng)過(guò)加密;安全提示問(wèn)題和答案是不是必需的等等)的話(huà),你就要在Web.config文件里手動(dòng)輸入XML模塊來(lái)指定你自己的用戶(hù)配置.(注意:你必須要為applicationName設置一個(gè)“硬編碼”值,有關(guān)這方面的更多詳情請參閱《Always set the "applicationName" property when configuring ASP.NET 2.0 Membership and other Providers》)
下面的XML代碼顯示了如何定制SqlMembershipProvider設置。具體來(lái)說(shuō),屬于黑體字的XML在<membership>元素里對設置進(jìn)行定制。在其上還有一個(gè)<connectionString>節點(diǎn),提供了連接到數據庫的連接字符串(我們可以推算出來(lái),該數據庫構架是使用ASP.NET SQL注冊工具添加的) :
<configuration>
<connectionStrings>
<add name="MyDB" connectionString="..." />
</connectionStrings>
<system.web>
... authentication & authorization settings ...
<membership defaultProvider="CustomizedProvider">
<providers>
<add name="CustomizedProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="MyDB"
applicationName="ScottsProject"
minRequiredPasswordLength="5"
minRequiredNonalphanumericCharacters="0" />
</providers>
</membership>
</system.web>
</configuration>
ASP.NET 2.0包含了一個(gè)內置的名為L(cháng)ocalSqlServer的連接字符串,它指向 App_Data文件夾里的ASPNETDB數據庫.如果你希望使用默認的ASPNETDB數據庫,并只改變一些屬性.將connectionStringName配置為L(cháng)ocalSqlServer.
在<membership>元素里添加一個(gè)名為CustomizedProvider的新provider,并作為默認的membership provider.該自定義provider仍然使用SqlMembershipProvider ;將connectionStringName設置為MyDB(就是在<connectionStrings>節點(diǎn)指定的);將applicationName設置為ScottsProject;將minRequiredPasswordLength設置為5;將minRequiredNonalphanumericCharacters設置為0.我們只是對很少的屬性進(jìn)行自定義設置,全部屬性清單見(jiàn)《<add> Element for Providers for Membership》。
Security Web控件的簡(jiǎn)單概述
Security Web控件為處理與用戶(hù)帳戶(hù)相關(guān)的的任務(wù)提供了一個(gè)用戶(hù)界面.主要包括7種security user控件:
Login控件——該控件呈現為標準的username/password登錄界面.默認下,當用戶(hù)點(diǎn)擊"Login"按鈕時(shí),立即產(chǎn)生頁(yè)面回傳,控件調用Membershipclass類(lèi)的VerifyUser(username, password)方法來(lái)對用戶(hù)進(jìn)行驗證,如果驗證通過(guò)則為用戶(hù)創(chuàng )建一個(gè)authentication ticket,否則顯示一個(gè)出錯信息.
為L(cháng)oginError event事件創(chuàng )建事件處理器,便于當用戶(hù)為通過(guò)驗證時(shí)自定義處理步驟;為Authenticate event事件創(chuàng )建事件處理器,以執行你個(gè)人的驗證邏輯;Login控件包含了很多的屬性供配置,以改變用戶(hù)界面的外觀(guān).要完善控件,可以使用LayoutTemplate,下面的截屏為L(cháng)ogin控件的默認用戶(hù)界面:
LoginView控件:有時(shí)我們希望根據訪(fǎng)問(wèn)者是匿名用戶(hù)還是登錄用戶(hù)來(lái)顯示不同的內容:比如,當一個(gè)匿名用戶(hù)訪(fǎng)問(wèn)主頁(yè)時(shí)你可能希望顯示一個(gè)Login Web控件,而如果是一個(gè)登錄用戶(hù)的話(huà),我們希望顯示一個(gè)消息,比如:"Welcome back, username" ,再附加一個(gè)注銷(xiāo)的鏈接.
LoginView控件提供了2個(gè)模板:AnonymousTemplate 和 LoggedInTemplate模板.將顯示給匿名用戶(hù)的Web控件和HTML標記放在A(yíng)nonymousTemplate模板里;而將顯示給登錄用戶(hù)看的放在LoggedInTemplate模板;此外該控件還可以根據登錄者角色的不同顯示不同的界面.
PasswordRecovery控件-允許用戶(hù)重新獲取現有的密碼或將一個(gè)新密碼傳遞到用戶(hù)的電子郵件地址.如果密碼在存儲時(shí)經(jīng)過(guò)哈希算法處理,那么“重新獲取”密碼實(shí)際上是創(chuàng )建一個(gè)新的隨機的密碼,再送回給你.如果密碼是純文本或經(jīng)過(guò)加密處理,那么將把現有密碼送回給用戶(hù).
LoginStatus控件—如果是匿名用戶(hù),該控件將顯示一個(gè)轉到登錄頁(yè)面的連接;如果是登錄用戶(hù)將顯示一個(gè)注銷(xiāo)鏈接.
LoginName控件——該控件僅僅將登錄用戶(hù)的username顯示出來(lái)。另外,可以通過(guò)User.Identity.Name,編程訪(fǎng)問(wèn)用戶(hù)的名稱(chēng).
CreateUserWizard控件——除了登錄的頁(yè)面外,我們還需要一個(gè)供訪(fǎng)問(wèn)者注冊的頁(yè)面。而CreateUserWizard控件就提供了創(chuàng )建用戶(hù)賬號所需的用戶(hù)界面,當用戶(hù)輸入必需的數據并點(diǎn)"Create User"按鈕時(shí),將調用Membership class類(lèi)的 CreateUser(...)方法.如果需要的話(huà),可以用templates對CreateUserWizard控件進(jìn)行定制.下面的截屏為運行中的CreateUserWizard控件.
同樣可以對CreateUserWizard控件的布局和參數進(jìn)行定制,詳情見(jiàn)《Customizing the CreateUserWizard Control》(
http://aspnet.4guysfromrolla.com/articles/070506-1.aspx)
ChangePassword控件——該控件允許用戶(hù)改變其密碼
所有的security Web控件在使用的時(shí)候可以不用寫(xiě)一行代碼。比如你要創(chuàng )建一個(gè)登錄頁(yè)面,你只需要創(chuàng )建一個(gè)Login.aspx頁(yè)面并拖一個(gè)Login控件到頁(yè)面上。瞧,就這么簡(jiǎn)單,不用寫(xiě)一行代碼。此外security Web控件包含了豐富的事件模型,你可以編程處理各種不同的事件.在本文的下載內容里包含了幾個(gè)頁(yè)面演示如何使用這些控件.
編程使用Membership System
可以通過(guò)Membership class類(lèi)來(lái)使用membership system的函數,比如: GetAllUsers(), CreateUser(), DeleteUser()等等.你可以通過(guò)security Web控件在使用這些方法,也可以直接從ASP.NET頁(yè)面調用這些方法.比如你可以創(chuàng )建一個(gè)頁(yè)面來(lái)列出所有的用戶(hù),方法是將Membership.GetAllUsers()返回的結果綁定到一個(gè)GridView控件上。在本文下載內容里有幾個(gè)編程使用Membership class類(lèi)的示例.
結語(yǔ):
由于很多網(wǎng)站需要用戶(hù)賬號支持,所以ASP.NET對該功能的支持是很有意義的.不過(guò)在A(yíng)SP.NET 1.0版本里面,本目標只達到了一半。雖然基于窗口的驗證提供了標準的手段包含一個(gè)authentication ticket來(lái)維系用戶(hù)的登錄狀態(tài),但無(wú)法存儲用戶(hù)帳戶(hù)信息或創(chuàng )建必要的web頁(yè)面(比如:登錄、創(chuàng )建帳戶(hù)等等)。在2.0版本,ASP.NET包含了membership服務(wù)和一些security Web控件.以彌補1.x版本的不足.
在本文,我們考察了membership system的注意目標,一個(gè)其中一個(gè)內置的membership providers——SqlMembershipProvider.該SqlMembershipProvider將用戶(hù)帳戶(hù)信息存儲在一個(gè)SQL Server數據庫,我們也可以通過(guò)Web.config文件對其進(jìn)行用戶(hù)定制。此外我們還總覽了ASP.NET的security Web控件.它用來(lái)執行登錄、創(chuàng )建用戶(hù)帳戶(hù),以及其他與用戶(hù)帳戶(hù)相關(guān)的用戶(hù)界面元素.
除了membership system外,ASP.NET 2.0還包含了roles 和 profile system.我們將在后面的文章探討它們.
祝編程快樂(lè )!
考察Membership, Roles, 和Profile - Part 2 導言:
在Part 1我們已經(jīng)提到Membership class類(lèi)包含很多方法可以用來(lái)創(chuàng )建、刪除、修改、檢索、驗證用戶(hù).由于每個(gè)開(kāi)發(fā)者的需求不同,Membership class類(lèi)被設計為使用provider模式。這就意味著(zhù)membership framework在實(shí)際的執行過(guò)程中可以進(jìn)行用戶(hù)定制.ASP.NET包含有SqlMembershipProvider和ActiveDirectoryMembershipProvider,如果有必要的話(huà),你可以構建自己的provider.
很多網(wǎng)站需要將用戶(hù)劃分為不同的角色.對于處于某種角色的用戶(hù),要指定其可以訪(fǎng)問(wèn)哪些頁(yè)面、可以看到頁(yè)面的哪些內容、頁(yè)面某些區域的內容對用戶(hù)是可編輯還是只讀.使用ASP.NET 2.0的roles service的話(huà),對用戶(hù)進(jìn)行角色劃分、基于角色的功能和驗證都比較簡(jiǎn)單了.與membership service類(lèi)似,我們可以創(chuàng )建、刪除role,對用戶(hù)分配或移除角色;哪些用戶(hù)屬于哪個(gè)角色.
在本文,我們將考察ASP.NET 2.0的role service.我們最開(kāi)始將看如何在網(wǎng)站里建立并配置roles service,以及如何基于認證規則來(lái)使用roles. 此外,我們看如何編程來(lái)處理roles service,以及如何使用LoginView Web控件,根據用戶(hù)的角色顯示信息.
先前準備
為使你的website支持角色,你的網(wǎng)站首先要有用戶(hù)帳戶(hù),這些帳戶(hù)要么
由SqlMembershipProvider存儲在數據庫里;要么存儲在其它的數據庫里.
而role只是一個(gè)概念,將role分給具體的用戶(hù).
設置你的Website支持Role
和membership service一樣,roles service也是使用provider模式,只是ASP.NET 2.0有3個(gè)role providers:
1.SqlRoleProvider(默認的)—將role信息存儲在一個(gè)SQL Server數據庫里.如果你使用SqlMembershipProvider的話(huà),也可以用它來(lái)處理roles.具體來(lái)說(shuō),roles service使用2個(gè)表:aspnet_Roles表用來(lái)為系統里的每一個(gè)role生成一條記錄;另一個(gè)是aspnet_UsersInRoles表,該表將aspnet_Users表與aspnet_Roles表里的roles聯(lián)系起來(lái).
2.WindowsTokenRoleProvider—獲得Windows用戶(hù)的族群信息.如果你是由Windows authentication登錄的授權用戶(hù),那么該provider允許你查看用戶(hù)所在組的情況
3.AuthorizationStoreRoleProvider—由授權管理策略提供role信息,比如Active Directory.
本文將只對SqlRoleProvider provider進(jìn)行考察,為了使用該provider,我們必須創(chuàng )建相應的數據庫表.就像我們在Part 1探討的那樣,有2種方式:
1.通過(guò)ASP.NET SQL Server注冊工具(aspnet_regsql.exe)——該工具允許你將必需的數據庫表、視圖、存儲過(guò)程拷貝到指定的SQL Server 2000 或 SQL Server 2005數據庫.
2.通過(guò)ASP.NET Website管理工具——當將認證類(lèi)型選為"From the internet"時(shí),該工具自動(dòng)的調用aspnet_regsql.exe工具,在A(yíng)pp_Data文件夾里的ASPNETDB.mdf數據庫里創(chuàng )建相應的數據表.
假設你已經(jīng)創(chuàng )建好了必需的數據庫表,要激活role service的話(huà),打開(kāi)ASP.NET Website管理工具,點(diǎn)擊“Security”標簽,再點(diǎn)擊"Enable roles"鏈接,如下所示:
在Web.config文件里添加如下的代碼:
<roleManager enabled="true" />
如果你是使用的自己的數據庫來(lái)存儲SqlMembershipProvider 和 SqlRoleProvider providers的數據,你需要在<roleManager>元素里指定
一個(gè)provider,像下面這樣:
<configuration>
<connectionStrings>
<add name="MyDB" connectionString="..." />
</connectionStrings>
<system.web>
... authentication & authorization settings ...
<roleManager enabled="true"
defaultProvider="CustomizedRoleProvider">
<providers>
<add name="CustomizedRoleProvider"
type="System.Web.Security.SqlRoleProvider"
connectionStringName="MyDB" />
</providers>
</roleManager>
</system.web>
</configuration>
其中,<connectionString>設置為myDB,其指定的連接字符串連接的數據庫里存儲的是與membership 和 role有關(guān)的數據庫表.(我們也可以在<roleManager> 和 <providers>元素里添加額外的特性,我們將在后面的文章探討)
管理Roles
role service提供了一個(gè)Roles class類(lèi),該類(lèi)包含的方法可用于創(chuàng )建和刪除角色,以及從角色里添加或移除用戶(hù).當你希望以編程的方式來(lái)完成這些事情的時(shí)候,這是比較有用的.不過(guò)我們通常以手工的形式來(lái)進(jìn)行操作.我們可以用ASP.NET Website管理工具來(lái)完成,一旦一個(gè)角色被激活后,"Enable roles"鏈接就會(huì )變成這2個(gè)鏈接:"Disable roles"和"Create or Manage roles"。點(diǎn)擊后一個(gè)鏈接的話(huà),在接下來(lái)的界面里你可以添加新角色或管理、刪除現有的角色.
點(diǎn)擊Manage鏈接的話(huà),你將看到某個(gè)角色下的一系列用戶(hù).類(lèi)似的,退回到Security標簽,選"Manage users"的話(huà),你也可以看到一個(gè)用戶(hù)所屬的一系列角色.
基于Role的認證
在A(yíng)SP.NET 1.x版本里,利用Web.config文件里的<authorization>元素,我們可以對基于URL的認證(URL-based authorization),進(jìn)行user和role級的限制.具體來(lái)說(shuō),一個(gè)<authorization>元素包含多個(gè)<allow> 和 <deny>元素,以指定一個(gè)文件夾或URL的認證角色.默認情況下anyone可以訪(fǎng)問(wèn)一個(gè)URL,因此如果你要進(jìn)行限制的話(huà),你應該將<allow> 和 <deny> 元素合理的組合起來(lái).
在2.0版本里,我們可以通過(guò)ASP.NET Website管理工具任意的進(jìn)行設置.(當然,如果你愿意的話(huà),也可以手工在Web.config文件添加<authorization>元素)。在管理工具的Security標簽,點(diǎn)"Create access rules"鏈接. 這樣在接下來(lái)的界面里你可以將authorization設置為users, user classes(*代表所有的users,?代表匿名users)或者roles(如果系統里有的話(huà)).選一個(gè)文件夾應用rules,指定user, user class,或 role,然后選擇訪(fǎng)問(wèn)權限(Allow 或 Deny)
處理Roles Class類(lèi)
通過(guò)Role class類(lèi)可以編程的方式以多種方法訪(fǎng)問(wèn)role service,比如:
.CreateRole(roleName):向系統添加新的角色
.DeleteRole(roleName):從系統刪除角色
.AddUserToRole(userName, roleName):向特定角色添加特定的用戶(hù)
.IsUserInRole(roleName) / IsUserInRole(userName, roleName):返回true 或 false.判斷當前用戶(hù)是否是某個(gè)特定角色的一員.
.GetAllRoles():返回有關(guān)系統所有的角色的字符串數組
.GetRolesForUser() / GetRolesForUser(userName):返回一個(gè)字符串數組,要么是當前用戶(hù)所屬的所有角色,要么是某個(gè)指定用戶(hù)所屬的所有角色.
這些方法可以用來(lái)在頁(yè)面展示系統里所有的角色,或某個(gè)用戶(hù)所屬的所有角色,或判斷某個(gè)用戶(hù)是否屬于某個(gè)角色.本文結尾部分的示例有2個(gè)頁(yè)面來(lái)演示如何使用Roles lass類(lèi):一個(gè)是UserList.aspx頁(yè)面,它列出了系統的所有用戶(hù)以及用戶(hù)所屬的角色。另一個(gè)是RoleList.aspx頁(yè)面, 它列出了系統所有的角色以及每個(gè)角色所屬的用戶(hù).
在LoginView控件里使用基于角色的代碼
在前面我們總覽了ASP.NET 2.0里的security Web控件.其中一個(gè)是LoginView控件,它有2個(gè)模板:AnonymousTemplate和LoggedInTemplate.
當一個(gè)匿名用戶(hù)登錄頁(yè)面時(shí),該控件將顯示AnonymousTemplate模板里的內容;如果是注冊用戶(hù)登錄的話(huà),將顯示LoggedInTemplate模板里的內容.
LoginView控件也可以包含基于角色的模板,比如向控件添加一個(gè) <RoleGroups>元素,再在里面放一個(gè)<asp:RoleGroup Roles="comma-delimited list of roles">模板,如下:
<asp:LoginView ID="LoginView1" runat="server">
<AnonymousTemplate>
OMG, you are, like so not logged in!
</AnonymousTemplate>
<RoleGroups>
<asp:RoleGroup Roles="Developer">
<ContentTemplate>
Welcome back! You are a Developer, I can
tell by your svelte figure and impeccable
social skills!
</ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Administrator">
<ContentTemplate>
We all bow down to our system adminstrators!
</ContentTemplate>
</asp:RoleGroup>
</RoleGroups>
<LoggedInTemplate>
You are logged in!! But, wait, you are not
a member of any roles.
</LoggedInTemplate>
</asp:LoginView>
當訪(fǎng)問(wèn)者登錄頁(yè)面時(shí),如果有對應其角色的模板,就顯示該模板里的內容.
如果你以Developer角色登錄的話(huà),你就會(huì )看到Developer模板的內容,而不是LoggedInTemplate模板的內容.類(lèi)似的,如果你屬于Developer 和 Administrator角色, 你將會(huì )看到看到模板列表里第一個(gè)吻合的模板(就本例而言,為Developer)
結語(yǔ):
就像我們在本文看到的那樣,ASP.NET 2.0支持roles和membership services 。它們都使用provider model模式。都包含很多方法來(lái)執行相應的任務(wù).都可以通過(guò)ASP.NET Website管理工具進(jìn)行配置.
激活了roles功能后,我們一個(gè)通過(guò)編程或使用Website管理工具手工的創(chuàng )建角色或向角色賦值.ASP.NET允許基于角色的驗證規則.所以你可以對各種文件和文件夾設置訪(fǎng)問(wèn)權限.最后,LoginView控件提供了基于角色的模板,那意味著(zhù)可以根據角色的不同顯示不同的內容.
祝編程快樂(lè )!
考察 ASP.NET 2.0的Membership, Roles, and Profile - Part 3導言:
默認情況下,ASP.NET使用的membership 和 roles 分別使用的是SqlMembershipProvider 和 SqlRoleProvider.它們將membership和roles信息存儲在一個(gè)SQL Server數據庫里. 具體來(lái)說(shuō),信息存儲在一個(gè)預先定義好了的表里,并可以通過(guò)一些預定義的存儲過(guò)程來(lái)進(jìn)行訪(fǎng)問(wèn).為了在你的應用程序里通過(guò)默認的provider來(lái)使用membership 或 roles,我們需要將這些預定義的構造應用到你的應用程序里.
如果你沒(méi)有數據庫,還好ASP.NET可以在A(yíng)pp_Data文件夾里創(chuàng )建一個(gè)SQL Server 2005 Express版本的數據庫以包含必需的數據結構(schema).開(kāi)發(fā)者經(jīng)常要面對這種情況——手動(dòng)將ASP.NET 2.0的 membership和roles特性添加到一個(gè)現有的數據模式(data model)。比如,你的應用程序目前還不支持membership 或 roles services,但是需要網(wǎng)站的某些部分對認證用戶(hù)可見(jiàn);或對屬于某種角色的用戶(hù)限制其功能.這時(shí),我們一個(gè)通過(guò)the ASP.NET SQL Server注冊工具(aspnet_regsql.exe)使一個(gè)現有的數據庫支持membership 和 roles.
在本文,我們將考察如何用該工具對SqlMembershipProvider 好 SqlRoleProvider providers應用membership 和 roles services.該工具可以通過(guò)命令行或圖形界面來(lái)運行.另外,我們還將看如何對App_Data文件夾里的現有的SQL Server 2005 Express數據庫運用該工具.
如Part 1所述,使用ASP.NET Website管理工具(AWAT)可以在A(yíng)pp_Data文件夾里創(chuàng )建一個(gè)名為ASPNETDB.MDF的數據庫.有可能有這種網(wǎng)站,其有2個(gè)數據庫,一個(gè)是ASPNETDB.MDF數據庫,一個(gè)是應用程序自己使用的數據庫.當涉及到與membership和roles有關(guān)的查詢(xún)時(shí),比如驗證用戶(hù)的身份,或創(chuàng )建一個(gè)新的用戶(hù)帳戶(hù)等,就使用ASPNETDB.MDF數據庫;而網(wǎng)站頁(yè)面依然使用原來(lái)的數據庫.下面的圖闡明了這種關(guān)系.
最理想的是將與membership相關(guān)的數據結構(schema),轉移到應用程序現有的數據庫,所有的查詢(xún)都要使用現有的數據庫.如下圖:
不過(guò),我們一定要這么做嗎?為什么不能使用2個(gè)數據庫,一個(gè)處理membership services另一個(gè)處理現有程序的工作?然而不管怎么樣我依然建議將這2者合并起來(lái).
打個(gè)比方,設想你有一個(gè)相薄程序,目前只有你才能添加相片。你可以運用ASP.NET 2.0的 membership功能,只允許認證用戶(hù)才能訪(fǎng)問(wèn)該圖片,另外,你還可以為你或你的配偶創(chuàng )建1到2個(gè)用戶(hù)帳戶(hù)。在這種情況,將membership schema添加到現有的數據庫是比較理想的.另外,如果你允許訪(fǎng)問(wèn)者創(chuàng )建自己的帳戶(hù),并對圖片發(fā)表評論的話(huà),你可以在現有數據庫創(chuàng )建一個(gè)Comments表,該表引用aspnet_Users表的一個(gè)外鍵,以便顯示是那個(gè)人發(fā)表的評論.
將Membership相關(guān)的Schema應用到一個(gè)現有的數據庫
要完成這個(gè)工作,使用ASP.NET SQL Server注冊工具(aspnet_regsql.exe)是很容易辦到的.在命令行里導航到%WINDOWS%\Microsoft.NET\Framework\v2.0.50727,再運行aspnet_regsql.exe這將開(kāi)啟ASP.NET SQL Server Setup Wizard向導,在向導里你要么為application services配置一個(gè)SQL Server數據庫(也就是添加membership相關(guān)的schema),要么從數據庫轉移出schema,如下圖:
在接下來(lái)的界面,你要選擇一個(gè)數據庫來(lái)添加或(轉移入)與membership相關(guān)的schema.如下圖:
指定了數據庫后,點(diǎn)Next預覽將要做的改動(dòng),預覽完后點(diǎn)Next完成schema的配置。這將為membership providers添加必要的表、視圖、存儲過(guò)程.
ASP.NET SQL Server注冊工具所要做的是將%WINDOWS%\Microsoft.NET\Framework\v2.0.50727\InstallCommon.sql創(chuàng )建的腳本稍加修改后運行.而%WINDOWS%\Microsoft.NET\Framework\v2.0.50727\InstallCommon.sql的腳本將嘗試連接名為aspnetdb的數據庫.它將建立所有與membership相關(guān)的服務(wù): Membership, Roles, Profile, Personalization等等.更多的腳本見(jiàn)%WINDOWS%\Microsoft.NET\Framework\v2.0.50727\目錄.
腳本嘗試連接具體的數據庫(要么是aspnetdb數據庫,要么是在向導里指定的數據庫)。如果它不能在server's sysdatabases table表里找到該數據庫的話(huà), 那么就將根據指定的名稱(chēng)創(chuàng )建數據庫,再添加各種表、視圖、存儲過(guò)程.
從命令行運行ASP.NET SQL Server注冊工具
除了圖形界面以外,我們也可以從命令行調用ASP.NET SQL Server注冊工具。導航到 %WINDOWS%\Microsoft.NET\Framework\v2.0.50727,運行:
-- To use Windows Authentication (i.e., a "trusted connection"), use:
aspnet_regsql.exe -S <server> -E -d <database> -A all
-- To use SQL Server credentials (a UserID and Password), use:
aspnet_regsql.exe -S <server> -U <login id> -P <password> -d <database> -A all
其中-A all將安裝與membership相關(guān)的所有服務(wù)。你也可以用-A list來(lái)安裝某個(gè)具體的membership特性,比如-A mr將安裝membership和roles.運行aspnet_regsql.exe -? 將查看所有選項的全部清單.
如果你的應用程序現有的數據庫為App_Data文件夾里的一個(gè)SQL Server 2005 Express Edition版本的數據庫.你也許想知道如何正確的通過(guò)ASP.NET SQL Server注冊工具來(lái)訪(fǎng)問(wèn)它. 那么你如何處理server name, credentials,以及database name呢?首先聲明我不是專(zhuān)家,下面的意見(jiàn)為我自己認識到的:
.Server name為localhost\InstanceName——默認時(shí)SQL Server 2005 Express Edition的InstanceName為SQLExpress,因此使用的Server name就為localhost\SQLExpress.
.SQL Server 2005 Express Edition默認安裝為接受Windows Authentication.所以將authentication項設置為Windows Authentication.
.database name有點(diǎn)棘手.就我所知,當在A(yíng)pp_Data數據庫里創(chuàng )建一個(gè)SQL Server 2005 Express Edition數據庫時(shí),那么數據庫名為其完整的路徑,比如如果數據庫完整路徑為C:\Home\Websites\PhotoAlbum\App_Data\Photos.mdf,那么其數據庫名也為C:\Home\Websites\PhotoAlbum\App_Data\Photos.mdf. 不過(guò)我們需要將其添加為SQL Server 2005 Express Edition實(shí)例,以便于A(yíng)SP.NET SQL Server注冊工具引用.
對現有的SQL Server 2005 Express Edition數據庫提供一個(gè)名稱(chēng),使用免費下載的SQL Server 2005 Management Studio Express Edition來(lái)做是很容易的.
如果你的電腦里安裝的是SQL Server 2005 Standard Edition或更高版本的話(huà),那么你也已經(jīng)裝好了SQL Server 2005的Management Studio 。那么你就無(wú)法再安裝Management Studio的Express Edition版本了.
如果你不能在連接屏幕通過(guò):localhost\SQLExpress (或者你安裝的其它目錄)來(lái)連接到SQL Server 2005 Express Edition數據庫實(shí)例的話(huà),
那么在Management Studio里,連接到一個(gè)database server后,在 Databases文件夾上點(diǎn)擊右鍵,選“Attach”,來(lái)分配數據庫.你然后可以選擇需要的數據庫。該數據庫的名稱(chēng)為初始(original)創(chuàng )建時(shí)的完整路徑名.我說(shuō)“初始”是因為如果你將該數據庫文件從一個(gè)目錄移動(dòng)到另一個(gè)目錄的話(huà),它的名稱(chēng)依然是最初創(chuàng )建時(shí)的完整路徑.要改變數據庫的名稱(chēng),可以點(diǎn)“Script” 按鈕,再重命名。還一個(gè)方法,在Management Studio里,在該數據庫上右鍵單擊,選“Rename”.
完成添加后,我們就可以用ASP.NET SQL Server注冊工具來(lái)引用它.如果你不想用Management Studio來(lái)進(jìn)行添加,那么請參閱文章《Working With SQL Server 2005 Express Databases》(
http://scottonwriting.net/sowblog/posts/5480.aspx)
讓Membership Providers使用應用程序現有的數據庫
當將與membership相關(guān)的schema轉移到應用程序現有的數據庫后,我們將從現有數據庫進(jìn)行membership相關(guān)的查詢(xún),而不是從ASPNETDB.MDF數據庫.然而SQL Server Membership的幾個(gè)provider——SqlMembershipProvider, SqlRoleProvider, SqlProfileProvider等都默認使用ASPNETDB.MDF數據庫.為此,我們要在Web.config文件里添加我們自己的provider實(shí)例以使用現有的數據庫,并作為默認的Membership providers.
就像在Part 1探討的那樣,我們可以在Web.config文件對provider信息進(jìn)行配置,使membership providers和roles providers調用現有的數據庫而不是ASPNETDB.MDF,如下:
<configuration>
<connectionStrings>
<add name="MyDB" connectionString="..." />
</connectionStrings>
<system.web>
... authentication & authorization settings ...
<roleManager enabled="true"
defaultProvider="CustomizedRoleProvider">
<providers>
<add name="CustomizedRoleProvider"
type="System.Web.Security.SqlRoleProvider"
connectionStringName="MyDB" />
</providers>
</roleManager>
<membership defaultProvider="CustomizedMembershipProvider">
<providers>
<add name="CustomizedMembershipProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="MyDB" />
</providers>
</membership>
</system.web>
</configuration>
以上設置為membership 和 roles systems創(chuàng )建了一個(gè)用戶(hù)自定義providers,用到的連接字符串為“MyDB”,它指向了應用程序里現有的數據庫.
結語(yǔ):
當對程序啟動(dòng)membership支持后,我們需要將membership schema應用到一個(gè)現有的數據庫上,該工作可以通過(guò)ASP.NET SQL Server注冊工具來(lái)完成。完成之后,最后一步是對provider進(jìn)行定制,以使用現有的數據庫而不是默認的ASPNETDB.MDF
祝編程快樂(lè )!
考察 ASP.NET 2.0的Membership, Roles, and Profile - Part 4導言:
ASP.NET 2.0 的Membership class類(lèi)有一個(gè)ValidateUser(userName, password)方法,返回一個(gè)布爾值。用于驗證用戶(hù)的登錄信息是否有效.該方法被Login Web控件自動(dòng)調用,當然如果需要的話(huà)也可以通過(guò)編程調用.在Membership system,有幾種情況,用戶(hù)無(wú)法通過(guò)驗證:
1.username無(wú)效
2.username有效,但password無(wú)效
3.username和password都有效,但
.用戶(hù)可能還未被核準(approved)
.用戶(hù)被鎖定(locked out),原因可能是用戶(hù)用無(wú)效密碼登錄了好幾次( 默認為5次)
不幸的是,如果登錄失敗,ValidateUser(userName, password)方法只會(huì )返回一個(gè)False,而不包含為什么登錄失敗的具體原因.對Login控件而言,當ValidateUser(userName, password)方法返回False的同時(shí),默認還會(huì )顯示一個(gè)消息:"Your login attempt was not successful. Please try again." 如果是這種情況,用戶(hù)被鎖定(locked out)或帳戶(hù)還未被核準——此時(shí)他們的username和password都沒(méi)問(wèn)題,顯示這樣的消息就讓用戶(hù)感到困惑了.在本文,我們將在登錄過(guò)程中添加額外的反饋信息,以避免這種尷尬的情況.
審核和鎖定(Locked Out)用戶(hù)帳戶(hù)
ASP.NET 2.0 Membership system里的用戶(hù)帳戶(hù),我們可以利用Membership 和 MembershipUser classes類(lèi)以編程的方式訪(fǎng)問(wèn)并進(jìn)行修改.Membership包含一系列的方法以返回某一個(gè)或所有用戶(hù)的信息、更新某個(gè)用戶(hù)的信息等;而MembershipUser class類(lèi)包含的屬性描述了某個(gè)用戶(hù)的具體情況(UserName, Email, LastLoggedOnDate等等).
Membership system可以將用戶(hù)帳戶(hù)標記為inactive(未審批)和locked out.當創(chuàng )建一個(gè)新賬號時(shí),默認是被審批的.然后在某些情況下,你可能希望讓管理員手動(dòng)批準新帳戶(hù),或者是其它自動(dòng)方式來(lái)審批(比如,點(diǎn)擊一個(gè)確認連接發(fā)送郵件).不管是哪種方式,剛創(chuàng )建的用戶(hù)都會(huì )被標記為inactive.這樣,該用戶(hù)就無(wú)法登錄網(wǎng)站,因為ValidateUser(userName, password)總是返回False,無(wú)論用戶(hù)名和密碼是否正確.
由于驗證過(guò)程是通過(guò)一個(gè)基于窗體的構架(forms-based scheme),只是簡(jiǎn)單的通過(guò)一個(gè)HTTP request來(lái)發(fā)送用戶(hù)認證(credential).這樣,某個(gè)攻擊者便可以嘗試破解某個(gè)用戶(hù)帳戶(hù),方法是編寫(xiě)腳本遍歷一個(gè)通用密碼字典(a dictionary of common passwords),對某個(gè)特定的帳戶(hù),用字典里的所有密碼嘗試進(jìn)行登錄.為阻止這種攻擊,如果在特定時(shí)間段內用無(wú)效密碼登錄了特定次數后,Membership system自動(dòng)將某個(gè)用戶(hù)鎖定.默認設置為在10分鐘內(a ten minute window)無(wú)效登錄5次.當然,我們可以在Web.config文件里對其進(jìn)行定制.和未審批用戶(hù)一樣,被鎖定的用戶(hù)同樣不能登錄網(wǎng)站,無(wú)論其用戶(hù)名和密碼是否正確.為了對用戶(hù)進(jìn)行解鎖,需要調用MembershipUser class類(lèi)的UnlockUser()方法.當從Login Web控件登錄失敗時(shí),我們可以對登錄頁(yè)面進(jìn)行定制以顯示更多恰當的消息.
登錄失敗時(shí)顯示更有價(jià)值的消息
當用戶(hù)用Login Web控件進(jìn)行登錄時(shí)就會(huì )觸發(fā)其LoginError事件.該事件不會(huì )傳遞指明登錄失敗的任何信息.不過(guò),我們可以通過(guò)Login控件的UserName 和 Password屬性來(lái)獲取該用戶(hù)的username 和 password.我們可以調用Membership.GetUser(userName)方法來(lái)獲取用戶(hù)帳戶(hù)的信息. 該方法返回一個(gè)MembershipUser對象,我們可以檢查其IsApproved 和 IsLockedOut屬性來(lái)判定為何用戶(hù)的認證被認為是無(wú)效的.
下面的代碼顯示了如何來(lái)實(shí)現它.最終的幫助信息顯示在一個(gè)名為L(cháng)oginErrorDetails的Label Web控件里.你在本文的下載代碼里可看到Login頁(yè)面的完整代碼和聲明.
Protected Sub Login1_LoginError(ByVal sender As Object, ByVal e As System.EventArgs) Handles
Login1.LoginError
'There was a problem logging in the user
'See if this user exists in the database
Dim userInfo As MembershipUser = Membership.GetUser(Login1.UserName)
If userInfo Is Nothing Then
'The user entered an invalid username...
LoginErrorDetails.Text = "There is no user in the database with the username " &
Login1.UserName
Else
'See if the user is locked out or not approved
If Not userInfo.IsApproved Then
LoginErrorDetails.Text = "Your account has not yet been approved by the site's
administrators. Please try again later..."
ElseIf userInfo.IsLockedOut Then
LoginErrorDetails.Text = "Your account has been locked out because of a maximum
number of incorrect login attempts. You will NOT be able to login until you contact a site
administrator and have your account unlocked."
Else
'The password was incorrect (don't show anything, the Login control already describes
the problem)
LoginErrorDetails.Text = String.Empty
End If
End If
End Sub
使用上面的代碼,用戶(hù)登錄失敗后將看到更有意義的消息.下面的截屏顯示的是以Bruce(其帳戶(hù)已被鎖定)和Alfred(其帳戶(hù)還未被審批)的名字登錄的情形.沒(méi)有上述的事件處理器,他們都只能看到標準的"Your login attempt was not successful. Please try again." 提示消息.很明顯,他們將感到很迷惑,無(wú)法意識到其帳戶(hù)已經(jīng)被鎖定或未被審批.
由于A(yíng)SP.NET 2.0 Membership system可以鎖定用戶(hù)帳戶(hù)并不再接受登錄.如此情況,可提供一個(gè)快速反饋,看哪些用戶(hù)被鎖定,哪些用戶(hù)未被審批.要捕獲這些信息,我在membership數據庫里創(chuàng )建了一個(gè)InvalidCredentialsLog表,其構架如下:
Membership system包含一個(gè)ApplicationID,它允許多個(gè)應用程序在一個(gè)數據庫里存儲各自的用戶(hù)帳戶(hù).理想情況下,該表應包含ApplicationID,并只報告當前應用程序的那些無(wú)效認證(假定你用一個(gè)單一的membership store來(lái)應對多個(gè)應用程序).我將其作為練習留給讀者.
接下來(lái),我將創(chuàng )建一個(gè)存儲過(guò)程——InvalidCredentialsLog_Insert,它存儲用戶(hù)的username, password,和IP address.它檢查當前username是否與aspnet_Users表里的用戶(hù)匹配,若是,則提取用戶(hù)的IsApproved 和 IsLockedOut列的值,然后添加到InvalidCredentialsLog_Insert表.
當用戶(hù)無(wú)效登錄時(shí),需要調用該存儲過(guò)程,并傳入用戶(hù)信息.為此,我創(chuàng )建了一個(gè)ID為InvalidCredentialsLogDataSource的SqlDataSource控件,設置其調用該存儲過(guò)程,然后我們對Login Web控件的LoginError事件處理器進(jìn)行擴充,為該存儲過(guò)程設置參數并調用它:
Protected Sub Login1_LoginError(ByVal sender As Object, ByVal e As System.EventArgs) Handles
Login1.LoginError
'Set the parameters for InvalidCredentialsLogDataSource
InvalidCredentialsLogDataSource.InsertParameters("ApplicationName").DefaultValue =
Membership.ApplicationName
InvalidCredentialsLogDataSource.InsertParameters("UserName").DefaultValue = Login1.UserName
InvalidCredentialsLogDataSource.InsertParameters("IPAddress").DefaultValue =
Request.UserHostAddress
'The password is only supplied if the user enters an invalid username or invalid password -
set it to Nothing, by default
InvalidCredentialsLogDataSource.InsertParameters("Password").DefaultValue = Nothing
'There was a problem logging in the user
'See if this user exists in the database
Dim userInfo As MembershipUser = Membership.GetUser(Login1.UserName)
If userInfo Is Nothing Then
'The user entered an invalid username...
LoginErrorDetails.Text = "There is no user in the database with the username " &
Login1.UserName
'The password is only supplied if the user enters an invalid username or invalid password
InvalidCredentialsLogDataSource.InsertParameters("Password").DefaultValue =
Login1.Password
Else
'See if the user is locked out or not approved
If Not userInfo.IsApproved Then
LoginErrorDetails.Text = "Your account has not yet been approved by the site's
administrators. Please try again later..."
ElseIf userInfo.IsLockedOut Then
LoginErrorDetails.Text = "Your account has been locked out because of a maximum
number of incorrect login attempts. You will NOT be able to login until you contact a site
administrator and have your account unlocked."
Else
'The password was incorrect (don't show anything, the Login control already describes
the problem)
LoginErrorDetails.Text = String.Empty
'The password is only supplied if the user enters an invalid username or invalid
password
InvalidCredentialsLogDataSource.InsertParameters("Password").DefaultValue =
Login1.Password
End If
End If
'Add a new record to the InvalidCredentialsLog table
InvalidCredentialsLogDataSource.Insert()
End Sub
此外,我還構建了一個(gè)報告頁(yè)面,在一個(gè)可分頁(yè)、排序的GridView控件.如下所示,該頁(yè)面也包含在下載內容里
就一般的網(wǎng)站而言,該InvalidCredentialsLog表可能會(huì )很大。我們可以采取一些措施來(lái)將超過(guò)某個(gè)時(shí)間的老的記錄刪除(比如3個(gè)月前的記錄)。上面顯示出來(lái)的報告很簡(jiǎn)單,對大數據的結果也沒(méi)有進(jìn)行優(yōu)化處理.它使用默認的分頁(yè),從數據庫將每個(gè)頁(yè)面的數據都檢索出來(lái)。如果想自定義分頁(yè),僅僅檢索當前頁(yè)面所需要的記錄,請參閱文章《Custom Paging in ASP.NET 2.0 with SQL Server 2005》(
http://aspnet.4guysfromrolla.com/articles/031506-1.aspx)
結語(yǔ):
本文,我們看如何對登錄過(guò)程進(jìn)行優(yōu)化,當被鎖定或未為被審批的用戶(hù)登錄時(shí)顯示更有意義的信息.這都要感謝Membership API,它可以通過(guò)編程、顯式地(通過(guò)數據源控件)、或Web控件(比如Login Web控件)來(lái)訪(fǎng)問(wèn).在下載代碼里有一個(gè)Admin頁(yè)面,它用一個(gè)GridView控件將系統的所有用戶(hù)列出來(lái),允許用戶(hù)查看其審批狀態(tài)、將鎖定的用戶(hù)解鎖、查看某人用戶(hù)是否是Administrator角色(只有Administrator角色才能查看與管理相關(guān)的頁(yè)面)
祝編程快樂(lè )!
考察ASP.NET 2.0中的Membership, Roles, and Profile - Part 5導言:
我們知道ASP.NET 2.0通過(guò)membership, roles,profile systems來(lái)創(chuàng )建和管理用戶(hù)帳戶(hù)。要為用戶(hù)提供登錄頁(yè)面的話(huà),我們只需要拖一個(gè)Login Web控件到頁(yè)面即可.但如果我們想做一些用戶(hù)定制呢?我們可以重新配置Login控件,再另外添加一些內容;或者出除了用戶(hù)名和密碼外,我們還希望用戶(hù)提供email地址等,或者包含一個(gè)CAPTCHA(一些box,其text的背景為圖片).可以通過(guò)多種方式來(lái)對Login Web控件進(jìn)行定制.比如,是否顯示"Remember me next time", 顏色、字體等設置;我們還可以將控件轉換成一個(gè)模板(template);我們還可以為Authenticate event事件創(chuàng )建處理器,自定義認證邏輯,甚至使用CAPTCHA作為認證的一部分。
本文,我們將考察如何通過(guò)Login控件的屬性、通過(guò)模板來(lái)對其進(jìn)行定制,通過(guò)Authentication事件處理器定制認證邏輯.
通過(guò)屬性定制Login控件
首先我們要創(chuàng )建一個(gè)登錄頁(yè)面,如果用戶(hù)嘗試訪(fǎng)問(wèn)其未被授權的頁(yè)面或點(diǎn)擊LoginStatus控件的Login按鈕時(shí)就會(huì )自動(dòng)的重新定位到該頁(yè)面。默認情況下,該頁(yè)面必須命名為L(cháng)ogin.aspx,并放在根目錄下.當然如果你喜歡的話(huà)也可以使用其它的登錄URL,但是你需要在Web.config文件里的<authentication>節點(diǎn)里更新<forms>元素:
<?xml version="1.0"?>
<configuration xmlns="
<system.web>
...
<authentication mode="Forms">
<forms loginUrl="LoginPath" />
</authentication>
...
</system.web>
</configuration>
從工具欄拖該Login控件到我們創(chuàng )建的登錄頁(yè)面上,如下圖所示,Login控件默認包含如下的內容:
. 一個(gè)User Name TextBox
. 一個(gè)Password TextBox
. 一個(gè)"Remember me next time" CheckBox
. 一個(gè)"Log In" Button
此外,Login控件還有RequiredFieldValidators控件,以確保用戶(hù)輸入的User Name和Password不為空。另外,還有一個(gè)Literal控件用來(lái)顯示Login控件的FailureText屬性的值.
可以通過(guò)各種與style相關(guān)的屬性來(lái)美好Login控件的外觀(guān)。比如Font, BackColor, Width, Height等等.另外還可以通過(guò)TextBoxStyle, CheckBoxStyle,和LoginButtonStyle來(lái)設置Login控件內部的TextBoxes, CheckBox,和Button Web控件.
我們還可以改變其屬性。比如,想將 "User Name:"文本換成別的嗎?改動(dòng)UserNameLabelText屬性即可;想在添加說(shuō)明嗎?在InstructionText屬性設置即可;通過(guò)LoginButtonText屬性改變"Log In" Button的文本.通過(guò)HelpPageText 和 HelpPageUrl屬性來(lái)添加一個(gè)定位到幫助頁(yè)面的鏈接.而Orientation屬性決定了Login控件如何布局.下圖以及下載內容包含的Login控件演示了如何改變相關(guān)屬性的值來(lái)改變其外觀(guān)。
以上只是進(jìn)行了一定程度的定制,要想進(jìn)行更大程度的定制,我們必須將其轉變成一個(gè)模板。為此,切換到設計模式,在智能標簽里點(diǎn)"Convert to Template" 項。這將在Login控件的聲明代碼里添加一個(gè) <LayoutTemplate>,包含一個(gè)HTML <table> ,其構造復制了默認的Login控件的布局.你想怎么調整布局都行,包括添加額外的內容和Web控件.
有一點(diǎn)很重要要記住,在模板里的某些登錄控件必須保留其原有的ID值。具體來(lái)說(shuō),<LayoutTemplate>里有2個(gè)控件的ID值為UserName和 Password,它們構成了ITextControl界面.另外,你可以使用你個(gè)人的自定義服務(wù)器控件或User Control,只要它們也可以執行這種界面.另外,你需要一個(gè)Button, LinkButton, 或ImageButton,其CommandName要設置為L(cháng)ogin控件的LoginButtonCommandName靜態(tài)域(默認為"Login"),但其ID卻可以是任意值.Login控件轉變成模板時(shí)自動(dòng)生成的其它控件都是可選的.
下面的聲明代碼是我對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>
心細的讀者可能已經(jīng)注意到上面的布局忽略了FailureText Literal控件,該控件在用戶(hù)登錄失敗時(shí)顯示Login控件FailureText屬性的值。我決定在一個(gè)客戶(hù)端alert box里顯示該文本。為此,我將為L(cháng)ogin控件的 LoginError事件創(chuàng )建處理器,編寫(xiě)必要的JavaScript來(lái)展示該alert box。(在Part 4我們探討過(guò)該事件)
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屬性做上述設置后,一旦輸入有誤(也就是說(shuō)沒(méi)有輸入username 或password)時(shí)將顯示一個(gè)客戶(hù)端的alert box.下面的一個(gè)圖,alert box顯示用戶(hù)必須輸入username;而第二個(gè)圖片,用戶(hù)嘗試登錄,但輸入有誤.
定制認證邏輯
當用戶(hù)輸入完并點(diǎn)擊"Log In"按鈕時(shí),緊接著(zhù)發(fā)生如下的步驟:
1.發(fā)生頁(yè)面回傳
2.Login控件的LoggingIn事件觸發(fā)
3.Login控件的Authenticate事件觸發(fā);它調用一個(gè)事件處理器以執行必要的邏輯,判斷用戶(hù)認證是否有效,若有 效則該事件處理器將傳入的AuthenticateEventArgs對象的Authenticated屬性設為T(mén)rue.
4.如果用戶(hù)認證失敗將引發(fā)LoginError事件,反之將引發(fā)LoggedIn事件
默認,Login控件在內部處理Authenticate事件,通過(guò)調用Membership class類(lèi)的ValidateUser(username, password)方法來(lái)進(jìn)行驗證。然而,我們可以對Login控件使用的驗證邏輯進(jìn)行定制,我們可以自己創(chuàng )建該事件的事件處理器,如果通過(guò)驗證就將e.Authenticated屬性設為T(mén)rue;而未通過(guò)就設為False.
本例,我們不僅要驗證username 和 password,而且還有其email地址,以及一個(gè)CAPTCHA.我們知道CAPTCHA是一種人可以感知(而機器人程序無(wú)法感知)的技術(shù).CAPTCHA通常為在一個(gè)圖片上顯示一些扭曲的文本,讓用戶(hù)輸入這些文本.計算機程序不能分析該圖片以及破解其中的文本,而只有人才可以輕松的做到。我用到的該CAPTCHA是eff Atwood的免費且優(yōu)秀ASP.NET CAPTCHA server control控件.
要創(chuàng )建一個(gè)用戶(hù)自定義驗證邏輯示例,我們先將一個(gè)標準的Login控件轉變?yōu)橐粋€(gè)模板,并添加一個(gè)名為Email的TextBox(附帶一個(gè)RequiredFieldValidator控件),以及一個(gè)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>
接下來(lái),我們將為Authenticate事件創(chuàng )建一個(gè)事件處理器.我們先檢查CAPTCHA是否有效,再用Membership.ValidateUser方法來(lái)確保用戶(hù)的username 和 password是有效的;再調用Membership.GetUser(username)方法,將Email屬性與用戶(hù)提供的做對比;如果有任何一項出錯,則將 e.Authenticated設為False,反之則設為T(mén)rue. 我對Login控件的FailureText屬性進(jìn)行了更新,顯示用戶(hù)無(wú)法登錄的更具體的信息.
注意我們在<LayoutTemplate>里用LoginControl.FindControl("ID")方法來(lái)訪(fǎng)問(wèn)控件(比如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控件現在包含了用戶(hù)的email以及一個(gè)CAPTCHA,如下所示,只有當用戶(hù)提供正確的username, password, email address,CAPTCHA后才能正確登錄.
結語(yǔ):
本文,我們看到了如何通過(guò)改變Login控件的配置或將其轉變?yōu)橐粋€(gè)模板,來(lái)定制其外觀(guān)和布局.當轉變?yōu)槟0搴笪覀兛梢蕴砑宇~外的Web控件,比如一個(gè)CAPTCHA.再為L(cháng)ogin控件的Authenticate事件創(chuàng )建一個(gè)事件處理器,我們可以自定義驗證邏輯.
祝編程快樂(lè )!
附錄:
名詞解釋?zhuān)篊APTCHA
(Completely Automated Public Turing test to tell Computers and Humans Apart ,全自動(dòng)區分計算機和人類(lèi)的圖靈測試)
CAPTCHA ——用以區分計算機和人類(lèi),在人機差別非常小的網(wǎng)絡(luò )上非常有效。它會(huì )生成一個(gè)隨機的圖片顯示給用戶(hù)。這個(gè)圖片含有一個(gè)不容易被計算機識別(OCR)的字符串,同時(shí)這個(gè)字符串在頁(yè)面的代碼及其他計算機可以獲取的地方被使用。如果表單提交的時(shí)候并不含有正確的字符串,系統就能夠確信輸入人員錄入錯誤或者不是一個(gè)真正的人在進(jìn)行錄入。
沿著(zhù)上述思路發(fā)展,除了圖象外,目前又出現了語(yǔ)音形式的驗證方法。
考察ASP.NET 2.0的Membership, Roles,Profile - Part 6導言:
除了用戶(hù)帳戶(hù)的username, passsword, email, security question和 answer等,在實(shí)際的程序中我們還可能添加額外的信息,比如我們可能需要用戶(hù)指定一個(gè)簽名、主頁(yè)URL、以及IM address等.
使用Membership model,有2種方法來(lái)將用戶(hù)帳戶(hù)與額外的信息聯(lián)系起來(lái).第一種,具有最大的靈活性也要做最多的準備工作——為額外信息創(chuàng )建自定義數據存儲.如果你使用的是SqlMembershipProvider,那就意味著(zhù)要創(chuàng )建一個(gè)額外的表,將aspnet_Users表的UserId值作為主鍵.以一個(gè)在線(xiàn)messageboard為例,該表可為forums_UserProfile,具有的列為UserId(將aspnet_Users表的UserId作為主鍵和外鍵)、HomepageUrl、 Signature、 IMAddress.
另外,我們還可以使用ASP.NET 2.0 Profile system來(lái)存儲用戶(hù)的具體信息.Profile system允許定義與用戶(hù)相關(guān)的屬性。一旦定義后,開(kāi)發(fā)者就可以通過(guò)編程來(lái)讀取和訪(fǎng)問(wèn)這些屬性的值.與Membership、Roles類(lèi)似,Profile system也是基于provider model,默認情況下,.NET Framework有一個(gè)SqlProfileProvider class類(lèi),它使用一個(gè)SQL Server數據庫表(aspnet_Profile)來(lái)作為其存儲備份(backing store).
在本文,我們將考察Profile system——如何定義用戶(hù)屬性以及如何從ASP.NET頁(yè)面來(lái)編程訪(fǎng)問(wèn)它們。同時(shí)我們還將看到.NET 2.0里的SqlProfileProvide使用自定義的profile provider。
概述Profile System
ASP.NET 2.0里面的Membership system設計來(lái)創(chuàng )建處理用戶(hù)帳戶(hù)的標準API.雖然Membership system有與用戶(hù)相關(guān)的核心屬性—username, password, email address等,但是我們還經(jīng)常需要獲取每個(gè)用戶(hù)的其它信息.而且每個(gè)程序之間的額外信息還彼此不同.
為此,微軟創(chuàng )建Profile system來(lái)處理這些額外的用戶(hù)屬性.Profile system允許將這些用戶(hù)屬性定義在Web.config文件并將其值存儲在某些數據存儲里.默認的Profile provider - SqlProfileProvider,將這些屬性值存儲在一個(gè)叫aspnet_Profile的SQL Server數據庫表里.
當使用Profile system時(shí),我們要記住其唯一的目的是作為一種定義一系列用戶(hù)屬性的方法.再通過(guò)某個(gè)具體的provider,將這些屬性值存儲在某些backing store里.
定義用戶(hù)屬性
用Profile system,我們必須要在Web.config文件里定義清楚.對每個(gè)屬性我們要指定其name, 數據類(lèi)型,以及數據應如何序列化(serialized).如下為4個(gè)serialization選項:
.ProviderSpecific(默認)—Profile provider要決定如何序列化屬性值.
.String—屬性值將轉換成一個(gè)字符串形式
.Xml—屬性值將轉換成一個(gè)XML形式
.Binary—屬性值將轉換成一個(gè)二進(jìn)制形式
你選擇哪種serialization方法取決于變量的類(lèi)型.如果允許將屬性值轉化為一個(gè)String,那么我們就要讓provider將數據序列化為一個(gè)String;對XML和Binary類(lèi)型,以此類(lèi)推.
定義在Profile system的屬性可為簡(jiǎn)單的標量類(lèi)型(scalar),進(jìn)而在聚合(grouped into)成復雜的類(lèi)型,或已存在的復雜類(lèi)型(例如一個(gè)類(lèi))。每個(gè)Profile屬性的names, types,和serialization都定義在<profile> 元素的<properties>節點(diǎn).比如,假象我們要用戶(hù)指定一個(gè)主頁(yè)的URL,我們可以添加如下的屬性:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
...
<profile defaultProvider="CustomProfileProvider" enabled="true">
<providers>
...
</providers>
<!-- Define the properties for Profile... -->
<properties>
<add name="HomepageUrl" type="String" serializeAs="String" />
...
</properties>
</profile>
</system.web>
</configuration>
<add>元素添加了一個(gè)名為HomepageUrl,類(lèi)型為String,serialized為String的屬性。除此之外我們還可以在<add>元素添加額外的屬性.
標量類(lèi)型的屬性可以通過(guò)使用<group>元素進(jìn)行聚合。比如,除了HomepageUrl屬性外,我們還想獲取用戶(hù)的實(shí)際位置、生日、選用的編程語(yǔ)言等.這些信息可以分別為3個(gè)標量值,也可以進(jìn)行聚合.如下的代碼顯示如何將這3個(gè)屬性值聚合為一個(gè)名為Bio的組.
<profile defaultProvider="CustomProfileProvider" enabled="true">
<providers>
...
</providers>
<!-- Define the properties for Profile... -->
<properties>
<add name="HomepageUrl" type="String" serializeAs="String" />
<group name="Bio">
<add name="BirthDate" type="DateTime" serializeAs="Xml" />
<add name="Location" type="String" />
<add name="ProgrammingLanguageOfChoice" type="ProgrammingLanguages" />
</group>
...
</properties>
</profile>
上述代碼里,BirthDate屬性(類(lèi)型為DateTime)被選作序列化為XML,而Location 和 ProgrammingLanguageOfChoice屬性未指定serializeAs屬性,那意味著(zhù)Profile provider將自行決定如何序列化這2個(gè)屬性.注意ProgrammingLanguageOfChoice屬性的類(lèi)型,該類(lèi)型是我們在A(yíng)pp_Code文件夾里創(chuàng )建的。代碼如下:
Public Enum ProgrammingLanguages As Integer
NoneSelected = 0
VB = 1
CSharp = 2
JSharp = 3
End Enum
此外,Profile屬性的類(lèi)型還可以為一個(gè)自定義類(lèi),在本文下載內容里,你將發(fā)現一個(gè)很簡(jiǎn)單的Address class類(lèi)(也是在A(yíng)pp_Code文件夾):
<Serializable()> _
Public Class Address
Public Address1 As String
Public Address2 As String
Public City As String
Public State As String
Public Zip As Integer
End Class
定義了上述類(lèi)后,我們可以添加一個(gè)Address類(lèi)型的屬性,如下:
<profile defaultProvider="CustomProfileProvider" enabled="true">
<providers>
...
</providers>
<!-- Define the properties for Profile... -->
<properties>
<add name="HomepageUrl" type="String" serializeAs="String" />
<group name="Bio">
<add name="BirthDate" type="DateTime" serializeAs="Xml" />
<add name="Location" type="String" />
<add name="ProgrammingLanguageOfChoice" type="ProgrammingLanguages" />
</group>
<add name="BillingAddress" type="Address" serializeAs="Xml" />
<add name="ShippingAddress" type="Address" serializeAs="String" />
</properties>
</profile>
上述代碼里,BillingAddress 和 ShippingAddress屬性都為Address類(lèi)型.
在代碼里處理Profile屬性
完成Profile屬性的定義后,ASP.NET引擎自動(dòng)地創(chuàng )建一個(gè)ProfileCommon class類(lèi),該類(lèi)包含的屬性與我們在Web.config文件里定義的屬性相匹配.當我們在Web.config文件里修改這些屬性后,該類(lèi)將重新創(chuàng )建并自動(dòng)處理當前登錄用戶(hù)的profile.(你也可以設置profile對匿名用戶(hù)的支持,不過(guò)這不是本文的主題,我們將在后面的文章探討).
可以在一個(gè)ASP.NET頁(yè)面里通過(guò)HttpContext對象的Profile屬性來(lái)訪(fǎng)問(wèn)ProfileCommon class類(lèi)。比如,要在一個(gè)ASP.NET頁(yè)面里讀取當前登錄用戶(hù)的HomepageUrl屬性,僅僅只需要使用Profile.HomepageUrl即可. 實(shí)際上,只要你輸入Profile再鍵入一個(gè)“.”智能感知功能將帶出各種屬性??釂??實(shí)際上聚合功能同樣工作地很好,輸入Profile再鍵入一個(gè)“.”,你將看到其中一個(gè)屬性為Bio.輸入Bio再鍵入一個(gè)“.”,那3個(gè)標量屬性就出現在智能感知界面里(BirthDate, Location, and ProgrammingLanguageOfChoice).
本文下載代碼里有3個(gè)頁(yè)面值得考察,第一個(gè)頁(yè)面為/UsersOnly/Default.aspx頁(yè)面,顯示了當前登錄用戶(hù)的Profile信息.第二個(gè)頁(yè)面為/UsersOnly/UpdateProfile.aspx,允許用戶(hù)更改其Profile數據.第三個(gè)頁(yè)面演示了如何使用Profile屬性的GetProfile("username")方法來(lái)獲取其它,非當前登錄用戶(hù)的Profile數據.頁(yè)面/UserList.aspx列出了系統里的所有用戶(hù)的membership信息(username, email等),以及HomepageUrl屬性值.
指定一個(gè)Profile Provider
如果你沒(méi)有顯式地指定一個(gè)Profile provider,那么默認使用的是SqlProfileProvider,它將用戶(hù)的property值存儲在aspnet_Profile表里,但效率比較低,該表的構架如下:
對于給定的記錄,PropertyNames列包含的是每個(gè)Profile屬性的名稱(chēng),根據其序列化形式的不同,將其值存儲在PropertyValuesString 或 PropertyValuesBinary列.如果是以String或 XML形式則存儲在PropertyValuesString列;如果是以Binary形式則在PropertyValuesBinary列.
就我們定義的Profile屬性而言,該某個(gè)用戶(hù)的PropertyNames列的值可能為:
Bio.ProgrammingLanguageOfChoice:S:0:92:HomepageUrl:S:92:30:ShippingAddress:B:0:212:...
如你所見(jiàn),每個(gè)property name的形式為PropertyName:B|S:StartIndex:Length,其中B 或 S指出了該屬性的值是存儲在PropertyValuesString(S)列 還是在 PropertyValuesBinary(B)列的.比如,Bio.ProgrammingLanguageOfChoice屬性的值存儲在PropertyValuesString (S)列,起始位置為0,長(cháng)度為92.而HomepageUrl屬性的值也存儲在PropertyValuesString (S)列,起始位置為92,長(cháng)度為30.
查看PropertyValuesString列,我們看到:
<?xml version="1.0" encoding="utf-16"?><ProgrammingLanguages>CSharp</ProgrammingLanguages>
http://www.datawebcontrols.com... ProgrammingLanguageOfChoice的值已經(jīng)被序列化為XML形式,開(kāi)始位置為0,長(cháng)度為92;同樣,HomepageUrl屬性開(kāi)始位置為92,長(cháng)度為30.(而ShippingAddress屬性為Binary形式,我們可以在PropertyValuesBinary列里找到其值,開(kāi)始位置為0.長(cháng)度為212)
SqlProfileProvider自動(dòng)的將Profile屬性遷移到數據庫.而SqlProfileProvider用到的表和存儲過(guò)程是在貫徹SqlMembershipProvider時(shí)自動(dòng)創(chuàng )建的(見(jiàn)Part 1).
SqlProfileProvider的局限性
由于SqlProfileProvider將所有的屬性值壓縮到一行的1到2列,每次對任何一個(gè)屬性值的讀取都要觸動(dòng)所有的屬性值;另外數據的格式也使的我們很難查詢(xún)和返回這些屬性信息。假如我們想知道有多少用戶(hù)使用C#(該信息存儲在PropertyValuesString列),我們首先需要將其數據格式轉換成正常的數據結構.
作為SqlProfileProvider的替代,我們可以使用微軟雇員Hao Kung開(kāi)發(fā)的免費的Table Profile Provider(
http://www.asp.net/downloads/sandbox/table-profile-provider-samples/)。該provider將每個(gè)Profile屬性存儲在各自的列里。另外,我們開(kāi)可以創(chuàng )建自己的Profile provider.
結語(yǔ):
除了username, password, email address這些基本的用戶(hù)信息外,如果你還想獲取更多的用戶(hù)信息,你要么使用自定義的解決方案(創(chuàng )建你自己的數據存儲,寫(xiě)代碼來(lái)對數據存儲進(jìn)行數據讀寫(xiě)),要么使用Profile system. 它允許頁(yè)面開(kāi)發(fā)者在Web.config文件里定義一系列的用戶(hù)屬性,這些信息自動(dòng)的轉換為一個(gè)類(lèi)(ProfileCommon),并可以通過(guò)HttpContext class類(lèi)的Profile屬性來(lái)進(jìn)行訪(fǎng)問(wèn).
ASP.NET 2.0默認的Profile provider為SqlProfileProvider,它將Profile屬性值存儲在一個(gè)SQL Server數據庫的aspnet_Profile表里.我們只需要通過(guò)Profile屬性來(lái)讀寫(xiě)用戶(hù)的Profile數據.
祝編程快樂(lè )!
考察ASP.NET 2.0的Membership, Roles, and Profile - Part 7導言:
在前6章我們考察了.NET Framework包括的那些provider——比如SqlMembershipProvider, SqlRoleProvider, SqlProfileProvider等.實(shí)際上
我們可以下載這些provider的源代碼,以及一個(gè)Provider工具包來(lái)創(chuàng )建我們自己的provider(
http://msdn2.microsoft.com/en-us/asp.net/aa336558.aspx),而Scott Guthrie在他的博客文章《ASP.NET 2.0 Membership, Roles, Forms Authentication, and Security
Resources》(
http://weblogs.asp.net/scottgu/archive/2006/02/24/ASP.NET-2.0-Membership_2C00_-Roles_2C00_-Forms-Authentication_2C00_-and-Security-Resources-.aspx)里列出了很多自定義的Membership, Roles, and Profile provider.
如果這些內置的provider無(wú)法滿(mǎn)足你的需要,你可以創(chuàng )建自己的provider.本文,我們將探討創(chuàng )建和使用一個(gè)自定義provider,然后用一個(gè)簡(jiǎn)單
的自定義provider將profile信息存儲到一個(gè)XML文件里.
Profile System的職責
像在Part 6探討的一樣,Profile System允許用戶(hù)在Web.config文件定義一系列的 "profile properties",然后在某些存儲備份(backing
store)里保存和讀取這些屬性值.
如果這些內置Profile providers不能滿(mǎn)足需要,你可以創(chuàng )建你自己的provider。創(chuàng )建一個(gè)用戶(hù)定制provider——不管是用于Profile,
Membership, Roles, ASP.NET site map,或是其他的什么子系統,都有遵循如下的步驟:
1.創(chuàng )建用戶(hù)定制provider——創(chuàng )建源自ProfileProvider class類(lèi)的一個(gè)類(lèi).
2.設置應用程序使用用戶(hù)定制provide——我們需要在Web.config文件里指定Profile system使用我們的定制provid而不是默認的
SqlProfileProvider。
微軟的Provider Toolkit web page頁(yè)面里有關(guān)于學(xué)習創(chuàng )建用戶(hù)定制provider的信息.對Profile system而言,有2個(gè)文件值得閱讀:
《Introduction to Profile Providers》——它介紹了Profile system的概念以及職責;而《ASP.NET 2.0's Profile Providers》——它更
直觀(guān)的介紹了 in ASP.NET 2.0里的SqlProfileProvider是如何貫徹的.
本文的剩下部分我們將探討我們自定義的XmlProfileProvider——將用戶(hù)信息裝入App_Data文件夾里的一個(gè)XML文件,完整代碼(以及簡(jiǎn)單的測
試程序)在本文的下載代碼里.
設計XmlProfileProvider
默認,Membership, Roles,Profile system的provider都被設計為多個(gè)應用程序存儲信息.明顯的例子就是aspnet_Applications表,每一個(gè)應用程序實(shí)例對應一條記錄.系統里的每一個(gè)用戶(hù)通過(guò)aspnet_Users表里的ApplicationId這個(gè)外鍵與某個(gè)應用程序聯(lián)系起來(lái).我決定將該功能應用到我們的profile provider里(對XmlProfileProvider來(lái)說(shuō),Membership 和 Roles providers都可以使用它).Profile數據默認時(shí)存儲在A(yíng)pp_Data文件夾里的一個(gè)文件里,以阻止用戶(hù)從瀏覽器直接請求這些文件(如果一個(gè)用戶(hù)試圖從瀏覽器訪(fǎng)問(wèn)
http://www.yoursite.com/App_Data/anyFile 的話(huà),ASP.NET會(huì )返回一個(gè)HTTP 404消息)。不過(guò)你也可以通過(guò) provider的profileFolder設置來(lái)指定XML文件存放在什么地方.
具體來(lái)說(shuō),Profile數據以如下形式進(jìn)行存儲:~/App_Data/XmlProfiles/applicationName/username.xml.每一個(gè)XML文件開(kāi)始為一個(gè)<profileData>根元素,而子元素就由定義在Web.config文件里的Profile屬性里決定了。
就像在Part 6探討了一樣,Profile屬性可以為strings, XML,或binary數據,對string類(lèi)型的屬性而言,XML文件將其保存為文本形式;如果是XML類(lèi)型則原封不動(dòng)的保存;如果是binary類(lèi)型的就保存為64位編碼的binary.假設我們在Web.config文件<properties>元素里定義了6個(gè)Profile屬性:
<profile defaultProvider="...">
<providers>
...
</providers>
<properties>
<add name="Birthdate" type="DateTime" serializeAs="String" />
<add name="HomepageUrl" type="String"/>
<add name="Signature" />
<add name="IncludeSignatureInPosts" type="Boolean"/>
<add name="BillingAddress" type="Address" serializeAs="Xml" />
<add name="ShippingAddress" type="Address" serializeAs="Binary" />
</properties>
</profile>
其中的Address類(lèi)型是定義在A(yíng)pp_Code文件夾里的一個(gè)簡(jiǎn)單類(lèi),包含了些標量屬性,比如:Address1, Address2, City等.注意:Profile屬性BillingAddress序列化為XML,而ShippingAddress為binary.
對于上面定義的Profile屬性,如果有個(gè)叫"Jisun"的用戶(hù)登錄我們的網(wǎng)站,使用Profile system,且應用程序的名稱(chēng)為"MyApp",那么將會(huì )創(chuàng )建一個(gè)~/App_Data/XmlProfiles/MyApp/Jisun.xml的文件,其包含的代碼可能為:
<?xml version="1.0" encoding="utf-8"?>
<profileData>
<IncludeSignatureInPosts>False</IncludeSignatureInPosts>
<Signature>What is life, but a seemingly random collection of 1s and 0s?</Signature>
<HomepageUrl>
http://www.datawebcontrols.com</HomepageUrl>
<Birthdate>1979-04-01</Birthdate>
<ShippingAddress><![CDATA[AAEAAAD/////AQAAAAAAAAAMAHBfQ29kZS5j...]]></ShippingAddress>
<BillingAddress>
<Address xmlns:xsi="
xmlns:xsd=" <Address1>123 Anywhere St.</Address1>
<Address2 />
<City>San Diego</City>
<State>CA</State>
<ZipCode>92109</ZipCode>
</Address>
</BillingAddress>
</profileData>
就像你看見(jiàn)的那樣,每個(gè)Profile屬性被處理成一個(gè)XML元素并作為<profileData>根元素的子元素. 對string類(lèi)型的屬性,比如IncludeSignatureInPosts, Signature, HomepageUrl,以及Birthdate,其值在XML元素里為文本值;對XML類(lèi)型的屬性(BillingAddress),其值在<BillingAddress>元素里保存為原始形式;binary類(lèi)型的屬性ShippingAddress被編碼為64位.
規劃XmlProfileProvider Class類(lèi)
當構建用戶(hù)定制Profile provider時(shí),我們要擴充System.Web.Profile.ProfileProvider class類(lèi).至少要包含2個(gè)方法——GetPropertyValues(context, collection) 和 SetPropertyValues(context, collection).這2個(gè)方法都接受2個(gè)輸入參數——context 和 collection. 其中context提供的信息為:用戶(hù)名以及該用戶(hù)是否經(jīng)過(guò)認證(ASP.NET支持匿名profile,不過(guò)這超出了本文的范圍,更多信息請參閱文章《Profiles section on the ASP.NET Quickstarts 》(http://dotnetjunkies.com/QuickStartv20/aspnet/doc/profile/default.aspx#anon));另外collection提供一系列的Profile屬性.
上面的GetPropertyValues方法返回所需Profile屬性的值,相反SetPropertyValues方法的目的是向backing store(就本例而言,為XML文件)寫(xiě)入屬性值.在ProfileProvider class類(lèi)里還有其它的類(lèi),不過(guò)我們可以創(chuàng )建僅僅執行這2個(gè)方法的自定義profile provider.
在你的Website里使用XmlProfileProvider自定義Provider
為了在你的應用程序里使用自定義的XmlProfileProvider,我們首先要在本文結尾處下載其代碼,將CustomProviders.dll放在你的web 應用程序的/bin目錄,再更新你的Web.config文件:
<profile defaultProvider="MyCustomProfileProvider">
<providers>
<clear />
<add name="MyCustomProfileProvider"
type="CustomProviders.XmlProfileProvider, CustomProviders"
applicationName="applicationName"
[profileFolder="baseFolderPathForProfileXMLFiles"] />
</providers>
<properties>
...
</properties>
</profile>
其中profileFolder屬性是可選的,如果忽略的話(huà),默認為~/App_Data/XmlProfiles/.
下載代碼里包含了一個(gè)測試的web應用程序,指導如何將自定義的XmlProfileProvider class類(lèi)與默認的SqlMembershipProvider class類(lèi)協(xié)調使用.
結語(yǔ):
在本文我們看到如何利用provider model來(lái)創(chuàng )建以用戶(hù)自定義的Profile provider,它將用戶(hù)屬性設置保存為XML文件而不是一個(gè)數據庫.
祝編程快樂(lè )!
考察ASP.NET 2.0的Membership, Roles,Profile - Part 8導言:
我最近遇到一個(gè)網(wǎng)站,其主要包含一些靜態(tài)的內容.在客戶(hù)端,需要一個(gè)頁(yè)面來(lái)從一個(gè)數據庫表里顯示數據.另外,還需要一個(gè)頁(yè)面來(lái)便于管理員添加、更新、刪除數據.用ASP.NET 2.0的數據源控件以及Membership system,該功能可以很容易的實(shí)現。不過(guò)有一個(gè)問(wèn)題,由于服務(wù)器不支持SQL Server數據庫,只有用Access數據庫來(lái)代替。問(wèn)題來(lái)了,.NET Framework BCL只包含了一個(gè)支持Microsoft SQL Server數據庫的Membership provider.
可喜的是,微軟提供了一個(gè)Access數據庫模板及在Membership, Roles,Profile里使用Access數據庫的provider.在本文我們將探討如何得到這些基于A(yíng)ccess的provider以及如何在A(yíng)SP.NET 2.0 web應用里使用它們.獲取并使用Access Provider
在A(yíng)SP.NET 2.0的Beta版本里,微軟為Membership, Roles, Profile設計的基于A(yíng)ccess和SQL Server數據庫的provider,并作為基本類(lèi)庫的一部分包含在.NET Framework里.到ASP.NET 2.0 正式發(fā)行的時(shí)候,微軟決定剔除內置的對Access數據庫的支持,而只支持Microsoft SQL Server 2005 Express Edition版本數據庫.但是不見(jiàn)得所有人都轉移到SQL Server 2005.很多web服務(wù)器公司就不支持Express Edition.其它人可能在此之前設計的是基于A(yíng)ccess數據庫的應用程序并使用的很好,要轉變到SQL Server的話(huà)可能困難重重.
還好,我們可以從微軟單獨下載到Access providers,其為一個(gè)Microsoft Visual Studio安裝文件(VSI),包含了C# class類(lèi)文件以及一個(gè)Access數據庫.你可以在這里:
http://download.microsoft.com/download/5/5/b/55bc291f-4316-4fd7-9269-dbf9edbaada8/SampleAccessProviders.vsi進(jìn)行下載,當然在本文的下載文件里也包含它.
我們必須采取以下3個(gè)步驟才能使用Access providers:
1.將Access Provider Classes類(lèi)應用到Web Application——下載內容里的C# class類(lèi)文件要么被編譯為一個(gè)文件并放到web應用程序的/bin目錄文件夾里,要么將這些類(lèi)文件拷貝到App_Code文件夾.
2.準備好數據庫——確保應用程序里有一個(gè)Access數據庫.
3.更新Web.config文件—必須對Web.config文件進(jìn)行設置以使應用程序為Membership, Roles,Profile調用Access providers而不是默認的SQL Server providers.
第一步:將Access Provider Classes類(lèi)應用到Web Application
下載完該VSI文件后,雙擊以進(jìn)行安裝過(guò)程,這將把文件安裝到恰當的文件夾(就我的電腦而言:My Documents\Visual Studio 2005\Templates\ProjectTemplates\Visual Web Developer\Starter Kits).在“New Projects”對話(huà)框里可以找到一個(gè)模板,如下面的截屏所示:
創(chuàng )建一個(gè)新工程,用該ASP.NET Access Providers模板創(chuàng )建一個(gè)Class Library對象.在此,最理想的是將Class Library對象進(jìn)行編譯得到一個(gè)文件,比如AccessProviders.dll.再將其拷貝到應用程序的/bin目錄下,這將允許你為Membership, Roles, Profile調用Access provider. 這是最理想的方法因此它便于部署和維護.
另外,你還可以將模板使用的C# class類(lèi)文件拷貝到應用程序的App_Code文件夾里.最簡(jiǎn)單的方法是從下載的VSI文件里將其抽出來(lái),在安裝向導的第一步里,選擇"View files in Windows Explorer",該VSI文件包含一個(gè)ZIP壓縮文件(ASP.NET Access Providers.zip),在它的Samples目錄下包含了必需的類(lèi).將這些類(lèi)拷貝進(jìn)App_Code文件夾.
第二步:準備好數據庫
該VSI文件包含了一個(gè)Microsoft Access數據庫(ASPNetDB.mdb),其包含了必需的表和視圖.我們要將該數據庫放在我們的應用程序里。我們可以在Windows資源管理器里查找到VSI里包含的Access數據庫;或者安裝完VSI后,我們也可以在硬盤(pán)里找到該數據庫.不管是哪種方式,你都要將這個(gè)ASPNetDB.mdb數據庫拷貝到App_Data文件夾.確保該文件沒(méi)有標明為只讀,這樣那些用戶(hù)帳戶(hù)才有對App_Data文件夾的讀寫(xiě)權限.
第三步:更新Web.config文件
最后我們要更新Web.config文件,以使Membership, Roles,Profile使用基于A(yíng)ccess的provider而不是默認的基于SQL Server的provider.我們也需要在<connectionStrings>節點(diǎn)引用該Access數據庫的路徑.
首先添加新的<connectionStrings>,假定你的數據庫還是ASPNetDB.mdb且放在A(yíng)pp_Data文件夾里,你可以使用如下的代碼:
接下來(lái)在<system.web>元素,添加必要的Membership, Roles,Profile元素.下面的聲明清除了默認的Membership provider設置,而將AccessMembershipProvider作為應用程序默認的Membership provider:
我們注意到<connectionStrings>節點(diǎn)用到的name與<add>元素里的connectionStringName屬性的值一樣(都是LocalAccessDatabase).如果你將類(lèi)編譯成一個(gè)文件并拷貝到/bin目錄的話(huà),type應該使用"Samples.AccessProviders.AccessMembershipProvider, AssemblyName";而如果你將類(lèi)文件拷貝到App_Code文件夾的話(huà),type應該使用 "Samples.AccessProviders.AccessMembershipProvider".
一旦完成上面3個(gè)步驟后,你就按同一種方式來(lái)使用Membership, Roles, Profile system就像是在使用默認的基于SQL Server的provider一樣(也不完全正確,見(jiàn)如下的警告以及VSI里的README文件)。這意味著(zhù)你可以使用Security Web控件——Login, LoginView, CreateUserWizard等.本文下載代碼里包含了一個(gè)簡(jiǎn)單的website,它的Membership 和 Roles功能調用的是Access providers.
關(guān)于使用Access-based Providers的警告
VSI里的README文件指出了使用Access Membership provider的缺點(diǎn),我這里再次重申:
1——在CreateUser 和 ChangePassword方法里并沒(méi)有強制性的對密碼長(cháng) 度做出要求,該設置可以進(jìn)行讀取但不能被provider使用.
2——不能在provider里執行帳戶(hù)鎖定,自然,UnlockUser方法也無(wú)效.同 時(shí)不能阻止帶有惡意的password 或password answer的企圖.
3——不支持創(chuàng )建帶有一個(gè)明顯UserId(比如providerUserKey)的新用戶(hù)
4——不支持創(chuàng )建檢索一個(gè)基于UserId(比如providerUserKey)的用戶(hù)
參見(jiàn)README文件以獲取更多的信息
結語(yǔ):
本文我們考察了適用于Membership, Roles,Profile的Access providers,它允許開(kāi)發(fā)者使用一個(gè)Access數據庫來(lái)作為Membership, Roles, 以及 Profile systems的存儲備份(backing store)
祝編程快樂(lè )!
下載示例程序