ASP.NET Identity のカスタム ストレージ プロバイダーの概要

Tom FitzMacken

ASP.NET ID は拡張可能なシステムであり、アプリケーションを再作業することなく、独自のストレージ プロバイダーを作成し、アプリケーションにプラグインできます。 このトピックでは、ASP.NET ID 用にカスタマイズされたストレージ プロバイダーを作成する方法について説明します。 独自のストレージ プロバイダーを作成するための重要な概念について説明しますが、カスタム ストレージ プロバイダーを実装する詳細なチュートリアルではありません。

カスタム ストレージ プロバイダーの実装例については、「 Custom MySQL ASP.NET Identity Storage Provider の実装」を参照してください。

このトピックは、ASP.NET Identity 2.0 用に更新されました。

チュートリアルで使用するソフトウェアのバージョン

  • Update 2 を使用したVisual Studio 2013
  • ASP.NET ID 2

はじめに

既定では、ASP.NET ID システムはユーザー情報をSQL Server データベースに格納し、Entity Framework Code First を使用してデータベースを作成します。 多くのアプリケーションでは、このアプローチは適切に機能します。 ただし、Azure Table Storage などの別の種類の永続化メカニズムを使用する場合や、既定の実装とは構造が非常に異なるデータベース テーブルが既にある場合があります。 どちらの場合も、ストレージ メカニズム用にカスタマイズされたプロバイダーを作成し、そのプロバイダーをアプリケーションに接続できます。

ASP.NET ID は、多くのVisual Studio 2013 テンプレートに既定で含まれています。 ASP.NET ID の更新は、 Microsoft AspNet Identity EntityFramework NuGet パッケージを通じて取得できます。

このトピックのセクションは次のとおりです。

アーキテクチャを理解する

ASP.NET ID は、マネージャーとストアと呼ばれるクラスで構成されます。 マネージャーは、アプリケーション開発者が ASP.NET ID システムでユーザーの作成などの操作を実行するために使用する高レベルのクラスです。 "ストア" は、ユーザーやロールなどのエンティティを永続化する方法を指定する下位レベルのクラスです。 ストアは永続化メカニズムと密接に結合されますが、マネージャーはストアから切り離されるため、アプリケーション全体を中断することなく永続化メカニズムを置き換えることができます。

次の図は、Web アプリケーションがマネージャーとやり取りし、ストアがデータ アクセス層と対話する方法を示しています。

Web アプリケーションがマネージャーとやり取りする方法を示す図

ASP.NET ID 用にカスタマイズされたストレージ プロバイダーを作成するには、データ ソース、データ アクセス層、およびこのデータ アクセス層と対話するストア クラスを作成する必要があります。 同じマネージャー API を引き続き使用してユーザーに対してデータ操作を実行できますが、これでデータは別のストレージ システムに保存されます。

UserManager または RoleManager の新しいインスタンスを作成するときに、ユーザー クラスの型を指定し、ストア クラスのインスタンスを引数として渡すので、マネージャー クラスをカスタマイズする必要はありません。 この方法を使用すると、カスタマイズしたクラスを既存の構造体に接続できます。 カスタマイズしたストア クラスを使用して UserManager と RoleManager をインスタンス化する方法については、「 新しいストレージ プロバイダーを使用するようにアプリケーションを再構成する」セクションを参照してください。

格納されているデータを理解する

カスタム ストレージ プロバイダーを実装するには、ASP.NET ID で使用されるデータの種類を理解し、アプリケーションに関連する機能を決定する必要があります。

Data 説明
ユーザー Web サイトの登録済みユーザー。 ユーザー ID とユーザー名が含まれます。 ユーザーがサイトに固有の資格情報 (Facebook などの外部サイトからの資格情報を使用するのではなく) を使用してログインする場合は、ハッシュされたパスワードと、ユーザーの資格情報に何かが変更されたかどうかを示すセキュリティ スタンプが含まれる場合があります。 電子メール アドレス、電話番号、2 要素認証が有効になっているかどうか、失敗したログインの現在の数、アカウントがロックされているかどうかも含まれます。
ユーザー要求 ユーザーの ID を表すユーザーに関するステートメント (またはクレーム) のセット。 ロールで実現できるよりも優れたユーザー ID の表現を実現できます。
ユーザー ログイン ユーザーのログイン時に使用する外部認証プロバイダー (Facebook など) に関する情報。
ロール サイトの認可グループ。 ロール ID とロール名 ("Admin" や "Employee" など) が含まれます。

