ASP.NET成員資格功能減少了您必須編寫的代碼數量,以便對用戶進行身份驗證並存儲其憑據。引用MSDN:
“ ASP.NET會員資格為您提供了一種內置的驗證和存儲用戶憑據的方式。您將ASP.NET成員資格與表單身份驗證和 /或ASP.NET登錄控件一起用於身份驗證用戶。”
需要在Web.config配置文件中指定會員提供商。您可以使用自己的自定義提供商或使用.NET Framework匯發的默認提供商之一,例如SQLMembersHippRovider提供商。
所有相關的數據都存儲在ASP.NET成員系統使用的一組表中。在大多數情況下,您每個Web應用程序都使用一組表。但是,您還可以重複使用相同的集合來存儲多個Web應用程序的用戶憑據。這有效地使您可以創建一個可作為門戶的Web應用程序,使您可以登錄這些“虛擬”應用程序之一。
但是,這是不可能的。在支持此類動態應用程序之前,您需要進行一些工作。
讓我們開始...
讓我們首先檢查以下web.config條目:
列表1 -Web.config asp.net成員資格配置
< membership defaultProvider = " SqlProvider " >
< providers >
< clear />
< add name = " SqlProvider "
type = " System.Web.Security.SqlMembershipProvider "
connectionStringName = " YourConnectionStringName "
applicationName = " MyApplication "
... />
</ providers >
</ membership >這些設置設置了您的Web應用程序,以使用SQLMembersHippRovider來管理您的用戶憑據。與您的用戶相關的所有數據都存儲在默認的ASP.NET成員表中。
評論:本文不是ASP.NET成員的入門,您應該熟悉它。使用ASP.NET SQL Server註冊工具(ASPNET_REGSQL.EXE)來設置數據庫。
如前所述,您可以將多個Web應用程序的用戶憑據存儲在同一表表中。每個Web應用程序通過在我所說的應用程序上下文中對其數據進行分區。此應用程序上下文由會員提供商的應用名稱屬性定義。如您在清單1中所見,應用程序名稱為“ myApplication”。
SQLMembersHippRovider使用此設置來確定應在哪個上下文中操作,所有與此應用程序名稱相關的用戶數據將由ASP.NET成員資格庫和登錄控件訪問。
不幸的是,此設置在web.config中靜態定義。您無法在運行時輕鬆更改它。如果您想支持動態應用程序,則需要一種控制提供商應用名稱屬性值的方法。解決方案在於創建您自己的ASP.NET會員資格提供商。
SQLMembersHippRovider從Web.config文件中包含的配置設置中檢索其應用名稱屬性的值。我們想更改此屬性返回的值的價值。
登錄用戶時,應明確他的意圖,換句話說,他必須指定他想訪問的虛擬應用程序。為此,我要求用戶不僅指定他的用戶名和密碼,還指定他要訪問的虛擬應用程序。從用戶的角度來看,我稱此虛擬應用程序為域。
例如,假設您已經建立了一個稱為ASPNETMEMBERHIP的SQL Server數據庫,該數據庫包含ASP.NET成員表。該數據庫包含以下在Aspnet_applications表中定義的虛擬應用程序:
每個虛擬應用程序都包含用戶名“ CGEERS”的用戶。現在,如果我想登錄Northwind應用程序,我必須將用戶名輸入“ Northwind cgeers”。對於AdventureWorks應用程序,這將是“ AdventureWorks cgeers”。
用戶名之前的零件是應用程序名稱,或者就用戶而言,這是他必須輸入的域,以指定他要訪問的虛擬應用程序。域和用戶名被後斜切隔開。
當用戶使用ASP.NET登錄控件登錄時,我們必須提取他輸入的域並將其存儲在我們的自定義會員提供者可以訪問的位置中。但是,我們必須考慮到ASP.NET在多線程環境中運行。 ASP.NET收到的每個請求均由單獨的線程處理。我們不能僅將域存儲在任何位置,其他傳入請求可能會覆蓋它。必須與單個請求聯繫在一起。
存儲與單個請求相關的上下文信息的理想場所是當前的HTTP上下文。此上下文信息由HTTPContext類封裝。
啟動Visual Studio並創建一個名為“ Aspnetdynamicapplications”的新空白解決方案。接下來,將一個新的類庫項目添加到名為“ cgeers.web.security”的解決方案中。將引用添加到System.configuration和System.Web組件中。我們的自定義提供商將從默認的SQLMembersHippRovider下降,因此需要這些參考。
圖1-視覺工作室解決方案

