Silverlight をインストールするには、ここをクリックします*
Japan変更|すべてのMicrosoft のサイト
MSDN
|MSDN ライブラリ|デベロッパー センター|ダウンロード情報|開発ツール製品|コミュニティ|ご意見・ご要望|サイトマップ
MSDN Magazine  
   MSDN Home >  MSDN マガジン >  2005 年 4 月
セキュリティ

資格情報管理のための Windows フォームと ASP.NET プロバイダの統合


この記事は、.NET Framework 2.0 と Visual Studio 2005 のプレリリース バージョンに基づいています。この記事のすべての情報は変更される可能性があります。

この記事で取り上げる話題:
  • .NET ユーザー資格情報の管理
  • ASP.NET 2.0 セキュリティ プロバイダ モデル
  • Windows フォーム用カスタム セキュリティの設計
  • クライアント アプリケーションのコード アクセス セキュリティの問題
この記事で使用する技術:
Windows フォーム、ASP.NET、Web サービス、C#


サンプルコードのダウンロード:
Security.exe (英語) (220KB)

目次


Windows フォームのイントラネット アプリケーションでは、Windows で統一された環境に配置される場合も含め、ユーザー資格情報がデータベースに格納されるのが一般的です。その理由は、Windows アカウントを使用しなければならないという制約にあります。ASP.NET アプリケーションでは、特別な設定を行わなくても、Microsoft .NET Framework 2.0 のカスタム資格情報管理を利用できます。これにより、Windows アカウントを使用しなくても簡単にユーザーを認証および承認できます。その結果、ソリューションの品質やセキュリティが向上することはもちろん、開発者の貴重な時間と労力も節約できます。

この記事では、互いに連携する一連のヘルパー クラスを紹介します。これらを使用すると、ASP.NET の資格情報管理インフラストラクチャを、ASP.NET アプリケーションと同じくらい簡単に Windows フォーム アプリケーションでも使用できるようになります。これにより、使用するアプリケーション ユーザー インターフェイスに関係なく ASP.NET の生産性のメリットを享受できるほか、統一された資格情報ストアも利用できます。このほか、.NET プログラミングのテクニックや、その他のデザインおよびプログラミングのさまざまなベスト プラクティスも併せて紹介します。


ASP.NET のセキュリティ インフラストラクチャ

ASP.NET のユーザー資格情報管理の活用方法を紹介する前に、その概要を簡単に説明します。ASP.NET アプリケーションでは、特別な設定を行わなくても、SQL Server または SQL Server Express にカスタム ユーザー資格情報を格納できます。また、この資格情報管理アーキテクチャはプロバイダ モデルによるものなので、他のストレージ オプション (Microsoft Access データベースなど) も簡単に追加できます。ASP.NET 開発者は、アプリケーションを直接 Visual Studio 2005 から構成できます。[Web site] メニューの [ASP.NET Configuration] をクリックすると、さまざまなセキュリティ パラメータを構成できる ASP.NET Web Site Administration Tool が表示されます (図 1 を参照)。ここからサイト管理プロバイダにアクセスして、使用するストア (SQL Server データベースや SQL Server Express データベースなど) を選択できます。このほか、ユーザーの新規作成および削除、ロールの新規作成および削除、ロールへのユーザーの割り当てなどを行うこともできます。ここで注目すべき点は、さまざまな ASP.NET アプリケーションのユーザー情報が同じデータベース テーブルを使って格納されていることです。このため、ユーザーやロールの各レコードは特定のアプリケーション名にも関連付けられています。

図 1 ASP.NET アプリケーションのセキュリティの構成
図 1 ASP.NET アプリケーションのセキュリティの構成

SQL Server プロバイダを使用するには、\WINDOWS\Microsoft.NET\Framework\Version\ にあるセットアップ ファイル aspnet_regsql.exe を実行します。このセットアップ プログラムは、aspnetdb という新しいデータベースを作成します。このデータベースには、資格情報の管理に必要なテーブルとストアド プロシージャが含まれています。

ASP.NET は、実行時に、データベースの資格情報を使用して呼び出し元を認証します。ユーザーが資格情報を入力できるようにするには、Web フォームに Login コントロールを追加する方法が最も簡単です。Login コントロールは、ユーザーからユーザー名とパスワードを収集し、MembershipProvider というクラスを使用して認証を行います。このクラスは次のように定義されています。

public abstract class MembershipProvider : ProviderBase
{
   public abstract string ApplicationName{get;set;}
   public abstract bool ValidateUser(string name, string password);
   ...
}