データ アクセス層を作成する

このトピックは、使用する永続化メカニズムと、そのメカニズムのエンティティを作成する方法を理解していることを前提としています。 このトピックでは、リポジトリまたはデータ アクセス クラスを作成する方法の詳細については説明しません。代わりに、ASP.NET ID を操作するときに行う必要がある設計上の決定に関するいくつかの提案が提供されます。

カスタマイズされたストア プロバイダーのリポジトリを設計する際には、多くの自由があります。 必要なのは、アプリケーションで使用する機能のリポジトリを作成することだけです。 たとえば、アプリケーションでロールを使用していない場合は、ロールまたはユーザー ロールのストレージを作成する必要はありません。 テクノロジと既存のインフラストラクチャには、ASP.NET ID の既定の実装とは大きく異なる構造が必要な場合があります。 データ アクセス層では、リポジトリの構造を操作するためのロジックを提供します。

ASP.NET Identity 2.0 のデータ リポジトリの MySQL 実装については、「 MySQLIdentity.sql」を参照してください。

データ アクセス層では、ASP.NET ID からデータ ソースにデータを保存するロジックを提供します。 カスタマイズした記憶域プロバイダーのデータ アクセス層には、ユーザーとロールの情報を格納する次のクラスが含まれる場合があります。

クラス 説明
Context 永続化メカニズムに接続してクエリを実行するための情報をカプセル化します。 このクラスは、データ アクセス層の中心です。 他のデータ クラスでは、このクラスのインスタンスが操作を実行する必要があります。 また、このクラスのインスタンスを使用してストア クラスを初期化します。 MySQLDatabase
ユーザー記憶域 ユーザー情報 (ユーザー名やパスワード ハッシュなど) を格納および取得します。 UserTable (MySQL)
ロール記憶域 ロール情報 (ロール名など) を格納および取得します。 RoleTable (MySQL)
UserClaims 記憶域 ユーザー要求情報 (要求の種類や値など) を格納および取得します。 UserClaimsTable (MySQL)
UserLogins 記憶域 ユーザー ログイン情報 (外部認証プロバイダーなど) を格納および取得します。 UserLoginsTable (MySQL)
UserRole 記憶域 ユーザーが割り当てられているロールを格納および取得します。 UserRoleTable (MySQL)

ここでも、アプリケーションで使用するクラスのみを実装する必要があります。

データ アクセス クラスでは、特定の永続化メカニズムに対してデータ操作を実行するコードを提供します。 たとえば、MySQL 実装内では、UserTable クラスに、Users データベース テーブルに新しいレコードを挿入するメソッドが含まれています。 という名前 _database の変数は、MySQLDatabase クラスのインスタンスです。

public int Insert(TUser user)
{
    string commandText = @"Insert into Users (UserName, Id, PasswordHash, SecurityStamp,Email,EmailConfirmed,PhoneNumber,PhoneNumberConfirmed, AccessFailedCount,LockoutEnabled,LockoutEndDateUtc,TwoFactorEnabled)
        values (@name, @id, @pwdHash, @SecStamp,@email,@emailconfirmed,@phonenumber,@phonenumberconfirmed,@accesscount,@lockoutenabled,@lockoutenddate,@twofactorenabled)";
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("@name", user.UserName);
    parameters.Add("@id", user.Id);
    parameters.Add("@pwdHash", user.PasswordHash);
    parameters.Add("@SecStamp", user.SecurityStamp);
    parameters.Add("@email", user.Email);
    parameters.Add("@emailconfirmed", user.EmailConfirmed);
    parameters.Add("@phonenumber", user.PhoneNumber);
    parameters.Add("@phonenumberconfirmed", user.PhoneNumberConfirmed);
    parameters.Add("@accesscount", user.AccessFailedCount);
    parameters.Add("@lockoutenabled", user.LockoutEnabled);
    parameters.Add("@lockoutenddate", user.LockoutEndDateUtc);
    parameters.Add("@twofactorenabled", user.TwoFactorEnabled);

    return _database.Execute(commandText, parameters);
}

