ASP.NET メンバーシップ認証から ASP.NET Core 2.0 Identity に移行する
著者: Isaac Levin
この記事では、メンバーシップ認証を使用する ASP.NET アプリ用のデータベース スキーマを ASP.NET Core 2.0 Identity に移行する方法について説明します。
Note
このドキュメントでは、ASP.NET メンバーシップ ベースのアプリ用のデータベース スキーマを、ASP.NET Core Identity に使用されるデータベース スキーマに移行するために必要な手順を示します。 ASP.NET メンバーシップ ベースの認証から ASP.NET Identity への移行について詳しくは、SQL メンバーシップから ASP.NET Identityへの既存アプリの移行に関する記事をご覧ください。 ASP.NET Core Identity について詳しくは、「ASP.NET Core での Identity の概要」をご覧ください。
メンバーシップ スキーマのレビュー
ASP.NET 2.0 より前の開発者は、アプリの認証と認可のプロセス全体を自分で作成する必要がありました。 ASP.NET 2.0 でメンバーシップが導入され、ASP.NET アプリ内でのセキュリティの処理に定型ソリューションが提供されるようになりました。 開発者は、ASP.NET SQL Server 登録ツール (Aspnet_regsql.exe
) を使用して、スキーマを SQL Server データベースにブートストラップできるようになりました (サポートされなくなっています)。 このコマンドを実行すると、次のテーブルがデータベースに作成されます。
既存のアプリを ASP.NET Core 2.0 Identity に移行するには、これらのテーブルのデータを、新しい Identity スキーマによって使用されるテーブルに移行する必要があります。
ASP.NET Core Identity 2.0 スキーマ
ASP.NET Core 2.0 は、ASP.NET 4.5 で導入された Identity の原則に従います。 原則は共有されていますが、実装はフレームワーク間で異なるだけでなく、ASP.NET Core のバージョン間でさえ異なります (認証と Identity の ASP.NET Core 2.0 への移行に関する記事をご覧ください)。
ASP.NET Core 2.0 Identity 用のスキーマを見る最も簡単な方法は、ASP.NET Core 2.0 アプリを新しく作成することです。 Visual Studio 2017 で次の手順のようにします。
ファイル>新規作成>プロジェクトを選択します。
CoreIdentitySample という名前の新しい ASP.NET Core Web アプリケーション プロジェクトを作成します。
ドロップダウン リストで [ASP.NET Core 2.0] を選んでから、[Web アプリケーション] を選びます。 このテンプレートからは、Razor Pages アプリが生成されます。 [OK] をクリックする前に、[認証の変更] をクリックします。
Identity テンプレートに対して [個人のユーザー アカウント] を選びます。 最後に、[OK] をクリックしてから、[OK] をクリックします。 Visual Studio により、ASP.NET Core Identity テンプレートを使ってプロジェクトが作成されます。
[ツール]>[NuGet パッケージ マネージャー]>[パッケージ マネージャー コンソール] を選んで、[パッケージ マネージャー コンソール] (PMC) ウィンドウを開きます。
PMC でプロジェクト ルートに移動し、Entity Framework (EF) Core の
Update-Database
コマンドを実行します。ASP.NET Core 2.0 Identity は EF Core を使用して、認証データが格納されているデータベースと対話します。 新しく作成したアプリを機能させるには、このデータを格納するためのデータベースが必要です。 新しいアプリを作成した後、データベース環境のスキーマを調べる最も簡単な方法は、EF Core移行を使ってデータベースを作成することです。 このプロセスにより、ローカル環境または他の場所に、そのスキーマに基づくデータベースが作成されます。 詳しくは、前に示したドキュメントをご覧ください。
EF Core のコマンドでは、
appsettings.json
で指定されているデータベースの接続文字列が使用されます。 次の接続文字列の対象は、localhost にある asp-net-core-identityidentity という名前のデータベースです。 この設定では、DefaultConnection
の接続文字列を使用するように EF Core が構成されます。{ "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=aspnet-core-identity;Trusted_Connection=True;MultipleActiveResultSets=true" } }
警告
この記事では、接続文字列の使用方法について説明します。 ローカル データベースでは、ユーザーを認証する必要はありませんが、運用環境では、接続文字列認証用のパスワードが含まれる場合があります。 リソース所有者パスワード資格情報 (ROPC) は、運用データベースで回避する必要があるセキュリティ リスクです。 運用アプリでは、使用可能な最も安全な認証フローを使用する必要があります。 テスト環境または運用環境にデプロイされたアプリの認証の詳細については、「 安全な認証フロー」を参照してください。
[表示]>[SQL Server オブジェクト エクスプローラー] を選びます。
appsettings.json
のConnectionStrings:DefaultConnection
プロパティで指定されているデータベース名に対応するノードを展開します。Update-Database
コマンドにより、スキーマで指定されているデータベースと、アプリの初期化に必要なデータが作成されました。 次の図は、前の手順で作成されたテーブルの構造を示したものです。
スキーマを移行する
メンバーシップと ASP.NET Core Identity のテーブルの構造とフィールドには、微妙な違いがあります。 ASP.NET と ASP.NET Core アプリでは認証と認可のパターンが大幅に変更されています。 Identity で引き続き使用されている主なオブジェクトは、Users と Roles です。 次に示すのは、Users、Roles、UserRoles の対応のテーブルです。
Users
Identity ( dbo.AspNetUsers ) 列 |
Type | Membership ( dbo.aspnet_Users / dbo.aspnet_Membership ) 列 |
Type |
---|---|---|---|
Id |
string |
aspnet_Users.UserId |
string |
UserName |
string |
aspnet_Users.UserName |
string |
Email |
string |
aspnet_Membership.Email |
string |
NormalizedUserName |
string |
aspnet_Users.LoweredUserName |
string |
NormalizedEmail |
string |
aspnet_Membership.LoweredEmail |
string |
PhoneNumber |
string |
aspnet_Users.MobileAlias |
string |
LockoutEnabled |
bit |
aspnet_Membership.IsLockedOut |
bit |
IsLockedOut
は LockoutEnabled
にマップされません。 IsLockedOut
は、ユーザーが何度もログインに失敗して、一定時間ロックアウトされた場合に設定されます。 LockoutEnabled
では、サインイン試行を何度も失敗したユーザーをロックアウトできます。 ユーザーがサインイン試行に何度も失敗した場合、LockoutEnd
は将来の日付に設定され、ユーザーはその日付までサインインできません。 LockoutEnabled
が false の場合、サインイン試行を何度も失敗したユーザーがロックアウトされることはありません。 OWASP ごとに、いくつかの試行が失敗した後の一時的なアカウントのロックアウトは、正当なユーザーに対する DoS 攻撃のターゲットとしては単純すぎます。
ロックアウトの詳細については、「OWASP による弱いロックアウト メカニズムのテスト」を参照してください。
アプリを失敗したログイン ロックアウトを有効にする Identity に移行する場合は、移行の一環として LockoutEnabled
を true に設定する必要があります。
Note
すべてのフィールド マッピングに、同様のメンバーシップから ASP.NET Core Identity への一対一の関係があるとは限りません。 上の表は、メンバーシップの既定のユーザー スキーマを取り出し、それを ASP.NET Core Identity のスキーマにマップしたものです。 メンバーシップで使われていた他のカスタム フィールドは、手動でマップする必要があります。 パスワードの条件とパスワード ソルトのどちらも 2 つの間で移行されないため、このマッピングではパスワードのマップはありません。 パスワードは null のままにしておき、ユーザーにパスワードのリセットを求めることをお勧めします。 ASP.NET Core Identity では、ユーザーがロックアウトされている場合は、LockoutEnd
を将来の日付に設定する必要があります。これは移行スクリプトに示されています。
ロール
Identity ( dbo.AspNetRoles ) 列 |
Type | Membership ( dbo.aspnet_Roles ) 列 |
Type |
---|---|---|---|
Id |
string |
RoleId |
string |
Name |
string |
RoleName |
string |
NormalizedName |
string |
LoweredRoleName |
string |
ユーザー ロール
Identity ( dbo.AspNetUserRoles ) 列 |
Type | Membership ( dbo.aspnet_UsersInRoles ) 列 |
Type |
---|---|---|---|
RoleId |
string |
RoleId |
string |
UserId |
string |
UserId |
string |
Users と Roles の移行スクリプトを作るときは、上のマッピング テーブルを参照してください。 次の例では、データベース サーバーに 2 つのデータベースがあるものとします。 1 つのデータベースには、既存の ASP.NET メンバーシップのスキーマとデータが含まれています。 もう 1 つの CoreIdentitySample データベースは、前に説明した手順を使用して作成されたものです。 詳細なコメントが含まれています。
-- THIS SCRIPT NEEDS TO RUN FROM THE CONTEXT OF THE MEMBERSHIP DB
BEGIN TRANSACTION MigrateUsersAndRoles
USE aspnetdb
-- INSERT USERS
INSERT INTO CoreIdentitySample.dbo.AspNetUsers
(Id,
UserName,
NormalizedUserName,
PasswordHash,
SecurityStamp,
EmailConfirmed,
PhoneNumber,
PhoneNumberConfirmed,
TwoFactorEnabled,
LockoutEnd,
LockoutEnabled,
AccessFailedCount,
Email,
NormalizedEmail)
SELECT aspnet_Users.UserId,
aspnet_Users.UserName,
-- The NormalizedUserName value is upper case in ASP.NET Core Identity
UPPER(aspnet_Users.UserName),
-- Creates an empty password since passwords don't map between the 2 schemas
'',
/*
The SecurityStamp token is used to verify the state of an account and
is subject to change at any time. It should be initialized as a new ID.
*/
NewID(),
/*
EmailConfirmed is set when a new user is created and confirmed via email.
Users must have this set during migration to reset passwords.
*/
1,
aspnet_Users.MobileAlias,
CASE
WHEN aspnet_Users.MobileAlias IS NULL THEN 0
ELSE 1
END,
-- 2FA likely wasn't setup in Membership for users, so setting as false.
0,
CASE
-- Setting lockout date to time in the future (1,000 years)
WHEN aspnet_Membership.IsLockedOut = 1 THEN Dateadd(year, 1000,
Sysutcdatetime())
ELSE NULL
END,
aspnet_Membership.IsLockedOut,
/*
AccessFailedAccount is used to track failed logins. This is stored in
Membership in multiple columns. Setting to 0 arbitrarily.
*/
0,
aspnet_Membership.Email,
-- The NormalizedEmail value is upper case in ASP.NET Core Identity
UPPER(aspnet_Membership.Email)
FROM aspnet_Users
LEFT OUTER JOIN aspnet_Membership
ON aspnet_Membership.ApplicationId =
aspnet_Users.ApplicationId
AND aspnet_Users.UserId = aspnet_Membership.UserId
LEFT OUTER JOIN CoreIdentitySample.dbo.AspNetUsers
ON aspnet_Membership.UserId = AspNetUsers.Id
WHERE AspNetUsers.Id IS NULL
-- INSERT ROLES
INSERT INTO CoreIdentitySample.dbo.AspNetRoles(Id, Name)
SELECT RoleId, RoleName
FROM aspnet_Roles;
-- INSERT USER ROLES
INSERT INTO CoreIdentitySample.dbo.AspNetUserRoles(UserId, RoleId)
SELECT UserId, RoleId
FROM aspnet_UsersInRoles;
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION MigrateUsersAndRoles
RETURN
END
COMMIT TRANSACTION MigrateUsersAndRoles
前のスクリプトが完了すると、前に作成した ASP.NET Core Identity アプリにメンバーシップのユーザーが設定されます。 ユーザーはログインする前にパスワードを変更する必要があります。
Note
メンバーシップ システムにメールアドレスと一致しないユーザー名を持つユーザーがいる場合は、前に作成されたアプリを変更してこれに対応する必要があります。 既定のテンプレートでは、UserName
と Email
が同じであることが想定されています。 それらが異なる場合は、Email
の代わりに UserName
を使うようにログイン プロセスを変更する必要があります。
ログイン ページの PageModel
で、Email プロパティから [EmailAddress]
属性を削除します。 その名前を UserName に変更します。 View と PageModel で EmailAddress
が使われているすべての場所で変更が必要です。 結果は次のようになります。
次のステップ
このチュートリアルでは、SQL メンバーシップから ASP.NET Core 2.0 Identity にユーザーを移植する方法について説明しました。 ASP.NET Core Identity について詳しくは、Identity の概要に関する記事をご覧ください。
ASP.NET Core