MembershipProvider の目的は、実際に使用されているプロバイダとデータ アクセスの詳細をカプセル化し、アプリケーション自体には影響を与えずにメンバシップ プロバイダを変更できるようにすることです。Login コントロールは、設定されているセキュリティ プロバイダに応じて、MembershipProvider クラスから派生する具体的なデータ アクセス クラス (SqlMembershipProvider など) を使用します。SQL Server を使用する場合は次のようになります。

public class SqlMembershipProvider : MembershipProvider
{...}

ただし、Login コントロールがやり取りするのは MembershipProvider 基本クラスだけです。Login コントロールは、Membership クラスの静的プロパティ Provider にアクセスすることによって、必要なメンバシップ プロバイダを取得します。このクラスは次のように定義されています。

public sealed class Membership
{
   public static string ApplicationName{get;set;}
   public static MembershipProvider Provider{get;}
   public static bool ValidateUser(string userName, string password);
   ...
}

Membership クラスには、ユーザー管理のあらゆる側面をサポートする数多くのメンバが用意されています。Membership.Provider は、Web アプリケーション構成ファイルの設定に基づいて、設定されているプロバイダのインスタンスを返します。

ここでは、Membership クラスの 2 つのメンバに焦点を絞ります。1 つは、アプリケーション名の設定および取得のために使用される ApplicationName プロパティ、 もう 1 つは、ValidateUser メソッドです。このメソッドは、指定された資格情報をストアに対して認証し、一致した場合は true を、一致しなかった場合は false を返します。静的メソッド Membership.ValidateUser は、設定されているプロバイダを Membership.Provider から取得して、そのインスタンスの ValidateUser メソッドを使用する、というプロセスの短縮形です。

ロールベースのセキュリティを適用して、操作やリソースへのアクセスを承認することもできます。aspnetdb データベースには、ユーザーとロールのマッピング情報が格納されています。ユーザーが認証されると、ASP.NET は、HTTP コンテキストとページの User プロパティを、RolePrincipal というカスタム セキュリティ プリンシパル オブジェクトに設定します。

public sealed class RolePrincipal : IPrincipal
{...}
RolePrincipal は、抽象クラス RoleProvider を使用します。
public abstract class RoleProvider : ProviderBase
{
   public abstract string ApplicationName{get;set;}
   public abstract bool IsUserInRole(string username, string roleName);
   public abstract string[] GetRolesForUser(string userName);
   ...
}
RoleProvider の ApplicationName プロパティは、ロール プロバイダを特定のアプリケーションにバインドします。IsUserInRole メソッドは、ユーザーのロール メンバシップを確認します。GetRolesForUser メソッドは、指定されたユーザーがメンバになっているすべてのロールを返します。

RolePrincipal は、メンバシップ プロバイダと同様に、設定されているセキュリティ プロバイダに対応するデータ アクセス クラス (SqlRoleProvider など) を使用して、呼び出し元を承認します。

public class SqlRoleProvider : RoleProvider
{...}

必要なロール プロバイダは、Roles クラスの静的プロパティ Provider にアクセスすることによって取得できます。このクラスは次のように定義されています。

public sealed class Roles
{
   public static string ApplicationName{get;set;}
   public static string[] GetRolesForUser(string username);
   public static bool IsUserInRole(string username, string roleName);
   public static RoleProvider Provider{get;}
   ...  
}
Roles.GetRolesForUser と Roles.IsUserInRole はどちらも短縮形で、内部で Roles.Provider プロパティを使用します。Roles.Provider は、Web アプリケーション構成ファイルの設定に基づいて、設定されているプロバイダのインスタンスを返します。

Back to top

ソリューション アーキテクチャ

ここでの第 1 の目標は、ASP.NET 2.0 の資格情報管理インフラストラクチャを活用することですが、そのほかに、Windows フォームのための汎用カスタム認証/承認インフラストラクチャを提供するという目標もあります。 そのようなインフラストラクチャは、必ずしも ASP.NET 2.0 に結び付いているとは限りません。また、任意のカスタム資格情報ストア (Access データベースや Lightweight Directory Access Protocol (LDAP) データベースなど) を簡単に使用できるようにする必要があります。 したがって、まず最初に、IUserManager インターフェイスを定義して実際の資格情報ストアを切り離します。

public interface IUserManager
{
   bool Authenticate(string applicationName,string userName, 
       string password);
   bool IsInRole(string applicationName, string userName, string role);
   string[] GetRoles(string applicationName, string userName);
}

Authenticate メソッドは、指定されたユーザー資格情報を資格情報ストアに対して認証するために使用されます。IsInRole は、ロールベースのセキュリティを使用する場合にユーザーの承認を行います。このほか、指定されたユーザーがメンバになっているすべてのロールを返す GetRoles メソッドも用意されています。GetRoles は、ロール メンバシップをキャッシュする際に便利です。これについては後ほど説明します。