データ アクセス クラスを作成した後、データ アクセス層の特定のメソッドを呼び出すストア クラスを作成する必要があります。

ユーザー クラスのカスタマイズ

独自のストレージ プロバイダーを実装する場合は、Microsoft.ASP.NET.Identity.EntityFramework 名前空間の IdentityUser クラスと同じユーザー クラスを作成する必要があります。

次の図は、作成する必要がある IdentityUser クラスと、このクラスに実装するインターフェイスを示しています。

Identity User クラスの画像

IUser<TKey> インターフェイスは、要求された操作の実行時に UserManager が呼び出そうとするプロパティを定義します。 インターフェイスには、Id と UserName の 2 つのプロパティが含まれています。 IUser<TKey インターフェイスを使用すると、ジェネリック TKey> パラメーターを使用して、ユーザーのキーの種類を指定できます。 Id プロパティの型は、TKey パラメーターの値と一致します。

また、Id フレームワークは、キーに文字列値を使用する場合に IUser インターフェイス (ジェネリック パラメーターなし) も提供します。

IdentityUser クラスは IUser を実装し、Web サイト上のユーザーの追加のプロパティまたはコンストラクターを含みます。 次の例は、キーに整数を使用する IdentityUser クラスを示しています。 Id フィールドは、ジェネリック パラメーターの値と一致するように int に設定されます。

public class IdentityUser : IUser<int>
{
    public IdentityUser() { ... }
    public IdentityUser(string userName) { ... }
    public int Id { get; set; }
    public string UserName { get; set; }
    // can also define optional properties such as:
    //    PasswordHash
    //    SecurityStamp
    //    Claims
    //    Logins
    //    Roles
}

完全な実装については、「 IdentityUser (MySQL)」を参照してください。

ユーザー ストアのカスタマイズ

また、ユーザーに対するすべてのデータ操作のメソッドを提供する UserStore クラスも作成します。 このクラスは、Microsoft.ASP.NET.Identity.EntityFramework 名前空間の UserStore<TUser> クラスと同じです。 UserStore クラスでは、IUserStore<TUser、TKey>、および任意のインターフェイスを実装します。 アプリケーションで提供する機能に基づいて、実装する省略可能なインターフェイスを選択します。

次の図は、作成する必要がある UserStore クラスと関連するインターフェイスを示しています。

User Store クラスの画像

Visual Studio の既定のプロジェクト テンプレートには、多くの省略可能なインターフェイスがユーザー ストアに実装されていることを前提とするコードが含まれています。 カスタマイズされたユーザー ストアで既定のテンプレートを使用している場合は、ユーザー ストアにオプションのインターフェイスを実装するか、実装していないインターフェイスでメソッドを呼び出さないようにテンプレート コードを変更する必要があります。

次の例は、単純なユーザー ストア クラスを示しています。 TUser ジェネリック パラメーターは、通常、定義した IdentityUser クラスであるユーザー クラスの型を受け取ります。 TKey ジェネリック パラメーターは、ユーザー キーの型を受け取ります。

public class UserStore : IUserStore<IdentityUser, int>
{
    public UserStore() { ... }
    public UserStore(ExampleStorage database) { ... }
    public Task CreateAsync(IdentityUser user) { ... }
    public Task DeleteAsync(IdentityUser user) { ... }
    public Task<IdentityUser> FindByIdAsync(int userId) { ... }
    public Task<IdentityUser> FindByNameAsync(string userName) { ... }
    public Task UpdateAsync(IdentityUser user) { ... }
    public void Dispose() { ... }
}

この例では、ExampleDatabase 型の database という名前のパラメーターを受け取るコンストラクターは、データ アクセス クラスを渡す方法の図にすぎません。 たとえば、MySQL 実装では、このコンストラクターは MySQLDatabase 型のパラメーターを受け取ります。

UserStore クラス内では、作成したデータ アクセス クラスを使用して操作を実行します。 たとえば、MySQL 実装では、UserStore クラスには、UserTable のインスタンスを使用して新しいレコードを挿入する CreateAsync メソッドがあります。 userTable オブジェクトの Insert メソッドは、前のセクションで示したメソッドと同じです。

