ASP.NETメンバーシップ機能は、ユーザーを認証して資格情報を保存するために書く必要があるコードの量を減らします。 MSDNを引用する:
「ASP.NETメンバーシップは、ユーザーの資格情報を検証および保存するための組み込みの方法を提供します。ASP.NETメンバーシップをフォーム認証および /またはユーザーを認証するためのASP.NETログインコントロールで使用します。」
メンバーシッププロバイダーは、web.config構成ファイルで指定する必要があります。独自のカスタムプロバイダーまたはSQLMemberShipproviderプロバイダーなどの.NETフレームワークを搭載したデフォルトのプロバイダーの1つを使用できます。
すべてのユーザー関連データは、ASP.NETメンバーシップシステムで使用される一連のテーブルに保存されます。ほとんどの場合、Webアプリケーションごとに1セットのテーブルを使用します。ただし、同じセットを再利用して、複数のWebアプリケーションのユーザー資格情報を保存することもできます。これにより、これらの「仮想」アプリケーションの1つにログインできるポータルとして機能する1つの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アプリケーションは、私が呼んでいるもので、アプリケーションのコンテキストを分割することにより、ユーザーデータを分離します。このアプリケーションコンテキストは、メンバーシッププロバイダーのApplicationNameプロパティによって定義されます。リスト1でわかるように、アプリケーション名は「myApplication」です。
SQLMembershipproviderは、この設定を使用して、どのコンテキストで動作するかを判断します。このアプリケーション名に関連するすべてのユーザーデータは、ASP.NETメンバーシップライブラリとログインコントロールがアクセスできます。
残念ながら、この設定はweb.configで静的に定義されています。ランタイム中に簡単に変更することはできません。動的アプリケーションをサポートする場合は、プロバイダーのApplicationNameプロパティの価値を制御する方法が必要です。ソリューションは、独自のASP.NETメンバーシッププロバイダーを作成することにあります。
sqlmembershipproviderは、web.configファイルに含まれる構成設定からアプリケーション名プロパティの値を取得します。このプロパティのゲッターが返す値を変更したいと考えています。
ユーザーにログインするとき、自分の意図を明確にする必要があります。つまり、アクセスしたい仮想アプリケーションのどれを指定する必要があります。この目的のために、ユーザーがユーザー名とパスワードだけでなく、アクセスしたい仮想アプリケーションも指定する必要があります。ユーザーの観点から、私はこの仮想アプリケーションであるドメインを呼び出します。
たとえば、ASPNetMembershipと呼ばれる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 and 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から下降し、ApplicationNameプロパティをオーバーライドします。私たちはセッターに関心がなく、ゲッターにのみ関心がありません。プロバイダーがApplicationNameプロパティを読み取ると、HTTPContextクラスの静的電流プロパティを呼び出すことにより、現在のHTTPコンテキストのハンドルを取得します。取得したHTTPContextオブジェクトには、アイテムと呼ばれるキー/バリューコレクションプロパティがあります。このコレクションは、キー「ApplicationName」によって識別されたエントリにアプリケーション名を保存します。このキーの名前は、設計時に決定しなければならないものです。
現在、カスタムメンバーシッププロバイダーがアプリケーション名を決定する必要があるたびに、現在のHTTPコンテキストから値を取得するApplicationNameプロパティを呼び出します。これは、各要求が独自のHTTPコンテキストに関連付けられているため、ASP.NETのマルチスレッド環境で機能します。
Curerct HTTPコンテキストからApplicationNameプロパティの値を取得するカスタムメンバーシッププロバイダーであるため、アプリケーション名を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
// ...
}ご覧のとおり、DynamicApplicationSloginコントロールは標準のASP.NETログインコントロールから下降し、_FulLuserNameと呼ばれるフィールドと2つのプライベートプロパティアプリケーション名とベースセルナル名が追加されています。
ユーザーはドメインとユーザー名を同じテキストボックスコントロールに入力するため、これらの部分を分割する必要があります。 ApplicationNameプロパティがドメインを抽出し、baseUsernameがユーザー名を返します。たとえば、ユーザーがnorthwind cgeersに入ると、ApplicationNameプロパティは「Northwind」を返し、baseUsernameプロパティは「Cgeers」を返します。
リスト4 -DynamicApplicationsLogin Control OnAuthenticateメソッド
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 ) ;
}次に、ユーザーが正しいアプリケーションコンテキスト内で検証されていることを確認するために、ログインコントロールのonAuthenticateメソッドをオーバーライドする必要があります(仮想アプリケーション)。
最初に、このメソッドは現在のHTTPコンテキストへの参照を取得し、ロードされたメンバーシッププロバイダーが実際にカスタムDynamicApplicationsQLMemberShipproviderであるかどうかをチェックします。次に、入力されたドメインまたはアプリケーション名が現在のHTTPコンテキストのアイテムコレクションに保存され、メンバーシッププロバイダーがApplicationNameプロパティの正しい値を取得できるようにします。
最後になりましたが、ユーザー名プロパティの値は、baseusernameプロパティを割り当てることにより、ユーザー名のみを含めるように設定されています。プライベート_fullusernameフィールドは、ユーザーが入力した値(ドメイン username)を一時的に保存します。このフィールドの使用方法は後で表示されます。
アプリケーション名がHTTPコンテキストに保存され、ユーザー名プロパティにはベース実装が呼び出されるユーザー名のみが含まれている後、これにより、正しいアプリケーションコンテキストの内部でユーザー名が検証されます。
リスト5 -dynamicApplicationsLogin Control OnLoginErrorメソッド
protected override void OnLoginError ( EventArgs e )
{
UserName = _fullUserName ;
base . OnLoginError ( e ) ;
}onloginerrorメソッドをオーバーライドし、ベース実装を呼び出す前に、_fullusernameフィールドをユーザー名プロパティに割り当てます。ユーザー(onauthenticateメソッド)を認証する場合、ユーザー名プロパティにはドメインではなくユーザー名のみを含める必要がありますが、認証に失敗する場合は、ユーザーが入力したテキストがユーザー名を表すテキストボックスコントロールに入力したテキストが最初に入力した値にリセットされることを確認する必要があります。他の場合、彼が入力したドメインが消えた場合、ユーザーにとって混乱する可能性があります。
リスト6 -LynamicApplicationsLogin Control 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コントロールを完了するには、オンログジンメソッドをオーバーライドする必要があります。アプリケーション名は、formsauthenticationTicketのuserDataプロパティに保存されます。この認証チケットは、Forms Authenticationによって使用され、認証されたユーザーを識別します。ここでは、Cookieに保存し、現在のHTTPコンテキストの応答に追加します。後で、これが必要な理由がわかります。
リスト6を見てみましょう。フォーム認証チケットを含むCookieは、formsauthenticationhelperと呼ばれるヘルパークラスによって作成されていることがわかります。これは、1つの方法、つまりStoreUserdatainAuthenticationCookieのみを含む静的ヘルパークラスです。
formsauthenticationhelperという新しいクラスをcgeers.web.securityプロジェクトに追加し、リスト7に示すコードを追加します。
リスト7- formsauthenticationhelper
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を取得します。次に、FormSauthenticationTicketオブジェクトにアクセスするために、Cookieが復号化されます。次に、古いチケットに基づいて新しいformsauthenticationTicketが作成され、userDataパラメーターに含まれる値はこのチケットに保存されます。最後のステップとして、新しいformsauthenticationTicketオブジェクトは暗号化されたCookie内に保存されます。
注:DynamicApplicationSloginコントロールストアは、このCookieをユーザーに送り返すため、クライアントはCookieをサポートする必要があります。
これまでのところ、私たちが持っているものを再現しましょう。
そのため、リクエストに応じて、操作するアプリケーションコンテキストを決定できるカスタムメンバーシッププロバイダーがあり、ユーザーがドメイン(=アプリケーション名)を使用してユーザーがユーザー名の前に先行することを要求することにより、このコンテキストを識別するカスタムログインコントロールがあります。
これは、Webアプリケーションがログインページからのみ構成されている場合、非常にうまく機能します。カスタムメンバーシッププロバイダーは、現在のHTTPコンテキストに保存されているアプリケーション名を読み取ることにより、アプリケーションコンテキストを決定することに注意してください。リクエストを行うたびに、新しいHTTPコンテキストが作成されます。アプリケーション名にログインすると、HTTPコンテキスト内に明示的に保存されます。ただし、その後のリクエストについては、そうではありません。どういうわけか、ユーザーがログインした後、各リクエストでHTTPコンテキストがアプリケーション名で正しく初期化されることを確認する必要があります。
前述のように、DynamicApplicationLoginコントロールは、正常にログインした後、暗号化されたCookieをユーザーに送信します。このCookieにはアプリケーション名が含まれており、その後のリクエストごとに送信されます。 Vaila、今必要なのは、このCookieの内容を読み、HTTPコンテキストに検索されたアプリケーション名を保存するために、ASP.NETリクエストパイプラインにロジックを挿入する方法です。
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のAuthingIcateRequestイベントは、setineApplicationNameイベントハンドラーにフックされます。
sechineApplicationName(...)イベントハンドラーは、認証されたリクエストを扱っているかどうかを確認します。
このチケットは、操作するアプリケーションコンテキストを識別します。アプリケーション名は、formsauthenticationTicketのgetApplicationName()メソッドを使用してチケットから抽出されます。これは拡張法です。次のセクションで説明します。
最後に、チケットからアプリケーション名を抽出した後、HTTPコンテキスト内に保存され、カスタムメンバーシッププロバイダーが読み取ることができます。
cgeers.web.securityプロジェクトにformsauthenticationTicketextensionsという新しいクラスを追加し、リスト9に示すコードを追加します。
リスト9- formsauthenticationTickeTextensions
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プロパティにアプリケーション名を保存します。したがって、この拡張法は、FormsAuthenticationTicketからアプリケーション名を取得する際にこれを考慮する必要があります。
注:アプリケーション名がチケットに保存される方法を自由に改善してください。ただし、DynamicApplicationsLoginコントロールとこの拡張法のために同じシステムを実装することを忘れないでください。
dynamicApplicationsloginコントロールと同様に、ASP.NET PasswordRecoveryとChangePasswordコントロールから下降するコントロールを作成しました。ユーザーがパスワードを取得したり、変更できるようにしたい場合は、これらのコントロールを使用する必要があります。彼らは、すべてが正しいアプリケーションコンテキスト内で実行されることを確認します。
この実装は、dynamicApplicationsLoginコントロールの実装と非常に似ているため、これらのコントロールのコードはここにリストされません。これらのコントロールのソースコードをチェックアウトしたい場合は、この記事に付随するソースコードをダウンロードしてください。これらのコントロールは、cgeers.web.ui.webcontrolsプロジェクトで見つけることができます。
この記事に付随するソースコードには、DynamicApplicationsSQLMemberShipprovider、新しいログインコントロール、カスタムHTTPモジュールなどを示すデモWebアプリケーションプロジェクトも含まれています。
図2-ビジュアルスタジオソリューション