Authenticate は、LoginControl という抽象 Windows フォーム カスタム コントロールによって使用されます。LoginControl は、ASP.NET の Login コントロールと同じように、Windows フォーム アプリケーションに追加して使用します (実際に追加されるのはそのサブクラスです)。LoginControl は IUserManager の実装を取得し、指定された資格情報を Authenticate メソッドを使用して認証します。ユーザーが指定した資格情報が有効だった場合は、CustomPrincipal という IPrincipal の実装をインスタンス化します。LoginControl は CustomPrincipal に IUserManager の実装を渡して、CustomPrincipal を現在のスレッドにアタッチします。CustomPrincipal クラスは、ユーザーの承認のために IUserManager の IsInRole メソッドや GetRoles メソッドを使用できます。図 2 はこのアーキテクチャを表しています。LoginControl と CustomPrincipal は、どちらも WinFormsEx.dll クラス ライブラリ アセンブリで定義されています。WinFormsEx.dll は、この記事のダウンロード コードに含まれています。

図 2 LoginControl のアーキテクチャ
図 2 LoginControl のアーキテクチャ

ここで、CustomPrincipal は認証を行わないことに注意してください。これは、LoginControl によって認証が行われることを前提としているためです。したがって、有効な認証が行われないまま CustomPrincipal がスレッドにアタッチされることがないようにする必要があります。そのために、CustomPrincipal クラスは、LoginControl によってのみ呼び出される内部クラスになっています。それでも、リフレクションを使用して CustomPrincipal のインスタンスを作成することはできますが、そのためには高いレベルの信頼が必要になります。したがって、最小限の特権の原則に従って、これを可能にする ReflectionPermission をアセンブリに与えないようにすることをお勧めします。

Back to top

IPrincipal の実装

CustomPrincipal は、Windows のセキュリティ プリンシパルに代わって PrincipalPermissionAttribute クラスと PrincipalPermission クラスにサービスを提供することを唯一の目的としています。また、CustomPrincipal は、認証が成功してからインストールされるようにする必要があります。この設計方針のさらなる徹底と自動化のために、CustomPrincipal にはパブリック コンストラクタがなく、クライアントが直接インスタンス化できないようになっています。クライアントは代わりにパブリックの静的メソッド Attach を使用します。 Attach はまず、指定された ID が認証済みユーザーのものかどうかを確認します。

Debug.Assert(user.IsAuthenticated); 

ユーザーが認証済みだった場合は、CustomPrincipal 型のオブジェクトを作成して、セキュリティ ID、使用する IUserManager の実装、およびロールのキャッシュ ポリシーを渡します (図 3 を参照)。

CustomPrincipal のコンストラクタは、渡された ID と、そのスレッドに関連付けられていた前のプリンシパルへの参照を保存します。そして最も重要な操作として、次のように、現在のスレッドの CurrentPrincipal プロパティを自身に設定して既定のプリンシパルを置き換えます。

Thread.CurrentPrincipal = this;

ログアウトのセマンティクスをサポートするために、CustomPrincipal には Detach メソッドが用意されています。これにより、CustomPrincipal をスレッドからデタッチして、作成時に保存した元のプリンシパルを回復できます。

Attach はさらに、CustomPrincipal が新しいスレッドに自動的にアタッチされるように、既定のスレッド プリンシパル オブジェクトを CustomPrincipal に設定します。ただし、スレッド プリンシパル オブジェクトはアプリケーション ドメインごとに 1 回しか設定できないため、静的フラグ m_ThreadPolicySet を使用して、既に設定されていないかどうかを最初に確認します (プリンシパルのアタッチやデタッチは可能です)。

if(m_ThreadPolicySet == false)
{
   m_ThreadPolicySet = true;
   currentDomain.SetThreadPrincipal(customPrincipal);
}

認証が 1 回限りのコストで済むのに対して、承認の処理は頻繁に行われる可能性があります。ロール メンバシップの確認はコストが高くなる場合があるため (データベースのクエリや Web サービスの呼び出しが行われる場合など)、CustomPrincipal は、ユーザーがメンバになっているすべてのロールをキャッシュできるようになっています。これは、ロールを m_Roles メンバ配列に保存することによって行われます。CustomPrincipal のコンストラクタは、cacheRoles というブール値のパラメータを受け取ります。cacheRoles が true だった場合は、指定されたユーザー マネージャの GetRoles メソッドを呼び出して m_Roles を初期化します。これにより、ほぼ一瞬でロール メンバシップを確認できるようになります。しかし残念ながら、ロールをキャッシュすると、ロール リポジトリに対する変更 (特定のロールからのユーザーの削除など) を CustomPrincipal が検出できなくなるという問題があります。したがって、キャッシュを使用するのは、ロールへのユーザーの割り当てがあまり行われない場合に、パフォーマンスとスケーラビリティのためにどうしても必要なときだけにしてください。cacheRoles パラメータを取らないバージョンの Attach が既定でキャッシュを使用しないようになっているのはこのためです。