public Task CreateAsync(IdentityUser user)
{
    if (user == null) {
        throw new ArgumentNullException("user");
    }

    userTable.Insert(user);

    return Task.FromResult<object>(null);
}

ユーザー ストアをカスタマイズするときに実装するインターフェイス

次の図は、各インターフェイスで定義されている機能の詳細を示しています。 オプションのすべてのインターフェイスは、IUserStore から継承されます。

各インターフェイスで定義されている機能の詳細を示す図

  • IUserStore
    IUserStore<TUser、TKey> インターフェイスは、ユーザー ストアに実装する必要がある唯一のインターフェイスです。 ユーザーの作成、更新、削除、取得を行うためのメソッドを定義します。

  • IUserClaimStore
    IUserClaimStore<TUser,TKey> インターフェイスは、ユーザー 要求を有効にするためにユーザー ストアに実装する必要があるメソッドを定義します。 これには、メソッドまたはユーザー要求の追加、削除、および取得が含まれます。

  • IUserLoginStore
    IUserLoginStore<TUser,TKey> は、外部認証プロバイダーを有効にするためにユーザー ストアに実装する必要があるメソッドを定義します。 ユーザー ログインを追加、削除、取得するためのメソッドと、ログイン情報に基づいてユーザーを取得するためのメソッドが含まれています。

  • IUserRoleStore
    IUserRoleStore<TKey、TUser> インターフェイスは、ユーザーをロールにマップするためにユーザー ストアに実装する必要があるメソッドを定義します。 ユーザーのロールを追加、削除、取得するメソッドと、ユーザーがロールに割り当てられているかどうかを確認するメソッドが含まれています。

  • IUserPasswordStore
    IUserPasswordStore<TUser,TKey> インターフェイスは、ハッシュされたパスワードを保持するためにユーザー ストアに実装する必要があるメソッドを定義します。 ハッシュされたパスワードを取得および設定するためのメソッドと、ユーザーがパスワードを設定したかどうかを示すメソッドが含まれています。

  • IUserSecurityStampStore
    IUserSecurityStampStore<TUser,TKey> インターフェイスは、ユーザーのアカウント情報が変更されたかどうかを示すためにセキュリティ スタンプを使用するためにユーザー ストアに実装する必要があるメソッドを定義します。 このスタンプは、ユーザーがパスワードを変更したとき、またはログインを追加または削除したときに更新されます。 セキュリティ スタンプを取得および設定するためのメソッドが含まれています。

  • IUserTwoFactorStore
    IUserTwoFactorStore<TUser,TKey> インターフェイスは、2 要素認証を実装するために実装する必要があるメソッドを定義します。 ユーザーに対して 2 要素認証が有効にされているかどうかを取得および設定するためのメソッドが含まれています。

  • IUserPhoneNumberStore
    IUserPhoneNumberStore<TUser,TKey> インターフェイスは、ユーザーの電話番号を格納するために実装する必要があるメソッドを定義します。 電話番号と、電話番号が確認済みかどうかを取得および設定するためのメソッドが含まれています。

  • IUserEmailStore
    IUserEmailStore<TUser,TKey> インターフェイスは、ユーザーの電子メール アドレスを格納するために実装する必要があるメソッドを定義します。 メール アドレスと、メールが確認済みかどうかを取得および設定するためのメソッドが含まれています。

  • IUserLockoutStore
    IUserLockoutStore<TUser,TKey> インターフェイスは、アカウントのロックに関する情報を格納するために実装する必要があるメソッドを定義します。 これには、失敗したアクセス試行の現在の数を取得し、アカウントをロックできるかどうかを取得および設定する、ロックアウト終了日を取得および設定する、失敗した試行の数を増やす、失敗した試行の数をリセットするメソッドが含まれています。

  • IQueryableUserStore
    IQueryableUserStore<TUser,TKey> インターフェイスは、クエリ可能なユーザー ストアを提供するために実装する必要があるメンバーを定義します。 これには、クエリ可能なユーザーを保持するプロパティが含まれています。

    アプリケーションで必要なインターフェイスを実装します。次に示すように、IUserClaimStore、IUserLoginStore、IUserRoleStore、IUserPasswordStore、および IUserSecurityStampStore インターフェイスなど。