このデモアプリケーションを実行するには、次の手順に従ってください。
これらの手順に従った後、デモWebアプリケーションを実行する準備ができている必要があります。各アプリケーションに2つのアプリケーションとユーザーを含むデータベースを作成しました。
Webアプリケーションを開始すると、アプリケーション名(ドメイン)を特定してバックスラッシュとユーザー名を特定して、各アプリケーションにログインできます。もちろん、ユーザーのパスワードも必要です。
作成したログインコントロールは、残りの部分を処理し、正しいアプリケーションコンテキスト内で動作していることを確認します。 ASP.NETメンバーシップライブラリとの後続の呼び出しは、カスタムメンバーシッププロバイダーに正しいアプリケーション名を返すようにトリガーし、これらの呼び出しが正しいアプリケーションコンテキスト内で動作することを確認します。
注:DEMO WebアプリケーションのWeb.Configファイルには、アプリケーションを適切にセットアップするために必要なすべてを指定するコメントが含まれています。必ずチェックしてください。
この記事では、ASP.NETメンバーシップシステムを使用して、ユーザーが多くの仮想アプリケーションのいずれかにログインできるポータルアプリケーションを作成する方法を示しました。
これを実現するための最初の鍵は、個々のリクエストに関連付けられたストレージ場所からアプリケーション名プロパティの値を取得することにより、正しいアプリケーションコンテキスト内で操作するカスタムメンバーシッププロバイダーを作成することです。
標準のASP.NETログインコントロールから下降するカスタムログインコントロールを作成することにより、ユーザーがアクセスしたいアプリケーションを決定できます。
暗号化されたCookieでこの情報をカプセル化することにより、ユーザーがリクエストごとにアクセスしたいアプリケーションを自動的に識別することができます。