IPrincipal を実装するのは簡単です。CustomPrincipal の Identity プロパティの実装では、保存された ID を返します。IsInRole を実装するには、キャッシュされているロールがあるかどうかを確認し、あった場合は、Array 型の静的メソッド Exists を使用してロールの配列を検索します。Exists は、Predicate 型のデリゲートを受け取ります。このデリゲートは次のように定義されています。

public delegate bool Predicate<T>(T t);

Exists は、配列の各項目について、predicate のターゲットとなるメソッドを評価し、一致した場合は true を返します。IsInRole は、predicate を匿名メソッドで初期化します。この匿名メソッドは、IsInRole への呼び出しで指定されたロールを、パラメータとして渡されたロールと比較します。キャッシュされているロールがなかった場合は、指定された IUserManager の実装にクエリをデリゲートして、その結果を返します。

Back to top

LoginControl クラス

LoginControl には、ユーザー名とパスワードを取得するための 2 つのテキストボックスがあります。また、このコントロールは ErrorProvider コンポーネントを使用します。ユーザー名とパスワードの両方が指定された場合にのみユーザーの認証を行い、それ以外の場合は ErrorProvider がユーザーに警告します。認証は、[Log In] ボタン の Click イベント処理メソッドで行われます。LoginControl のコードの一部を図 4 に示します (スペースの都合で一般的なコードが一部省略されています)。

LoginControl を使用する際には、使用する資格情報プロバイダ、アプリケーション名、およびロールのキャッシュ ポリシーを指定する必要があります。 資格情報プロバイダの指定は、LoginControl のサブクラスによって行われます。このサブクラスでは、GetUserManager をオーバーライドして、IUserManager の実装を返す必要があります。

LoginControl には、ApplicationName と CacheRoles というプロパティが用意されています。LoginControl は UserControl から派生しているため、ネイティブで Windows フォーム デザイナと統合されます。これら 2 つのプロパティは、LoginControl を使用するフォームやウィンドウのデザイン時にデザイナで視覚的に編集できます。デザイナのサポートを強化するために、これらのプロパティは、次のように Category 属性で修飾されています。

[Category("Credentials")]
デザイナでコントロールのプロパティの [Categories] ビューを選択すると、これらのプロパティが [Credentials] カテゴリの下にグループ化されます。LoginControl のサブクラスのプロパティをこのカテゴリに追加することもできます。CacheRoles プロパティは true または false に設定できます。LoginControl は、このプロパティをそのまま CustomPrincipal.Attach に渡します。設定を変更しない場合、CacheRoles は既定で false になります。

LoginControl は単独で使用することはできず、別のフォームやダイアログに含まれている必要があります。さらにそのコンテナは、さまざまなアプリケーション名を持つさまざまなアプリケーションで使用できます。このため、LoginControl では 2 つの方法でアプリケーション名を指定できるようになっています。まず、単純に ApplicationName プロパティを使用してアプリケーション名を指定する方法があります。アプリケーション名が事前にわからない場合 (汎用のコンテナを開発する場合) は、もう 1 つの方法として、コントロールを起動するために使用された Windows フォーム エントリ アプリケーション アセンブリからアプリケーション名を取得できます。認証の際に ApplicationName プロパティに値が設定されていなかった場合、LoginControl は、エントリ アセンブリのフレンドリ名をアプリケーション名として使用します。このロジックは、プライベートのヘルパー メソッド GetAppName にカプセル化されています。

Back to top

ユーザーの認証

ユーザーが [Log In] ボタンをクリックすると、OnLogin メソッドが呼び出されます。OnLogin は、ユーザー名とパスワードを確認した後、GetAppName を呼び出してアプリケーション名を取得します。その後、GetUserManager を呼び出して IUserManager の実装を取得します。認証そのものは、単純にユーザー マネージャの Authenticate メソッドを呼び出すことによって行われます。認証が成功した場合は、ユーザー名を汎用 ID オブジェクトでラップし、カスタム プリンシパルをアタッチします。OnLogin がカスタム プリンシパルと直接やり取りすることはありません。OnLogin はただ、IIdentity オブジェクト、アプリケーション名、キャッシュ ポリシー、および使用する IUserManager の実装を渡すだけです。