將自動生成的class1.cs文件重命名為dynamicApplicationssqlmembershipprovider.cs,並添加列表2中顯示的代碼。
清單2 -DynamIcapplicationssqlmembershipprovider
public class DynamicApplicationsSqlMembershipProvider : SqlMembershipProvider
{
#region Fields
private const string ApplicationNameSetting = "ApplicationName" ;
#endregion
public override string ApplicationName
{
get
{
HttpContext context = HttpContext . Current ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
string applicationName = String . Empty ;
if ( context . Items . Contains ( ApplicationNameSetting ) )
{
if ( ! String . IsNullOrEmpty ( ( string ) context . Items [ ApplicationNameSetting ] ) )
{
applicationName = ( string ) context . Items [ ApplicationNameSetting ] ;
}
}
return applicationName ;
}
set
{
base . ApplicationName = value ;
}
}
}如您所見,我們的自定義會員提供商從默認的SQLMembersHippRovider下降並覆蓋了應用程序名稱屬性。我們並不關心固定器,只關心getter。當提供商讀取ApplicationName屬性時,我們通過調用HTTPContext類的靜電當前屬性來獲取當前HTTP上下文的處理。獲得的HTTPContext對象具有稱為項目的密鑰/值收集屬性。該集合將應用程序名稱存儲在密鑰“ ApplicationName”標識的條目中。此鍵的名稱是您必須在設計時間確定的。
現在,每當我們的自定義會員提供商都需要確定調用應用程序名稱的應用名稱屬性,該應用程序名稱屬性從當前的HTTP上下文中檢索值。這在ASP.NET的多線程環境中起作用,因為每個請求都與其自己的HTTP上下文相關。
現在,我們是一個自定義會員提供商,它從Courte http上下文中檢索其應用名稱屬性的值,我們仍然必須找出一種將應用程序名稱實際存儲在HTTP上下文中的方法。
用戶第一次指定他要訪問的虛擬應用程序或<domain><username>是他登錄時。我們必須創建自己的登錄控件,該控件從ASP.NET登錄控件下降。用戶身份驗證,我們需要提取輸入的域並將其保存在當前的HTTP上下文中。
使用類庫模板添加一個新的項目,稱為“ cgeers.web.ui.webcontrols”的解決方案。添加以下對項目的引用:
接下來刪除自動生成的class1.cs文件,並添加一個名為DynamIcapplicationsLogin的新類。由於此類的代碼非常漫長,因此我選擇將其分解為幾個列表。在列出每個列表後,添加了代碼的簡要說明。
列表3 -DynamicApplicationsLogin控制私人屬性
public class DynamicApplicationsLogin : Login
{
#region Fields
private string _fullUserName ;
#endregion
#region Properties
private string ApplicationName
{
get
{
string [ ] data = base . UserName . Split ( @"" . ToCharArray ( ) , 2 ) ;
string applicationName = ( data . Length == 2 ) ? data [ 0 ] : String . Empty ;
return applicationName ;
}
}
private string BaseUserName
{
get
{
string [ ] data = base . UserName . Split ( @"" . ToCharArray ( ) , 2 ) ;
string userName = ( data . Length == 2 ) ? data [ 1 ] : base . UserName ;
return userName ;
}
}
#endregion
// ...
}如您所見,從標準ASP.NET登錄控件和一個稱為_fullusername的字段和兩個私有屬性ApplicationName和baseUserName的字段中添加了DynamicApplicationsLogin控件。
由於用戶將域和用戶名輸入相同的文本框控件,因此我們需要將這些零件分開。您可能會猜測,應用程序名稱屬性提取域,並且baseSername返回用戶名。例如,如果用戶進入Northwind cgeers,則應用名稱屬性將返回“ northwind”,並且baseSername屬性將返回“ cgeers”。
列表4 -DynamicApplicationsLogin Control Onuthenticate方法
protected override void OnAuthenticate ( AuthenticateEventArgs e )
{
HttpContext context = HttpContext . Current ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
MembershipProvider provider = Membership . Provider ;
if ( provider == null )
{
throw new InvalidOperationException ( "MembershipProvider cannot be null." ) ;
}
provider = provider as DynamicApplicationsSqlMembershipProvider ;
if ( provider == null )
{
throw new InvalidOperationException (
"The specified MembershipProvider must be of type DynamicApplicationsSqlMembershipProvider." ) ;
}
// Store the application name in the current Http context's items collections
context . Items [ "ApplicationName" ] = ApplicationName ;
// Validate the user
_fullUserName = UserName ;
UserName = BaseUserName ;
base . OnAuthenticate ( e ) ;
}接下來,您需要覆蓋登錄控件的啟用方法,以確保用戶在正確的應用程序上下文(虛擬應用程序)中驗證。
首先,此方法檢索了對當前HTTP上下文的引用,然後檢查加載的會員資格提供商是否確實是我們的自定義DynamIcapplicationssqlmembershipprovider。然後將輸入的域或應用程序名稱存儲在當前HTTP上下文的項目集合中,以便我們的會員提供者可以檢索其應用名稱屬性的正確值。
最後但並非最不重要的一點是,用戶名屬性的值設置為僅通過為其分配基本名稱屬性來包含用戶名。私有_fullusername字段臨時存儲用戶輸入的值(域用戶名)。您將看到以後如何使用該字段。
應用程序名稱存儲在HTTP上下文中,並且用戶名屬性僅包含調用基本實現的用戶名,這會導致用戶名在正確的應用程序上下文中進行驗證。
列表5 -DynamicApplicationsLogin控制OnloginError方法
protected override void OnLoginError ( EventArgs e )
{
UserName = _fullUserName ;
base . OnLoginError ( e ) ;
}覆蓋onLoginError方法,然後在調用基本實現之前,將_fullusername字段分配給用戶名屬性。在對用戶進行身份驗證時,用戶名屬性必須僅包含用戶名而不是域,但是當身份驗證失敗時,您需要確保在用戶中輸入的用戶在代表用戶名中輸入的文本重置為用戶名,以將用戶重置為用戶最初輸入的用戶。其他原因,如果他進入的域消失了,這可能會使用戶感到困惑。
清單6- dynamicApplicationsLogin控制onloggedin方法
protected override void OnLoggedIn ( EventArgs e )
{
UserName = _fullUserName ;
HttpContext context = HttpContext . Current ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
string userName = BaseUserName ;
MembershipUser user = Membership . GetUser ( userName ) ;
if ( user != null )
{
string userData = String . Format ( "AN={0};" , ApplicationName ) ;
HttpCookie cookie = FormsAuthenticationHelper . StoreUserDataInAuthenticationCookie (
userName , userData , RememberMeSet
) ;
// Manually add the cookie to the Cookies collection
context . Response . Cookies . Add ( cookie ) ;
}
}要完成DynamicApplicationsLogin控制,您需要覆蓋OnloggedIn方法。該應用名稱存儲在FormauthenticationTicket的UserData屬性中。表格身份驗證使用此身份驗證票證以識別身份驗證的用戶。在這裡,我們將其存儲在cookie中,並將其添加到當前HTTP上下文的響應中。稍後,您會看到為什麼需要。
您可以看一下清單6,您會看到包含表單身份驗證票的cookie是由稱為formauthenticationhelper的幫助人類創建的。這是一個靜態的助手類,僅包含一種方法,即storeuserdatainauthenticationcookie。
在cgeers.web.security項目中添加一個名為formauthenticationhelper的新類,並添加列表7中顯示的代碼。
清單7- form authenticationhelper
public static class FormsAuthenticationHelper
{
public static HttpCookie StoreUserDataInAuthenticationCookie (
string userName , string userData , bool persistent )
{
if ( String . IsNullOrEmpty ( userName ) )
{
throw new InvalidOperationException ( "UserName cannot be null or empty." ) ;
}
if ( String . IsNullOrEmpty ( userData ) )
{
throw new InvalidOperationException ( "User data cannot be null or empty." ) ;
}
// Create the cookie that contains the forms authentication ticket
HttpCookie cookie = FormsAuthentication . GetAuthCookie ( userName , persistent ) ;
// Get the FormsAuthenticationTicket out of the encrypted cookie
FormsAuthenticationTicket ticket = FormsAuthentication . Decrypt ( cookie . Value ) ;
// Create a new FormsAuthenticationTicket that includes our custom user data
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket ( ticket . Version ,
ticket . Name ,
ticket . IssueDate ,
ticket . Expiration ,
persistent ,
userData ) ;
// Update the cookie's value to use the encrypted version of our new ticket
cookie . Value = FormsAuthentication . Encrypt ( newTicket ) ;
// Return the cookie
return cookie ;
}
}StoreUserDatainAuthenticationCookie(...)方法檢索包含表單身份驗證票的指定用戶(用戶名參數)的cookie。接下來,將cookie解密以訪問formauthenticationticket對象。然後,基於舊票證創建一個新的FormaUthenticationTicket,並且在此票證中存儲了用戶數據參數中包含的值。作為最後一步,新的FormsAuthenticationTicket對象存儲在加密的cookie中。
備註:DynamicApplicationsLogin Control商店將此cookie發送給用戶,因此客戶需要支持cookie!
讓我們概括到目前為止我們擁有的東西。
因此,我們有自定義會員資格提供商,可以根據請求確定要在此操作的應用程序上下文,並且我們擁有一個自定義登錄控件,該控件可以通過要求用戶在用戶名中使用域(=應用程序名稱)來標識此上下文。
如果您的Web應用程序僅由登錄頁面組成,則可以很好地工作。請記住,自定義會員提供商通過閱讀當前HTTP上下文中存儲的應用程序名稱來確定應用程序上下文。通過您提出的每個請求,將創建一個新的HTTP上下文。登錄應用程序名稱時,將明確存儲在HTTP上下文中。但是,對於任何後續請求,情況都不是這樣。不知何故,我們必須確保在用戶登錄後,使用應用程序名稱正確初始化了HTTP上下文。
如前所述,DynamicApplicationLogin控件成功登錄後,向用戶發送了加密的cookie。此Cookie包含應用程序名稱,並與每個後續請求一起發送。 VOILA,現在我們需要的是一種將某些邏輯插入ASP.NET請求管道的方法,以讀取此Cookie的內容並將檢索到的應用程序名稱存儲在HTTP上下文中。
HTTP模塊非常適合這種情況。每個請求都調用HTTP模塊,並在處理請求之前和之後運行。
將一個名為DynamIcapplicationsModule的新類添加到cgeers.web.security項目。此類需要實現IHTTPMODULE接口。此HTTP模塊的代碼顯示在清單8中。
清單8 -DynamicApplicationsModule
public class DynamicApplicationsModule : IHttpModule
{
#region Fields
private const string ApplicationNameSetting = "ApplicationName" ;
#endregion
#region IHttpModule Members
public void Dispose ( )
{ }
public void Init ( HttpApplication context )
{
context . AuthenticateRequest += DetermineApplicationName ;
}
#endregion
private static void DetermineApplicationName ( object sender , EventArgs e )
{
// Access the current Http application.
HttpApplication application = sender as HttpApplication ;
if ( application == null )
{
throw new InvalidOperationException ( "Http application cannot be null." ) ;
}
// Get the HttpContext for the current request.
HttpContext context = application . Context ;
if ( context == null )
{
throw new InvalidOperationException ( "Http context cannot be null." ) ;
}
// Read the application name stored in the FormsAuthenticationTicket
string applicationName = String . Empty ;
if ( context . Request . IsAuthenticated )
{
FormsIdentity identity = context . User . Identity as FormsIdentity ;
if ( identity != null )
{
FormsAuthenticationTicket ticket = identity . Ticket ;
if ( ticket != null )
{
applicationName = ticket . GetApplicationName ( ) ;
}
}
}
// Store the application name in the Items collection of the per-request http context.
// Storing it in the session state is not an option as the session is not available at this
// time. It is only available when the Http application triggers the AcquireRequestState event.
context . Items [ ApplicationNameSetting ] = applicationName ;
}
}INIT(...)方法初始化了HTTP模塊並準備處理請求。在這裡,HTTPApplication的AuthentIcateRequest事件已掛在dectiNeApplicationName事件處理程序上。
decitioneapplicationname(...)事件處理程序檢查其是否處理已驗證的請求,如果這樣檢索客戶端發送的FormaAthentication票。
該票證標識操作的應用程序上下文。使用formauthenticationticket的getApplicationName()方法從票證中提取應用程序名稱。這是一種擴展方法,我將在下一節中解決。
最後,從票證中提取應用程序名稱後,它將存儲在HTTP上下文中,以便我們的自定義會員提供者可以閱讀它。
將一個名為formauthenticationTicketextensions的新類添加到cgeers.web.security項目中,並添加清單9中顯示的代碼。
列表9- formauthenticationticketextensions
internal static class FormsAuthenticationTicketExtensions
{
public static string GetApplicationName ( this FormsAuthenticationTicket ticket )
{
// Check if the application name (AN=) is stored in the ticket's userdata.
string applicationName = String . Empty ;
List < string > settings = new List < string > ( ticket . UserData . Split ( ';' ) ) ;
foreach ( string s in settings )
{
string setting = s . Trim ( ) ;
if ( setting . StartsWith ( "AN=" ) )
{
int startIndex = setting . IndexOf ( "AN=" ) + 3 ;
applicationName = setting . Substring ( startIndex ) ;
break ;
}
}
return applicationName ;
}
}如您在列表6中所見,DynamicApplicationLogin控件將應用程序名稱保存在FormsAuthenticationTicket的UserData屬性中,該名稱是通過前綴“ AN =”之前的。因此,此擴展方法在從formauthenticationticket檢索應用程序名稱時需要考慮到這一點。
註釋:隨時可以改進票務中的應用程序名稱的方式,但請記住為DynamicApplicationsLogin Control和此擴展方法實現相同的系統。
與DynamicApplicationsLogin控件相似,我創建了從ASP.NET passwordRecovery和ChangePassword控件下降的控件。如果您希望用戶能夠檢索他的密碼和/或更改它,則需要使用這些控件。他們確保在正確的應用程序上下文中執行所有內容。
該實現與DynamicApplicationsLogin控件的實現非常相似,因此我不會在此處列出這些控件的代碼。如果要查看這些控件的源代碼,請下載本文隨附的源代碼。您可以在cgeers.web.ui.webcontrols項目中找到這些控件。
本文隨附的源代碼還包含一個演示Web應用程序項目,該項目演示了DynamicApplicationssqlmembershipprovider,新的登錄控件,我們的自定義HTTP模塊...等等。
圖2-視覺工作室解決方案