public class UserStore : IUserStore<IdentityUser, int>,
                         IUserClaimStore<IdentityUser, int>,
                         IUserLoginStore<IdentityUser, int>,
                         IUserRoleStore<IdentityUser, int>,
                         IUserPasswordStore<IdentityUser, int>,
                         IUserSecurityStampStore<IdentityUser, int>
{
    // interface implementations not shown
}

完全な実装 (すべてのインターフェイスを含む) については、「 UserStore (MySQL)」を参照してください。

IdentityUserClaim、IdentityUserLogin、IdentityUserRole

Microsoft.AspNet.Identity.EntityFramework 名前空間には、IdentityUserClaimIdentityUserLogin、IdentityUserRole クラスの実装が含まれています。 これらの機能を使用している場合は、これらのクラスの独自のバージョンを作成し、アプリケーションのプロパティを定義することができます。 ただし、基本的な操作 (ユーザーの要求の追加や削除など) を実行するときに、これらのエンティティをメモリに読み込まない方が効率的な場合があります。 代わりに、バックエンド ストア クラスを使用して、データ ソースに対してこれらの操作を直接実行できます。 たとえば、UserStore.GetClaimsAsync() メソッドは userClaimTable.FindByUserId(user) を呼び出すことができます。ID) メソッドを使用して、そのテーブルに対してクエリを直接実行し、要求の一覧を返します。

public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
    ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
    return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}

ロール クラスのカスタマイズ

独自のストレージ プロバイダーを実装する場合は、Microsoft.ASP.NET.Identity.EntityFramework 名前空間の IdentityRole クラスと同じロール クラスを作成する必要があります。

次の図は、作成する必要がある IdentityRole クラスと、このクラスに実装するインターフェイスを示しています。

Identity Role クラスの画像

IRole<TKey> インターフェイスは、要求された操作の実行時に RoleManager が呼び出そうとするプロパティを定義します。 インターフェイスには、Id と Name の 2 つのプロパティが含まれています。 IRole<TKey インターフェイスを使用すると、ジェネリック TKey> パラメーターを使用してロールのキーの種類を指定できます。 Id プロパティの型は、TKey パラメーターの値と一致します。

また、Id フレームワークでは、キーに文字列値を使用する場合に IRole インターフェイス (ジェネリック パラメーターなし) も提供されます。

次の例は、キーに整数を使用する IdentityRole クラスを示しています。 Id フィールドは、ジェネリック パラメーターの値と一致するように int に設定されます。

public class IdentityRole : IRole<int>
{
    public IdentityRole() { ... }
    public IdentityRole(string roleName) { ... }
    public int Id { get; set; }
    public string Name { get; set; }
}

完全な実装については、「 IdentityRole (MySQL)」を参照してください。

ロール ストアのカスタマイズ

ロールに対するすべてのデータ操作のメソッドを提供する RoleStore クラスも作成します。 このクラスは、Microsoft.ASP.NET.Identity.EntityFramework 名前空間の RoleStore<TRole> クラスと同じです。 RoleStore クラスでは、IRoleStore<TRole、TKey>、および必要に応じて IQueryableRoleStore<TRole、TKey> インターフェイスを実装します。

ロール ストア クラスを示す画像

次の例は、ロール ストア クラスを示しています。 TRole ジェネリック パラメーターは、ロール クラスの型を受け取ります。これは通常、定義した IdentityRole クラスです。 TKey ジェネリック パラメーターは、ロール キーの型を受け取ります。