ここで問題になるのが、認証の後の LoginControl の動作です。ホスト コンテナで必要とされている動作はコントロールにはわかりません。認証が失敗した場合、ホスト コンテナでは、メッセージ ボックスを表示したり例外をスローしたりすることが考えられます。認証が成功した場合には、ホスト ダイアログを閉じる、ウィザードの次の画面に移動する、などの動作が考えられます。認証が成功した場合についても失敗した場合についても、次に何をすべきかを知っているのはホスト アプリケーションだけなので、LoginControl にできることは、イベントを発生させて通知することだけです。LoginControl は、EventHandler<LoginEventArgs> 型のイベント LoginEvent を宣言します。LoginEventArgs には、認証の結果を示す Authenticated というブール型のプロパティが含まれています。マルチスレッド競合によって、発行の直前に別のスレッドによって LoginEvent からすべてのサブスクライバが削除されるのを防ぐために、LoginControl は、LoginEvent を匿名メソッドで初期化します。

LoginControl には、このほかにも便利なヘルパーが 2 つ用意されています。ブール型の静的プロパティ IsLoggedIn と、静的メソッド Logout です。IsLoggedIn を使用すると、呼び出し元は、ユーザーがログインしているかどうかを問い合わせることができます。LoginControl は現在のプリンシパルを取得し、CustomPrincipal 型かどうかを確認します。ユーザーがログインしている場合は、当然、使用されているプリンシパルが CustomPrincipal になります。Logout メソッドを使用すると、ユーザーがログアウトできます。Logout は、現在のプリンシパルを取得し、CustomPrincipal 型だった場合は現在のスレッドからデタッチします。既に説明したように、CustomPrincipal に対して Detach を呼び出すと、前のプリンシパルが回復されます。なお、複数のスレッドが使用されている場合は、それぞれについてログアウトする必要があるので注意してください。

Back to top

AspNetLoginControl

では、ここで、筆者が作成した AspNetLoginControl の定義を見てみましょう。

public partial class AspNetLoginControl : LoginControl
{
   protected override IUserManager GetUserManager()
   {
      return new AspNetUserManager();
   }
}
AspNetLoginControl は LoginControl から派生しており、GetUserManager をオーバーライドして、IUserManager の実装 AspNetUserManager を返します。AspNetUserManager は、ASP.NET のプロバイダを直接使用します (図 5 を参照)。

図 5 AspNetLoginControl
図 5 AspNetLoginControl

AspNetUserManager は、その名のとおり、任意の有効な ASP.NET 2.0 プロバイダを使用できます。AspNetLoginControl を使用するには、アプリケーションの構成ファイルに、選択するプロバイダやプロバイダ固有の値と設定など、Web アプリケーション構成ファイルの場合と同じ値を追加する必要があります。

たとえば、SQL Server プロバイダを使用するには、図 6 のような設定をアプリケーション構成ファイルに追加する必要があります。図 6 の接続文字列の値は、既定のインストールの後にローカル コンピュータの aspnetdb データベースに接続するために使用されます。承認を有効にするために roleManager タグの enabled 属性が使用されている点に注意してください。

Visual Studio 2005 によって生成されるアプリケーション構成ファイルには、既定で、NTFS セキュリティによるアクセス許可のセキュリティが適用されます。したがって、プロバイダの設定を変更できるのはアプリケーション管理者だけです。

Back to top

IUserManager の実装

AspNetUserManager には、ASP.NET のメンバシップ プロバイダとロール プロバイダのインスタンスがメンバ変数として含まれています。IUserManager のすべての機能は、ASP.NET プロバイダにデリゲートすることによって実現されます (図 7 を参照)。

Authenticate を実装するために、AspNetUserManager は Membership の ValidateUser メソッドを呼び出します。また、IsInRole と GetRoles を実装するために、Roles の IsUserInRole メソッドと GetRolesForUser メソッドを呼び出します。

1 つ注意する必要があるのは、このコードはスレッドセーフではなく、別のスレッドによって membership.ApplicationName が変更された場合に対応できないということです。このため、このコードを問題なく使用できるのは、マルチスレッド アクセスが必要とされない場合だけです。

Back to top

AspNetLoginControl とコード アクセス セキュリティ

AspNetLoginControl は便利で、もちろん実際に使用できますが、重要な欠点が 1 つあります。ASP.NET のプロバイダはサーバーで使用するように作られているため、いくつかの重要なアクセス許可 (アンマネージ コード アクセス、無制限の SQL Server クライアント アクセス、最小限の ASP.NET ホスト アクセス許可など) を必要とします。サーバー側のアプリケーションは一般に高い信頼環境で実行されるため (完全信頼が使用される場合さえあります)、これらのアクセス許可が問題になることもなく正常に実行されます。

