セキュリティ保護された ASP.NET アプリケーションの構築 : 認証、認定、および通信のセキュリティ保護 Active Directory でフォーム認証を使用する方法
J.D. Meier, Alex Mackman, Michael Dunner, and Srinath Vasireddy
Microsoft Corporation
November 2002
日本語版最終更新日 2003 年 3 月 17 日
適用対象:
Microsoft® ASP.NET
Microsoft Active Directory®
Microsoft Windows® 2000
Microsoft Visual Studio® .NET
全体の概要については、「セキュリティ保護された ASP.NET アプリケーションの構築」の開始ページを参照してください。
要約 : ここでは、Active Directory の資格情報ストアに対してフォーム認証を行う方法について説明します。
目次
ASP.NET フォーム認証を使用すると、ユーザーが資格情報 (ユーザー名とパスワード) を Web フォームに入力して、自分自身の身分を証明することができます。この資格情報を受け取った Web アプリケーションは、ユーザー名とパスワードの組み合わせとデータ ソースで照合を行い、そのユーザーを認証します。
ここでは、LDAP (Lightweight Directory Access Protocol) を使用して、Microsoft® Active Directory® ディレクトリ サービスに対してユーザーを認証する方法について説明します。また、ユーザーが所属するセキュリティ グループの一覧や配布リストを取得する方法、この情報を GenericPrincipal オブジェクトや、ASP.NET Web アプリケーションを通して要求と共に行き来する HttpContext.Current.User プロパティへ格納する方法についても解説しています。ここでの説明は、.NET のロールを基準とした認証を行う場合に参考になります。
必要条件
ハードウェア、ソフトウェア、ネットワーク インフラストラクチャ、スキル、知識、サービス パックの要件は、以下のとおりです。
- Microsoft Windows® 2000 オペレーティング システム
- Microsoft Visual Studio® .NET 開発システム
また、ここで説明する手順には、Microsoft Visual C#® 開発ツールの知識も必要です。
要約
ここでは、次の手順について説明します。
- ログオン ページを含む Web アプリケーションを作成する。
- フォーム認証を使用するよう Web アプリケーションを構成する。
- Active Directory でユーザーを参照するための LDAP 認証コードを開発する。
- ユーザーのグループ メンバシップを参照するための LDAP グループ取得コードを開発する。
- ユーザーを認証し、フォームの認証チケットを作成する。
- 認証要求ハンドラを実装して、GenericPrincipal オブジェクトを生成する。
- アプリケーションをテストする。
1. ログオン ページを含む Web アプリケーションを作成する
ユーザー名とパスワードを入力するログオン ページ、および現在の Web 要求に関連付けられた ID 名とグループ メンバシップ情報が表示される既定のページを含むシンプルな C# Web アプリケーションを作成する手順を次に示します。
■ ログオン ページを含む Web アプリケーションを作成するには
Visual Studio .NET を起動して、"FormsAuthAD" という名前の新しい C# ASP.NET Web アプリケーションを作成します。
ソリューション エクスプローラで、WebForm1.aspx の名前を Logon.aspx に変更します。
System.DirectoryServices.dll へのアセンブリ参照を追加します。これにより、Active Directory のクエリや操作に役立つマネージ クラスを格納する System.DirectoryServices 名前空間にアクセスできるようになります。
表 1 内のコントロールを Logon.aspx に追加して、シンプルなログオン フォームを作成します。
表 1 Logon.aspx コントロール
コントロールの種類 テキスト ID Label ドメイン名 : - Label ユーザー名 : - Label パスワード - TextBox - txtDomainName TextBox - txtUserName TextBox - txtPassword Button ログオン btnLogon Label - lblError txtPassword の TextMode プロパティを Password に設定します。
ソリューション エクスプローラで、FormsAuthAd を右クリックし、[追加] をポイントして、[Web フォームの追加] をクリックします。
[ファイル名] フィールドに「default.aspx」と入力して、[開く] をクリックします。
ソリューション エクスプローラで、default.aspx を右クリックして、[スタート ページに設定] をクリックします。
default.aspx をダブルクリックして、ページ読み込みのイベント ハンドラを表示します。
イベント ハンドラに次のコードを追加して、現在の Web 要求に関連付けられている ID 名を表示します。
Response.Write( HttpContext.Current.User.Identity.Name );
2. フォーム認証を使用するよう Web アプリケーションを構成する
アプリケーションの Web.config ファイルを編集し、フォーム認証を使用するようアプリケーションを構成する手順を次に示します。
■ フォーム認証を使用するよう Web アプリケーションを構成するには
ソリューション エクスプローラから Web.config を開きます。
<authentication> 要素を探し、mode 属性を Forms に変更します。
authentication 要素の子要素として <forms> 要素を追加して、loginUrl、name、timeout、path の各属性を次のように設定します。
<authentication mode="Forms">
<forms loginUrl="logon.aspx" name="adAuthCookie" timeout="60" path="/">
</forms>
</authentication>
- <authentication> 要素の下に次の <authorization> 要素を追加します。この要素を追加することにより、認証されたユーザー以外はアプリケーションにアクセスできなくなります。認証されていない要求は、前の手順で確立された <authentication> 要素の loginUrl 属性によって logon.aspx ページにリダイレクトされます。
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
Web.config を保存します。
IIS の Microsoft 管理コンソール (MMC) スナップインを開きます。
アプリケーションの仮想ディレクトリを右クリックして、[プロパティ] をクリックします。
[ディレクトリ セキュリティ] タブをクリックして、[匿名アクセスおよび認証コントロール] の [編集] ボタンをクリックします。
[匿名アクセス] チェック ボックスをオンにして、[IIS によるパスワードの管理を許可する] チェック ボックスをオフにします。
既定の匿名アカウント (IUSR_MACHINE) には Active Directory へのアクセス許可がないため、最小限の特権だけを持つ新しいアカウントを作成して、[認証方法] ダイアログ ボックスでアカウントの詳細を設定します。
[OK] を 2 回クリックして、[プロパティ] ダイアログ ボックスを閉じます。
Visual Studio .NET に戻り、Web.config の <authorization> 要素の下に <identity> 要素を追加して、impersonate 属性に true を設定します。これにより、前の手順で指定した匿名アカウントを ASP.NET が偽装することになります。
<identity impersonate="true" />
このように構成すると、アプリケーションへのすべての要求がこの匿名アカウントのセキュリティ コンテキストで実行されます。ユーザーは Web フォームを使って Active Directory に対する認証に必要な資格情報を入力しますが、Active Directory へのアクセスに使用するアカウントはこの匿名アカウントとなります。
3. Active Directory でユーザーを参照するための LDAP 認証コードを開発する
Web アプリケーションに新しいヘルパ クラスを追加して、LDAP コードをカプセル化する手順を次に示します。このクラスでは、まず IsAuthenticated メソッドを定義して、入力されたドメイン、ユーザー名、パスワードが Active Directory ユーザー オブジェクトに対して有効かどうかを検証します。
■ LDAP 認証コードを開発して Active Directory 内のユーザーを参照するには
- 新しい C# クラス ファイルを作成し、LdapAuthentication.cs という名前を付けます。
- System.DirectoryServices.dll アセンブリへの参照を追加します。
- LdapAuthentication.cs の先頭に次の using ステートメントを追加します。
using System.Text;
using System.Collections;
using System.DirectoryServices;
- 既存の名前空間の名前を FormsAuthAD に変更します。
- 2 つの private 文字列を LdapAuthentication クラスに追加します。1 つはActive Directory への LDAP パスを表し、もう 1 つは Active Directory の検索に使用される filter 属性を表します。
private string _path;
private string _filterAttribute;
- public コンストラクタを追加します。これは、Active Directory パスを初期化するために使用します。
public LdapAuthentication(string path)
{
_path = path;
}
- 次の IsAuthenticated メソッドを追加します。このメソッドはドメイン名、ユーザー名、パスワードをパラメータとして受け取り、一致するパスワードを持つユーザーが Active Directory に存在するかどうかを示すブール型の値を返します。このメソッドは、まず指定された資格情報を使用して Active Directory へのバインドを試みます。バインドが成功した場合は DirectorySearcher マネージ クラスを使用して指定されたユーザー オブジェクトを検索します。ユーザー オブジェクトが見つかると、_path メンバがそのユーザー オブジェクトへのパスに更新され、_filterAttribute メンバはそのユーザー オブジェクトの共通名属性を使用して更新されます。
public bool IsAuthenticated(string domain, string username, string pwd)
{
string domainAndUsername = domain + @"\" + username;
DirectoryEntry entry = new DirectoryEntry( _path,
domainAndUsername, pwd);
try
{
// Bind to the native AdsObject to force authentication.
Object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if(null == result)
{
return false;
}
// Update the new path to the user in the directory
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
}
4. ユーザーのグループ メンバシップを参照するための LDAP グループ取得コードを開発する
LdapAuthentication クラスを拡張して、現在のユーザーが所属するグループの一覧を取得する GetGroups メソッドを定義する手順を次に示します。GetGroups メソッドは、パイプで区切られた文字列としてグループの一覧を返します。
"Group1|Group2|Group3|"
■ LDAP グループ取得コードを開発してユーザーのグループ メンバシップを参照するには
- 次の GetGroups メソッド実装を LdapAuthentication クラスに追加します。
public string GetGroups()
{
DirectorySearcher search = new DirectorySearcher(_path);
search.Filter = "(cn=" + _filterAttribute + ")";
search.PropertiesToLoad.Add("memberOf");
StringBuilder groupNames = new StringBuilder();
try
{
SearchResult result = search.FindOne();
int propertyCount = result.Properties["memberOf"].Count;
String dn;
int equalsIndex, commaIndex;
for( int propertyCounter = 0; propertyCounter < propertyCount;
propertyCounter++)
{
dn = (String)result.Properties["memberOf"][propertyCounter];
equalsIndex = dn.IndexOf("=", 1);
commaIndex = dn.IndexOf(",", 1);
if (-1 == equalsIndex)
{
return null;
}
groupNames.Append(dn.Substring((equalsIndex + 1),
(commaIndex - equalsIndex) - 1));
groupNames.Append("|");
}
}
catch(Exception ex)
{
throw new Exception("Error obtaining group names. " + ex.Message);
}
return groupNames.ToString();
}
5. ユーザーを認証し、フォームの認証チケットを作成する
ユーザーを認証するための btnLogon_Click イベント ハンドラを実装する手順を次に示します。認証されたユーザーについては、フォーム認証チケットを作成して、そのユーザーが所属するグループの一覧を格納します。そして、認証されたユーザーが要求した元のページに、そのユーザーをリダイレクトします。ログオン ページにリダイレクトするのはその後です。
■ ユーザーを認証し、フォーム認証チケットを作成するには
- Logon.aspx フォームに戻って [ログオン] をダブルクリックし、空の btnLogon_Click イベント ハンドラを作成します。
- ファイルの先頭にある、既存の using ステートメントの下に次の using ステートメントを追加します。これにより、FormsAuthentication のメソッドにアクセスできます。
using System.Web.Security;
- コードを追加して、LdapAuthentication クラスのインスタンスを新しく作成します。このとき同時に LDAP Active Directory のパスを指定してインスタンスを初期化します。Active Directory サーバーを参照するようにパスを変更することを忘れないでください。
// Path to you LDAP directory server.
// Contact your network administrator to obtain a valid path.
string adPath = "LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com";
LdapAuthentication adAuth = new LdapAuthentication(adPath);
- 次の操作を行うコードを追加します。
- Active Directory に対して呼び出し側のユーザーを認証する。
- ユーザーが所属するグループの一覧を取得する。
- グループ一覧を格納する FormsAuthenticationTicket を作成する。
- チケットを暗号化する。
- 暗号化されたチケットを格納する新しい Cookie を作成する。
- 新しく作成した Cookie をユーザーのブラウザに返された Cookie の一覧に追加する。
try
{
if(true == adAuth.IsAuthenticated(txtDomainName.Text,
txtUserName.Text,
txtPassword.Text))
{
// Retrieve the user's groups
string groups = adAuth.GetGroups();
// Create the authetication ticket
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1, // version
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false, groups);
// Now encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// Create a cookie and add the encrypted ticket to the
// cookie as data.
HttpCookie authCookie =
new HttpCookie(FormsAuthentication.FormsCookieName,
encryptedTicket);
// Add the cookie to the outgoing cookies collection.
Response.Cookies.Add(authCookie);
// Redirect the user to the originally requested page
Response.Redirect(
FormsAuthentication.GetRedirectUrl(txtUserName.Text,
false));
}
else
{
lblError.Text =
"Authentication failed, check username and password.";
}
}
catch(Exception ex)
{
lblError.Text = "Error authenticating. " + ex.Message;
}
6. 認証要求ハンドラを実装して、GenericPrincipal オブジェクトを生成する
global.asax 内で Application_AuthenticateRequest イベント ハンドラを実装し、現在認証されているユーザーの GenericPrincipal オブジェクトを作成する手順を次に示します。このオブジェクトにはユーザーが所属するグループの一覧が格納されます。グループの一覧は認証 Cookie に含まれる FormsAuthenticationTicket から取得したものです。最後に、GenericPrincipal オブジェクトと、Web 要求ごとに作成される現在の HttpContext オブジェクトを関連付けます。
■ 認証要求ハンドラを実装して GenericPricipal オブジェクトを生成するには
- ソリューション エクスプローラから global.asax.cs を開きます。
- ファイルの先頭部に次の using ステートメントを追加します。
using System.Web.Security;
using System.Security.Principal;
- Application_AuthenticateRequest イベント ハンドラを探して次のコードを追加します。これにより、要求と共に渡された Cookie のコレクションから、暗号化されたFormsAuthenticationTicket を含む Cookie を取得します。
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if(null == authCookie)
{
// There is no authentication cookie.
return;
}
- 次のコードを追加して、Cookie から FormsAuthenticationTicket を取り出し、解読します。
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch(Exception ex)
{
// Log exception details (omitted for simplicity)
return;
}
if (null == authTicket)
{
// Cookie failed to decrypt.
return;
}
- 次のコードを追加して、ユーザーが最初に認証されたときにチケットに格納されたグループ名の一覧を解析します。この一覧はパイプで区切られています。
// When the ticket was created, the UserData property was assigned a
// pipe delimited string of group names.
String[] groups = authTicket.UserData.Split(new char[]{'|'});
- 次のコードを追加して、チケット名から取得したユーザー名で GenericIdentity オブジェクトを作成し、この ID とユーザーのグループ一覧を格納する GenericPrincipal オブジェクトを作成します。
// Create an Identity object
GenericIdentity id = new GenericIdentity(authTicket.Name,
"LdapAuthentication");
// This principal will flow throughout the request.
GenericPrincipal principal = new GenericPrincipal(id, groups);
// Attach the new principal object to the current HttpContext object
Context.User = principal;
7. アプリケーションをテストする
Web アプリケーションを使用して default.aspx ページを要求する手順を次に示します。認証は、ログオン ページにリダイレクトされてから行われます。認証が正常に行われると、ブラウザが元の要求先である default.aspx ページにリダイレクトされます。これにより、認証されたユーザーが所属するグループの一覧が、認証プロセスにより現在の要求に関連付けられた GenericPrincipal オブジェクトから抽出および表示されます。
■ アプリケーションをテストするには
[ビルド] メニューの [ソリューションのビルド] をクリックします。
ソリューション エクスプローラで、default.aspx を右クリックして、[ブラウザで表示] をクリックします。
有効なドメイン名、ユーザー名、パスワードを入力して、[ログオン] をクリックします。
認証が正常に行われると default.aspx にリダイレクトされます。このページには、認証されたユーザーのユーザー名が表示されます。
認証されたユーザーが所属するグループの一覧を確認するには、global.aspx.cs ファイルのApplication_AuthenticateRequest イベント ハンドラの最後に次のコードを追加します。
Response.Write("Groups: " + authTicket.UserData + "<br>");