public class RoleStore : IRoleStore<IdentityRole, int>
{
    public RoleStore() { ... }
    public RoleStore(ExampleStorage database) { ... }
    public Task CreateAsync(IdentityRole role) { ... }
    public Task DeleteAsync(IdentityRole role) { ... }
    public Task<IdentityRole> FindByIdAsync(int roleId) { ... }
    public Task<IdentityRole> FindByNameAsync(string roleName) { ... }
    public Task UpdateAsync(IdentityRole role) { ... }
    public void Dispose() { ... }
}
  • IRoleStore<TRole>
    IRoleStore インターフェイスは、ロール ストア クラスに実装するメソッドを定義します。 これには、ロールの作成、更新、削除、および取得のためのメソッドが含まれています。

  • RoleStore<TRole>
    RoleStore をカスタマイズするには、IRoleStore インターフェイスを実装するクラスを作成します。 システムでロールを使用する場合にのみ、このクラスを実装する必要があります。 ExampleDatabase 型の database という名前のパラメーターを受け取るコンストラクターは、データ アクセス クラスを渡す方法の図にすぎません。 たとえば、MySQL 実装では、このコンストラクターは MySQLDatabase 型のパラメーターを受け取ります。

    完全な実装については、 RoleStore (MySQL) に関するページを参照してください。

新しいストレージ プロバイダーを使用するようにアプリケーションを再構成する

新しいストレージ プロバイダーを実装しました。 ここで、このストレージ プロバイダーを使用するようにアプリケーションを構成する必要があります。 既定のストレージ プロバイダーがプロジェクトに含まれていた場合は、既定のプロバイダーを削除し、プロバイダーに置き換える必要があります。

MVC プロジェクトの既定のストレージ プロバイダーを置き換える

  1. [ NuGet パッケージの管理 ] ウィンドウで、 Microsoft ASP.NET Identity EntityFramework パッケージを アンインストールします。 このパッケージを見つけるには、Identity.EntityFramework のインストール済みパッケージを検索します。
    Nu Get パッケージ ウィンドウの画像 Entity Framework もアンインストールするかどうかを確認するメッセージが表示されます。 アプリケーションの他の部分で不要な場合は、アンインストールできます。

  2. Models フォルダー内の IdentityModels.cs ファイルで、 ApplicationUser クラスと ApplicationDbContext クラスを削除するかコメントアウトします。 MVC アプリケーションでは、IdentityModels.cs ファイル全体を削除できます。 Web Forms アプリケーションで、2 つのクラスを削除しますが、IdentityModels.cs ファイルにも配置されているヘルパー クラスを保持してください。

  3. ストレージ プロバイダーが別のプロジェクトに存在する場合は、Web アプリケーションに参照を追加します。

  4. using Microsoft.AspNet.Identity.EntityFramework; へのすべての参照を、記憶域プロバイダーの名前空間の using ステートメントに置き換えます。

  5. Startup.Auth.cs クラスで、適切なコンテキストの単一インスタンスを使用するように ConfigureAuth メソッドを変更します。

    public void ConfigureAuth(IAppBuilder app)
    {
        app.CreatePerOwinContext(ExampleStorageContext.Create);
        app.CreatePerOwinContext(ApplicationUserManager.Create);
        ...
    
  6. App_Start フォルダーで IdentityConfig.cs を開きます。 ApplicationUserManager クラスで、 Create メソッドを変更して、カスタマイズしたユーザー ストアを使用するユーザー マネージャーを返します。

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
    {
        var manager = new ApplicationUserManager(new UserStore(context.Get<ExampleStorageContext>()));
        ...
    }
    
  7. ApplicationUser へのすべての参照を IdentityUser に置き換えます。

  8. 既定のプロジェクトには、IUser インターフェイスで定義されていない一部のメンバーがユーザー クラスに含まれています。Email、PasswordHash、GenerateUserIdentityAsync などです。 ユーザー クラスにこれらのメンバーがない場合は、それらを実装するか、これらのメンバーを使用するコードを変更する必要があります。

  9. RoleManager のインスタンスを作成した場合は、新しい RoleStore クラスを使用するようにそのコードを変更します。

    var roleManager = new RoleManager<IdentityRole>(new RoleStore(context.Get<ExampleStorageContext>()));
    
  10. 既定のプロジェクトは、キーの文字列値を持つユーザー クラス用に設計されています。 ユーザー クラスのキーの型 (整数など) が異なる場合は、型を操作するようにプロジェクトを変更する必要があります。 「 ASP.NET ID のユーザーの主キーを変更する」を参照してください。

  11. 必要に応じて、接続文字列を Web.config ファイルに追加します。

その他のリソース