これに対して、AspNetLoginControl は Windows フォーム アプリケーションによって使用されます。一般にこれらのアプリケーションは、ClickOnce アプリケーションとして実行されるなど、部分信頼の環境で実行されると考えられます。クライアント環境では、AspNetLoginControl を使用するアプリケーションに対して必要なアクセス許可が与えられない可能性が非常に高くなります。

図 8 は、現在 AspNetLoginControl に必要なアクセス許可と、それを必要とするコンポーネントの一覧です。これらのアクセス許可は、このコントロール自体とそれが使用するコンポーネント (CustomPrincipal と AspNetUserManager) の両方に由来するものです。

これらのアクセス許可の要件に対処するには、いくつかの選択肢があります。WinFormsEx.dll アセンブリとそのすべてのクライアントに対して完全信頼を与えることもできますが、アセンブリによって本来とは違う操作が行われる可能性が生じるため、お勧めできません。

.NET 構成ツールを使用すると、WinFormsEx.dll アセンブリとそれを使用するすべてのクライアント アプリケーションの両方に、これらのアクセス許可のほとんどを与えることができます。さらに、これらのアクセス許可を ClickOnce アプリケーション マニフェストに含めることもできます。この記事に付属のソース ファイルの中には、簡単にアクセス許可を与えることができるように、WinFormsEx.xml というファイルが含まれています。このファイルはカスタム アクセス許可セット ファイルで、これを .NET 構成ツールに追加することによって、必要なアクセス許可を与えることができます。

ただし、最善の解決策はほかにあります。それは、ASP.NET プロバイダによって必要とされるアクセス許可の一切を回避して、クライアント環境でサーバー側のアクセス許可が必要とされることのない別の IUserManager の実装を使用することです。次のセクションでは、この方法を紹介します。なお、ASP.NET チームは現在、製品の最終的なリリースに先立って、必要とされるアクセス許可を削減するための調査を行っています。

Back to top

UserManager Web サービス

部分信頼の問題に対する解決策として、ASP.NET プロバイダを Web サービスでラップします。Web サービスを使用した場合、プロバイダによって必要とされるセキュリティのアクセス許可がクライアントまでさかのぼって要求されることはありません。

Web サービスを使用すると、個々のクライアント アプリケーションではなく Web サービスのみがデータベースへの接続を使用するようになるため、スケーラビリティが向上するというメリットもあります。さらにもう 1 つのメリットとして、クライアントが直接 SQL Server に対して認証を行ったり、クライアント側で接続文字列を管理したりすることに伴う潜在的なセキュリティの問題も回避できます。

しかし、Web サービスを使用することにはマイナスの面もいくつかあります。たとえば、クライアントがネットワーク経由で資格情報を送信することになるため、クライアントと Web サービスの間の通信をセキュリティで保護する必要があります。これは、HTTPS を使用することで簡単に解決できます。また、呼び出し待ち時間が余分に発生するという問題もあります。この問題は、ロールのキャッシュを使用して解決する必要があります。このほか、Web サービス自体に対する認証の問題もありますが、イントラネット環境でサービスへの匿名アクセスを利用できる場合はそれほど問題になりません。最後に、Web サービス呼び出しの承認が問題になる可能性があります。この Web サービスを使用すると、呼び出し元はユーザーに関するロールの情報を取得できますが、 ロール メンバシップの情報は、それ自体が機密情報である可能性があります。この問題は、Web サービスにロールベースのセキュリティを追加して呼び出し元を承認することで対処できます。ただし、承認は認証を必要とすることに注意してください。

WebServiceBinding 属性を使用すると、IUserManager を Web サービス インターフェイスとして定義し、実装することができます (図 9 を参照)。図 9 の UserManager クラスは、Membership と Roles の両方を使用して、設定されているプロバイダを Web サービス構成ファイルから取得します。UserManager の各 Web サービス メソッドは、使用するアプリケーションの名前も受け取ります。これにより、1 つの Web サービスでさまざまな Windows フォーム アプリケーションをサポートできます。

Back to top

WSLoginControl

WinFormsEx.dll には、次のような WSLoginControl の定義が含まれています。

public partial class WSLoginControl : LoginControl
{
   protected override IUserManager GetUserManager()
   {
      return new UserManager();
   }
}