為了運行此演示應用程序,請按照以下步驟:
遵循這些步驟後,您應該準備運行演示Web應用程序。您創建了一個數據庫,該數據庫包含兩個應用程序和一個用戶。
啟動Web應用程序時,您可以通過指定應用程序名稱(域)後面登錄每個應用程序,然後使用後斜線和用戶名。當然,也需要用戶密碼。
我們創建的登錄控件會照顧其餘的,並確保您在正確的應用程序上下文中運行。隨後使用ASP.NET成員庫的任何後續呼叫都將觸發自定義會員提供商返回正確的應用程序名稱,從而確保這些調用在正確的應用程序上下文中運行。
備註:演示Web應用程序的Web.config文件包含註釋,這些註釋指定了您需要做的所有操作以正確設置應用程序。請務必檢查一下。
本文向您展示瞭如何使用ASP.NET成員系統來創建一個門戶應用程序,該應用程序允許用戶登錄到許多虛擬應用程序之一。
意識到這一點的第一個關鍵是創建一個自定義會員資格提供商,該提供商可以通過從與單個請求相關的存儲位置檢索應用名稱屬性的值來確保您在正確的應用程序上下文中運行。
通過創建從標準ASP.NET登錄控件下降的自定義登錄控件,我們可以確定用戶想要訪問的應用程序。
通過將這些信息封裝在加密的cookie中,我們可以自動識別用戶想要為其每個請求訪問的應用程序。