WSLoginControl は LoginControl から派生しており、GetUserManager をオーバーライドして UserManager を返します。UserManager は、UserManager Web サービスを呼び出すために使用されるクライアント側の Web サービス プロキシ クラスです。WSLoginControl はその名のとおり、IUserManager インターフェイスさえサポートされていれば、ユーザーの資格情報を管理する任意の Web サービスを使用できます。プロキシ クラスを生成するには、UserManager Web サービスへの Web 参照を追加します。その後、自動的に生成された UserManager Web サービスのプロキシ クラスを、IUserManager から派生するように変更する必要があります。プロキシ クラスはパーシャル クラスで、自動的に生成されるため、そのコードは別のファイルに追加することが望ましいです。

partial class UserManager : IUserManager
{}
WinFormsEx.dll には、UserManager Web サービスのプロキシ クラスの定義が既に含まれています。プロキシ クラスの自動生成コードは、アプリケーション構成ファイルで Web サービスのアドレスを調べます。アプリケーション構成ファイルの appSettings セクションに、Web サービスのアドレスを値とする UserManager というキーを追加します。
<?xml version="1.0"?>
<configuration>
   <appSettings>
      <add key="UserManager" 
           value="http://localhost/SecurityServices/UserManager.asmx"/>
   </appSettings>
</configuration>

図 10 WSLoginControl とそのサポート クラス
図 10 WSLoginControl とそのサポート クラス

図 10 は、WSLoginControl、CustomPrincipal、およびそれらと UserManager Web サービスとのやり取りを表しています。

Back to top

WSLoginControl とコード アクセス セキュリティ

Web サービスを使用すると、ASP.NET プロバイダのアクセス許可の要件をサーバー側に限定することができます。その代償として、ユーザー管理 Web サービスに接続するための Web アクセス許可をクライアントに与える必要があります。そのほか、LoginControl と CustomPrincipal によって必要とされるその他のアクセス許可も与える必要があります。図 11 は、資格情報管理 Web サービスを使用する場合に必要となるアクセス許可の一覧です。Web サービスを使用しない場合に比べて少なくなっています。

.NET 構成ツールを使用すると、WinFormsEx.dll アセンブリとそれを使用するすべてのクライアント アプリケーションの両方に、図 11 のすべてのアクセス許可を与えることができます。さらに、Visual Studio 2005 を使用して、これらのアクセス許可を ClickOnce アプリケーション マニフェストに含めることもできます。たとえば、Visual Studio 2005 で、WSLoginControl を使用する ClickOnce アプリケーションのプロジェクト設定の [Security] タブに移動し、 [Enable ClickOnce Security Settings] チェック ボックスと [This is a partial trust application] チェック ボックスをオンにします。次に、[Zone your application will be installed from] で、[(Custom)] を選択します。これで、実行を除くすべてのアクセス許可が削除されます。 次に、[SecurityPermission] を選択し、[Settings] で [Include] を選択します。[Properties] をクリックして [Permission Settings] ダイアログ ボックスを開きます。アセンブリの実行とプリンシパルの制御のアクセス許可を選択し、[OK] をクリックします。Web サービスを呼び出すためのアクセス許可を与えるには、WebPermission を含め、そのプロパティで UserManager の URL を指定して、アプリケーションがそこに接続できるようにします。

同様のマネージャで、図 8 の一覧に含まれているユーザー インターフェイスのアクセス許可を与えます。これで、この ClickOnce アプリケーションを発行すると、そのアプリケーション マニフェストにこれらのアクセス許可が含まれます。Visual Studio 2005 を使用して、これらの部分信頼の設定でアプリケーションをデバッグすることもできます。

Back to top

サンプル アプリケーション

図 12 は、WSLoginControl を使用するサンプル Windows フォーム MDI (マルチ ドキュメント インターフェイス) アプリケーションです。このアプリケーションは、[Security] タブで図 11 のアクセス許可を必要とします。ユーザーは、ログインと認証を済ませてからでないと新しいドキュメント ウィンドウを作成できません。 ユーザーが [File] メニューの [New] をクリックすると、OnFileNew メソッドが実行されます。このメソッドは、ユーザーが Manager ロールのメンバであることを要求します。

[PrincipalPermission(SecurityAction.Demand,Role = "Manager")] 
void OnFileNew(object sender,EventArgs args)
{...}
このアプリケーションを使用する前に、データベースでユーザーとロールを作成する必要があります。aspnetdb データベースに格納されている資格情報は、Visual Studio 2005 のサポート機能を使って管理できます (この機能を使って管理するようにしてください)。

図 12 サンプル アプリケーション
図 12 サンプル アプリケーション

新しい ASP.NET Web サイトを作成します。[Web site] メニューの [ASP.NET Configuration] をクリックして、ASP.NET Web Site Administration Tool を開きます (図 1 を参照)。[Provider] タブをクリックし、[Select a single provider for all site management data] リンクをクリックします。[Provider] の一覧で、[AspNetSqlProvider] をクリックします。次に、[Security] タブをクリックします。[Users] で、[Select authentication type] をクリックし、次のページで [From the Internet] をクリックします。 [Security] タブに戻ります。

[Roles] で、[Enable Roles] をクリックし、続いて [Create roles] をクリックします。Manager という新しいロールを追加し、[Security] タブに戻ります。[Users] で、[Create user] をクリックし、ユーザー名とパスワードおよびその他の必要な情報を指定します。[Roles] で [Manager] チェック ボックスをオンにして、新しいユーザーを Manager ロールのメンバにするのを忘れないようにしてください。[Create User] をクリックし、Visual Studio 2005 を閉じます (またはユーザーとロールをさらに追加します)。Visual Studio 2005 は、既定のアプリケーション名としてスラッシュ ("/") を使用します。

Back to top

LoginDialog クラス

このサンプル アプリケーションには、[Security] というメニュー項目があります。ユーザーが [Security] メニューの [Log In] をクリックすると、LoginDialog ダイアログ ボックスが開きます。LoginDialog のコードを図 13 に示します。

LoginDialog には WSLoginControl が含まれています。Windows フォーム デザイナで LoginDialog を開くと、WSLoginControl のプロパティを設定できます。LoginDialog は、ApplicationName プロパティを "/" に、CacheRoles を False に設定します。アプリケーション構成ファイルは、UserManager Web サービスのアドレスを http://localhost/SecurityServices/UserManager.asmx に設定します。

LoginDialog は、LoginControl の LoginEvent イベントにサブスクライブし、ダイアログの OnLogin をイベント処理メソッドとして指定します。

m_LogInControl.LoginEvent += OnLogin;

OnLogin イベント処理メソッドでは、ユーザーがログインに失敗した場合に警告のメッセージ ボックスを表示します。ログインが成功した場合は、Authenticated というパブリック プロパティを true に設定し、ダイアログを閉じます。Authenticated は、LoginDialog のクライアントによって、認証の結果を調べるために使用されます。ユーザーがログインしないまま LoginDialog を閉じた場合、Authenticated は false になります。Authenticated のアクセサは public の get と protected の set になっているため、クライアントは値を取得することはできても設定することはできません。サンプル アプリケーションの [Security] メニューには、[Log Out] というオプションもあります。この実装は、LoginDialog の静的メソッド Logout を呼び出します。このメソッドは、LoginControl.Logout にデリゲートすることによって、CustomPrincipal を現在のスレッドからデタッチします。

このサンプル アプリケーションには、このほか、ユーザーがいつでもログインのステータスを確認できるようにするという要件もあります。そのために、タイマーを使用してステータス バーを更新します。タイマーの tick イベントが発生するたびに静的プロパティ LoginDialog.IsLoggedIn の値をチェックしてユーザーがログインしているかどうかを確認し、その結果に基づいてステータス バーを更新します。LoginDialog.IsLoggedIn は、単純に LoginControl.IsLoggedIn にデリゲートします。

Back to top

まとめ

資格情報管理ソリューションを提供するには、アプリケーションの配置やコード アクセス セキュリティの要件に加えて、スケーラビリティ、拡張性、デザイン時の統合、コンテキストの再利用までをも視野に入れた、総体的なアプローチが必要になります。

どのような理由であれ、Windows フォーム アプリケーションで Windows アカウントを使用できない場合は、ここで紹介した方法を使用すれば、綿密に設計された ASP.NET 2.0 のセキュリティ資格情報ストアを通じて、資格情報管理の最新のベスト プラクティスを利用できます。LoginControl では、ASP.NET と同じ生産性の高いプログラミング モデルはもちろん、統一された資格情報ストアも利用できます。これにより、さまざまなフロント エンドを同じ認証/承認インフラストラクチャで簡単に使用できる中間層が実現されます。

Back to top


Juval Lowy は、.NET アーキテクチャのコンサルタントや高度なトレーニングを提供しているソフトウェア アーキテクトです。また、マイクロソフトのシリコン バレー担当リジョナル ディレクターとしても働いています。この記事には、間もなく出版される彼の著書、『Programming .NET Components 2nd Edition』 (O'Reilly, 2005) からの抜粋が含まれています。連絡先は、www.idesign.net (英語) です。

 この記事は、 MSDN マガジン - 2005 年 4 月号からの翻訳です。 .
Back to topBack to top QJ: 050402

Microsoft