既存 Web サイトを SQL メンバーシップから ASP.NET Identity に移行する

作成者: Rick AndersonSuhas Joshi

このチュートリアルでは、SQL メンバーシップを使用して作成されたユーザーとロール データを持つ既存の Web アプリケーションを新しい ASP.NET Identity システムに移行する手順について説明します。 この方法では、既存のデータベース スキーマを、ASP.NET Identity に必要なスキーマに変更し、古いまたは新しいクラスをそのスキーマに統合します。 このアプローチを採用した後、データベースを移行したら、それ以降の ID の更新を簡単に処理できます。

このチュートリアルでは、Visual Studio 2010 で作成した Web アプリケーション テンプレート (Web Forms) を使用して、ユーザーとロール データを作成します。 次に、SQL スクリプトを使用して、既存のデータベースを ID システムで必要とされるテーブルに移行します。 さらに、必要な NuGet パッケージをインストールし、メンバーシップ管理に ID システムを使用する新しいアカウント管理ページを追加します。 移行のテストとして、SQL メンバーシップを使用して作成されたユーザーがログインでき、新しいユーザーが登録できることを確認します。 完全なサンプルは、こちらでご覧いただけます。 「ASP.NET メンバーシップから ASP.NET Identity への移行」も参照してください。

作業の開始

SQL メンバーシップを使用したアプリケーションの作成

  1. SQL メンバーシップを使用し、ユーザーとロール データを持つ既存のアプリケーションから始める必要があります。 この記事では、Visual Studio 2010 で Web アプリケーションを作成します。

    Screenshot of creating a web application in Visual Studio 2010.

  2. ASP.NET 構成ツールを使用して、oldAdminUseroldUser の 2 つのユーザーを作成します。

    Screenshot of the A S P . N E T Configuration tool to create two users.

    Screenshot of the website administration tool to manage all the security.

  3. Admin という名前のロールを作成し、そのロールのユーザーとして "oldAdminUser" を追加します。

    Screenshot of the Admin role created as a user in that role.

  4. Default.aspx を使用して、サイトの管理セクションを作成します。 web.config ファイルで認可タグを設定して、Admin ロールのユーザーのみがアクセスできるようにします。 詳細については、https://www.asp.net/web-forms/tutorials/security/roles/role-based-authorization-cs を参照してください。

    Screenshot of the site's Admin section.

  5. サーバー エクスプローラーでデータベースを表示し、SQL メンバーシップ システムによって作成されたテーブルを調べます。 ユーザー ログイン データは aspnet_Users と aspnet_Membership テーブルに格納され、ロール データは aspnet_Roles テーブルに格納されます。 aspnet_UsersInRoles テーブルにロールが格納されているユーザーに関する情報。 基本的なメンバーシップ管理では、上記の表の情報を ASP.NET Identity システムに移植するだけで十分です。

    Screenshot of the Server Explorer to view the database to understand the tables created by the S Q L membership system.

Visual Studio 2013 への移行

  1. Visual Studio Express 2013 for Web または Visual Studio 2013 を最新の更新プログラムと共にインストールします。

  2. インストールされているバージョンの Visual Studio で、上記のプロジェクトを開きます。 SQL Server Express がマシンにインストールされていない場合、接続文字列では SQL Express が使用されるため、プロジェクトを開くときにプロンプトが表示されます。 SQL Express のインストールを選択することも、回避策として接続文字列を LocalDb に変更することもできます。 この記事では、LocalDb に変更します。

  3. web.config を開き、接続文字列を .SQLExpress から (LocalDb)v11.0 に変更します。 接続文字列から "User Instance=true" を削除します。

    Screenshot of changing the connection string to migrate to Visual Studio 2013.

  4. サーバー エクスプローラーを開き、テーブル スキーマとデータを表示できることを確認します。

  5. ASP.NET Identity システムは、バージョン 4.5 以降のフレームワークで動作します。 アプリケーションのターゲットを 4.5 以上に変更します。

    Screenshot of the A S P . N E T Identity system.

    プロジェクトをビルドして、エラーがないことを確認します。

Nuget パッケージのインストール

  1. ソリューション エクスプローラーで >[NuGet パッケージの管理] プロジェクトを右クリックします。 検索ボックスに「Asp.net Identity」と入力します。 結果の一覧でパッケージを選択し、[インストール] を選択します。 [同意する] ボタンをクリックして、使用許諾契約書に同意します。 このパッケージでは、EntityFramework と Microsoft ASP.NET Identity Core という依存関係パッケージがインストールされることに注目してください。 同様に、次のパッケージをインストールします (OAuth ログインを有効にしない場合は、最後の 4 つの OWIN パッケージをスキップしてください)。

    • Microsoft.AspNet.Identity.Owin

    • Microsoft.Owin.Host.SystemWeb

    • Microsoft.Owin.Security.Facebook

    • Microsoft.Owin.Security.Google

    • Microsoft.Owin.Security.MicrosoftAccount

    • Microsoft.Owin.Security.Twitter

      Screenshot of installing the Nuget packages in Solution Explorer.

データベースを新しい ID システムに移行する

次の手順では、既存のデータベースを ASP.NET Identity システムで必要とされるスキーマに移行します。 これを実行するために、新しいテーブルを作成し、既存のユーザー情報を新しいテーブルに移行するための一連のコマンドを含む SQL スクリプトを実行します。 スクリプト ファイルについては、こちらを参照してください

このスクリプト ファイルは、このサンプルに固有のものです。 SQL メンバーシップを使用して作成されたテーブルのスキーマをカスタマイズまたは変更する場合は、それに応じてスクリプトを変更する必要があります。

スキーマ移行用の SQL スクリプトを生成する方法

ASP.NET Identity クラスが既存のユーザーのデータですぐに使用できるようにするには、ASP.NET Identity で必要とされるデータベース スキーマに移行する必要があります。 これを行うには、新しいテーブルを追加し、既存の情報をそれらのテーブルにコピーします。 既定では、ASP.NET Identity は EntityFramework を使用して ID モデル クラスをデータベースにマップし、情報を格納/取得します。 これらのモデル クラスは、ユーザーとロール オブジェクトを定義するコア ID インターフェイスを実装します。 データベース内のテーブルと列は、これらのモデル クラスに基づいています。 Identity v2.1.0 の EntityFramework モデル クラスとそのプロパティは、以下で定義されています

IdentityUser Type IdentityRole IdentityUserRole IdentityUserLogin IdentityUserClaim
Id string Id RoleId ProviderKey Id
ユーザー名 string 名前 UserId UserId ClaimType
PasswordHash string LoginProvider ClaimValue
SecurityStamp string User_Id
メール string
EmailConfirmed [bool]
PhoneNumber string
PhoneNumberConfirmed [bool]
LockoutEnabled [bool]
LockoutEndDate DateTime
AccessFailedCount int

これらの各モデルのテーブルと、プロパティに対応する列が必要です。 クラスとテーブルの間のマッピングは、IdentityDBContextOnModelCreating メソッドで定義されます。 これは、構成の fluent API メソッドと呼ばれています。詳細については、こちらを参照してください。 クラスの構成は次のとおりです。

クラス Table 主キー 外部キー
IdentityUser AspnetUsers Id
IdentityRole AspnetRoles Id
IdentityUserRole AspnetUserRole UserId + RoleId User_Id->AspnetUsers RoleId->AspnetRoles
IdentityUserLogin AspnetUserLogins ProviderKey+UserId + LoginProvider UserId->AspnetUsers
IdentityUserClaim AspnetUserClaims Id User_Id->AspnetUsers

この情報を使用して、新しいテーブルを作成する SQL ステートメントを作成できます。 各ステートメントを個別に記述するか、必要に応じて編集可能な EntityFramework PowerShell コマンドを使用してスクリプト全体を生成します。 これを行うには、VS で [表示] メニューまたは [ツール] メニューからパッケージ マネージャー コンソールを開きます。

  • コマンド "Enable-Migrations" を実行して EntityFramework の移行を有効にします。
  • C#/VB でデータベースを作成するための初期セットアップ コードを作成するコマンド "Add-migration initial" を実行します。
  • 最後の手順では、モデル クラスに基づいて SQL スクリプトを生成する "Update-Database –Script" コマンドを実行します。

アプリで SQLite を ID データ ストアとして使用する場合、一部のコマンドはサポートされません。 データベース エンジンの制限により、Alter コマンドは次の例外をスローします。

"System.NotSupportedException: SQLite はこの移行操作をサポートしていません。"

回避策として、データベースで Code First の移行を実行してテーブルを変更します。

このデータベース生成スクリプトは、新しい列を追加してデータをコピーするために追加の変更を加える最初のスクリプトとして使用できます。 この方法の利点は、モデル クラスが将来のバージョンの ID リリースで変更されたときに、EntityFramework でデータベース スキーマを変更するために使用される _MigrationHistory テーブルを生成できることにあります。

SQL メンバーシップのユーザー情報には、ID ユーザー モデル クラスのプロパティに加えて、メール、パスワードの試行、最終ログイン日付、最終ロックアウト日付などのプロパティがありました。これは有用な情報であり、これらの情報を ID システムに引き継ぎたいと考えています。 これを行うには、ユーザー モデルにプロパティを追加し、データベース内のテーブル列にマッピングし直します。 これを行うには、IdentityUser モデルをサブクラス化するクラスを追加します。 このカスタム クラスにプロパティを追加し、SQL スクリプトを編集して、テーブルの作成時に対応する列を追加できます。 このクラスのコードについては、この記事で詳しく説明します。 新しいプロパティを追加した後に AspnetUsers テーブルを作成するための SQL スクリプトは、次のようになります。

CREATE TABLE [dbo].[AspNetUsers] (
    [Id]            NVARCHAR (128) NOT NULL,
    [UserName]      NVARCHAR (MAX) NULL,
    [PasswordHash]  NVARCHAR (MAX) NULL,
    [SecurityStamp] NVARCHAR (MAX) NULL,
    [EmailConfirmed]       BIT            NOT NULL,
    [PhoneNumber]          NVARCHAR (MAX) NULL,
    [PhoneNumberConfirmed] BIT            NOT NULL,
    [TwoFactorEnabled]     BIT            NOT NULL,
    [LockoutEndDateUtc]    DATETIME       NULL,
    [LockoutEnabled]       BIT            NOT NULL,
    [AccessFailedCount]    INT            NOT NULL,
    [ApplicationId]                          UNIQUEIDENTIFIER NOT NULL,
    [LegacyPasswordHash]  NVARCHAR (MAX) NULL,
    [LoweredUserName]  NVARCHAR (256)   NOT NULL,
    [MobileAlias]      NVARCHAR (16)    DEFAULT (NULL) NULL,
    [IsAnonymous]      BIT              DEFAULT ((0)) NOT NULL,
    [LastActivityDate] DATETIME2         NOT NULL,
    [MobilePIN]                              NVARCHAR (16)    NULL,
    [Email]                                  NVARCHAR (256)   NULL,
    [LoweredEmail]                           NVARCHAR (256)   NULL,
    [PasswordQuestion]                       NVARCHAR (256)   NULL,
    [PasswordAnswer]                         NVARCHAR (128)   NULL,
    [IsApproved]                             BIT              NOT NULL,
    [IsLockedOut]                            BIT              NOT NULL,
    [CreateDate]                             DATETIME2               NOT NULL,
    [LastLoginDate]                          DATETIME2         NOT NULL,
    [LastPasswordChangedDate]                DATETIME2         NOT NULL,
    [LastLockoutDate]                        DATETIME2         NOT NULL,
    [FailedPasswordAttemptCount]             INT              NOT NULL,
    [FailedPasswordAttemptWindowStart]       DATETIME2         NOT NULL,
    [FailedPasswordAnswerAttemptCount]       INT              NOT NULL,
    [FailedPasswordAnswerAttemptWindowStart] DATETIME2         NOT NULL,
    [Comment]                                NTEXT            NULL,
    CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC),
    FOREIGN KEY ([ApplicationId]) REFERENCES [dbo].[aspnet_Applications] ([ApplicationId]),
);

次に、既存の情報を SQL メンバーシップ データベースから新たに追加した ID のテーブルにコピーする必要があります。 これは SQL を使用して、テーブル間でデータを直接コピーすることで行えます。 テーブルの行にデータを追加するには、INSERT INTO [Table] コンストラクトを使用します。 別のテーブルからコピーするには、INSERT INTO ステートメントと SELECT ステートメントを使用できます。 すべてのユーザー情報を取得するには、aspnet_Usersaspnet_Membership テーブルに対してクエリを実行し、AspNetUsers テーブルにデータをコピーする必要があります。 JOIN および LEFT OUTER JOIN ステートメントと共に INSERT INTOSELECT を使用します。 クエリと実行と、テーブル間でのデータのコピーの詳細については、このリンクを参照してください。 また、AspnetUserLogins と AspnetUserClaims テーブルは、既定でこれらにマップされる情報が SQL メンバーシップにないため、最初は空です。 コピーされる情報は、ユーザーとロールのみです。 前の手順で作成したプロジェクトの場合、ユーザー テーブルに情報をコピーする SQL クエリは次のようになります。

INSERT INTO AspNetUsers(Id,UserName,PasswordHash,SecurityStamp,EmailConfirmed,
PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEndDateUtc,LockoutEnabled,AccessFailedCount,
ApplicationId,LoweredUserName,MobileAlias,IsAnonymous,LastActivityDate,LegacyPasswordHash,
MobilePIN,Email,LoweredEmail,PasswordQuestion,PasswordAnswer,IsApproved,IsLockedOut,CreateDate,
LastLoginDate,LastPasswordChangedDate,LastLockoutDate,FailedPasswordAttemptCount,
FailedPasswordAnswerAttemptWindowStart,FailedPasswordAnswerAttemptCount,FailedPasswordAttemptWindowStart,Comment)
SELECT aspnet_Users.UserId,aspnet_Users.UserName,(aspnet_Membership.Password+'|'+CAST(aspnet_Membership.PasswordFormat as varchar)+'|'+aspnet_Membership.PasswordSalt),NewID(),
'true',NULL,'false','true',aspnet_Membership.LastLockoutDate,'true','0',
aspnet_Users.ApplicationId,aspnet_Users.LoweredUserName,
aspnet_Users.MobileAlias,aspnet_Users.IsAnonymous,aspnet_Users.LastActivityDate,aspnet_Membership.Password,
aspnet_Membership.MobilePIN,aspnet_Membership.Email,aspnet_Membership.LoweredEmail,aspnet_Membership.PasswordQuestion,aspnet_Membership.PasswordAnswer,
aspnet_Membership.IsApproved,aspnet_Membership.IsLockedOut,aspnet_Membership.CreateDate,aspnet_Membership.LastLoginDate,aspnet_Membership.LastPasswordChangedDate,
aspnet_Membership.LastLockoutDate,aspnet_Membership.FailedPasswordAttemptCount, aspnet_Membership.FailedPasswordAnswerAttemptWindowStart,
aspnet_Membership.FailedPasswordAnswerAttemptCount,aspnet_Membership.FailedPasswordAttemptWindowStart,aspnet_Membership.Comment
FROM aspnet_Users
LEFT OUTER JOIN aspnet_Membership ON aspnet_Membership.ApplicationId = aspnet_Users.ApplicationId 
AND aspnet_Users.UserId = aspnet_Membership.UserId;

上記の SQL ステートメントでは、aspnet_Usersaspnet_Membership テーブルの各ユーザーに関する情報が AspnetUsers テーブルの列にコピーされます。 パスワードをコピーするときのみ、変更が発生します。 SQL メンバーシップでのパスワードの暗号化アルゴリズムでは、"PasswordSalt" と "PasswordFormat" が使用されていたため、ID によるパスワードの復号化に使用できるように、ハッシュされたパスワードと共にそれらをコピーします。 これは、カスタム パスワード ハッシャーをフックする際の記事で詳しく説明します。

このスクリプト ファイルは、このサンプルに固有のものです。 テーブルが追加されているアプリケーションの場合、開発者は同様の方法に従ってユーザー モデル クラスにプロパティを追加し、AspnetUsers テーブルの列にそれらをマップできます。 スクリプトを実行するには、次の手順を実行します。

  1. [サーバー エクスプローラー] を開きます。 "ApplicationServices" 接続を展開してテーブルを表示します。 [テーブル] ノードを右クリックし、[新しいクエリ] オプションを選択します

    Screenshot of the Open Server Explorer to expand the Application Services connection to display the tables.

  2. クエリ ウィンドウで、Migrations.sql ファイルから SQL スクリプト全体をコピーして貼り付けます。 [実行] 矢印ボタンを押して、スクリプト ファイルを実行します。

    Screenshot of the query window to copy and paste the entire S Q L script.

    サーバー エクスプローラー ウィンドウを更新します。 データベースに 5 つの新しいテーブルが作成されます。

    Screenshot of refreshing the Server Explorer window to create five new tables in the database.

    Screenshot of the Tables in Data Connections.

    SQL メンバーシップ テーブルの情報を新しい ID システムにマップする方法を次に示します。

    aspnet_Roles --> AspNetRoles

    asp_netUsers and asp_netMembership --> AspNetUsers

    aspnet_UserInRoles --> AspNetUserRoles

    上記のセクションで説明したように、AspNetUserClaims と AspNetUserLogins テーブルは空です。 AspNetUser テーブルの [識別子] フィールドは、次の手順で定義されるモデル クラス名と一致する必要があります。 また、PasswordHash 列の形式は "暗号化されたパスワード |パスワード ソルト|パスワード形式" です。 これにより、特別な SQL メンバーシップ暗号化ロジックを使用でき、古いパスワードも再利用できるようになります。 これについては、この記事の後続の部分で説明します。

モデルとメンバーシップ ページの作成

既に説明したように、ID 機能が既定でアカウント情報を格納するために、Entity Framework を使用してデータベースと通信します。 テーブル内の既存のデータを操作するには、テーブルにマップし直すモデル クラスを作成し、これらを ID システムと統合する必要があります。 ID コントラクトの一環として、モデル クラスは Identity.Core dll に定義されているインターフェイスを実装するか、Microsoft.AspNet.Identity.EntityFramework で使用できるこれらのインターフェイスの既存の実装を拡張できます。

このサンプルでは、AspNetRoles、AspNetUserClaims、AspNetLogins、AspNetUserRole のテーブルには、ID システムの既存の実装に似た列があります。 そのため、既存のクラスを再利用して、これらのテーブルにマップできます。 AspNetUser テーブルには、SQL メンバーシップ テーブルの追加情報を格納するために使用される追加の列がいくつかあります。 これをマップするには、"IdentityUser" の既存の実装を拡張し、その他のプロパティを追加するモデル クラスを作成します。

  1. プロジェクトに Models フォルダーを作成し、User クラスを追加します。 クラスの名前は、"AspnetUsers" テーブルの [識別子] 列に追加されたデータと一致する必要があります。

    Screenshot of creating a Models folder in the project and adding a class User.

    User クラスは、Microsoft.AspNet.Identity.EntityFramework dll にある IdentityUser クラスを拡張する必要があります。 AspNetUser 列にマップするクラスのプロパティを宣言します。 プロパティの ID、Username、PasswordHash、SecurityStamp は IdentityUser で定義されているため、省略されます。 すべてのプロパティを持つ User クラスのコードを次に示します。

    public class User : IdentityUser
    {
        public User()
        {
            CreateDate = DateTime.Now;
            IsApproved = false;
            LastLoginDate = DateTime.Now;
            LastActivityDate = DateTime.Now;
            LastPasswordChangedDate = DateTime.Now;
            LastLockoutDate = DateTime.Parse("1/1/1754");
            FailedPasswordAnswerAttemptWindowStart = DateTime.Parse("1/1/1754");
            FailedPasswordAttemptWindowStart = DateTime.Parse("1/1/1754");
        }
    
        public System.Guid ApplicationId { get; set; }
        public string MobileAlias { get; set; }
        public bool IsAnonymous { get; set; }
        public System.DateTime LastActivityDate { get; set; }
        public string MobilePIN { get; set; }
        public string LoweredEmail { get; set; }
        public string LoweredUserName { get; set; }
        public string PasswordQuestion { get; set; }
        public string PasswordAnswer { get; set; }
        public bool IsApproved { get; set; }
        public bool IsLockedOut { get; set; }
        public System.DateTime CreateDate { get; set; }
        public System.DateTime LastLoginDate { get; set; }
        public System.DateTime LastPasswordChangedDate { get; set; }
        public System.DateTime LastLockoutDate { get; set; }
        public int FailedPasswordAttemptCount { get; set; }
        public System.DateTime FailedPasswordAttemptWindowStart { get; set; }
        public int FailedPasswordAnswerAttemptCount { get; set; }
        public System.DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
        public string Comment { get; set; }
    }
    
  2. モデルのデータをテーブルで保持し、テーブルからデータを取得してモデルに反映するために、Entity Framework の DbContext クラスが必要になります。 Microsoft.AspNet.Identity.EntityFramework dll は、ID テーブルと対話して情報を取得および格納する IdentityDbContext クラスを定義します。 IdentityDbContext<tuser> は、IdentityUser クラスを拡張する任意のクラスとして "TUser" クラスを受け取ります。

    手順 1 で作成した "User" クラスを渡して、"Models" フォルダーの下に IdentityDbContext を拡張する新しいクラス ApplicationDBContext を作成します。

    public class ApplicationDbContext : IdentityDbContext<User>
    {
            
    }
    
  3. 新しい ID システムでのユーザー管理は、Microsoft.AspNet.Identity.EntityFramework dll で定義されている UserManager<tuser> クラスを使用して行われます。 手順 1 で作成した "User" クラスを渡して、UserManager を拡張するカスタム クラスを作成する必要があります。

    Models フォルダーで、UserManager<user> を拡張する新しいクラス UserManager を作成します。

    public class UserManager : UserManager<User>
    {
            
    }
    
  4. アプリケーションのユーザーのパスワードは暗号化され、データベースに格納されます。 SQL メンバーシップで使用される暗号化アルゴリズムは、新しい ID システムの暗号化アルゴリズムとは異なります。 古いパスワードを再利用するには、古いユーザーが SQL メンバーシップ アルゴリズムを使用してログインするものの、新しいユーザーには ID の暗号化アルゴリズムを使用する場合、パスワードを選択的に復号化する必要があります。

    UserManager クラスには、"IPasswordHasher" インターフェイスを実装するクラスのインスタンスを格納するプロパティ "PasswordHasher" があります。 これは、ユーザー認証トランザクション中にパスワードを暗号化/復号化するために使用されます。 手順 3 で定義した UserManager クラスで、新しいクラス SQLPasswordHasher を作成し、次のコードをコピーします。

    public class SQLPasswordHasher : PasswordHasher
    {
        public override string HashPassword(string password)
        {
            return base.HashPassword(password);
        }
    
        public override PasswordVerificationResult VerifyHashedPassword(string  hashedPassword, string providedPassword)
        {
            string[] passwordProperties = hashedPassword.Split('|');
            if (passwordProperties.Length != 3)
            {
                return base.VerifyHashedPassword(hashedPassword, providedPassword);
            }
            else
            {
                string passwordHash = passwordProperties[0];
                int passwordformat = 1;
                string salt = passwordProperties[2];
                if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase))
                {
                    return PasswordVerificationResult.SuccessRehashNeeded;
                }
                else
                {
                    return PasswordVerificationResult.Failed;
                }
            }
        }
    
        //This is copied from the existing SQL providers and is provided only for back-compat.
        private string EncryptPassword(string pass, int passwordFormat, string salt)
        {
            if (passwordFormat == 0) // MembershipPasswordFormat.Clear
                return pass;
    
            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bRet = null;
    
            if (passwordFormat == 1)
            { // MembershipPasswordFormat.Hashed 
                HashAlgorithm hm = HashAlgorithm.Create("SHA1");
                if (hm is KeyedHashAlgorithm)
                {
                    KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
                    if (kha.Key.Length == bSalt.Length)
                    {
                        kha.Key = bSalt;
                    }
                    else if (kha.Key.Length < bSalt.Length)
                    {
                        byte[] bKey = new byte[kha.Key.Length];
                        Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
                        kha.Key = bKey;
                    }
                    else
                    {
                        byte[] bKey = new byte[kha.Key.Length];
                        for (int iter = 0; iter < bKey.Length; )
                        {
                            int len = Math.Min(bSalt.Length, bKey.Length - iter);
                            Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
                            iter += len;
                        }
                        kha.Key = bKey;
                    }
                    bRet = kha.ComputeHash(bIn);
                }
                else
                {
                    byte[] bAll = new byte[bSalt.Length + bIn.Length];
                    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
                    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
                    bRet = hm.ComputeHash(bAll);
                }
            }
    
            return Convert.ToBase64String(bRet);
        }
    

    System.Text と System.Security.Cryptography 名前空間をインポートして、コンパイル エラーを解決します。

    EncodePassword メソッドは、既定の SQL メンバーシップ暗号化実装に従ってパスワードを暗号化します。 これは System.Web dll から取得されます。 古いアプリがカスタム実装を使用していた場合は、ここに反映する必要があります。 他にも HashPasswordVerifyHashedPassword の 2 つのメソッドを定義する必要があります。これらは、EncodePassword メソッドを使用して特定のパスワードをハッシュするか、データベースに存在するパスワードを使用してプレーン テキスト パスワードを検証します。

    SQL メンバーシップ システムでは、PasswordHash、PasswordSalt、PasswordFormat を使用して、ユーザーがパスワードを登録または変更したときに入力したパスワードをハッシュしました。 移行している間、3 つのフィールドはすべて、AspNetUser テーブルの PasswordHash 列に、"|" 文字で区切られて格納されます。 ユーザーがログインする際、パスワードにこれらのフィールドがある場合は、SQL メンバーシップ暗号化を使用してパスワードをチェックします。ない場合は、ID システムの既定の暗号化を使用してパスワードを確認します。 このように、古いユーザーは、アプリを移行した後にパスワードを変更する必要はありません。

  5. UserManager クラスのコンストラクターを宣言し、これを SQLPasswordHasher としてコンストラクターのプロパティに渡します。

    public UserManager()
                : base(new UserStore<User>(new ApplicationDbContext()))
    {
                this.PasswordHasher = new SQLPasswordHasher();
    }
    

新しいアカウント管理ページを作成する

移行の次の手順は、ユーザーが登録してログインできるようにするアカウント管理ページを追加することです。 SQL メンバーシップの古いアカウント ページでは、新しい ID システムでは動作しないコントロールが使用されます。 新しいユーザー管理ページを追加するには、こちらのリンク https://www.asp.net/identity/overview/getting-started/adding-aspnet-identity-to-an-empty-or-existing-web-forms-project のチュートリアルに従います。プロジェクトを既に作成し、NuGet パッケージを追加しているため、"アプリケーションにユーザーを登録するための Web フォームの追加" に関する手順から始めます。

ここでは、サンプルがプロジェクトで動作するようにいくつかの変更を加える必要があります。

  • Register.aspx.cs と Login.aspx.cs 分離コード クラスでは、ID パッケージから UserManager を使用してユーザーを作成します。 この例では、前に説明した手順に従って、Models フォルダーに追加された UserManager を使用します。

  • Register.aspx.cs と Login.aspx.cs 分離コード クラスで IdentityUser の代わりに作成された User クラスを使用します。 これにより、カスタム ユーザー クラスが ID システムに統合されます。

  • データベースを作成する部分はスキップできます。

  • 開発者は、新しいユーザーの ApplicationId を、現在のアプリケーション ID と一致するように設定する必要があります。 これを行うには、ユーザー オブジェクトが Register.aspx.cs クラスで作成される前に、このアプリケーションの ApplicationId に対してクエリを実行し、ユーザーを作成する前に設定します。

    例:

    Register.aspx.cs ページでメソッドを定義し、aspnet_Applications テーブルに対してクエリを実行し、アプリケーション名に従ってアプリケーション ID を取得します

    private Guid GetApplicationID()
    {
        using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString))
        {
            string queryString = "SELECT ApplicationId from aspnet_Applications WHERE ApplicationName = '/'"; //Set application name as in database
    
            SqlCommand command = new SqlCommand(queryString, connection);
            command.Connection.Open();
    
            var reader = command.ExecuteReader();
            while (reader.Read())
            {
                return reader.GetGuid(0);
            }
    
            return Guid.NewGuid();
        }
    }
    

    ここで、ユーザー オブジェクトに対してこれを設定します

    var currentApplicationId = GetApplicationID();
    
    User user = new User() { UserName = Username.Text,
    ApplicationId=currentApplicationId, …};
    

既存のユーザーにログインするには、古いユーザー名とパスワードを使用します。 [登録] ページを使用して、新しいユーザーを作成します。 また、ユーザーが期待どおりにロール内にあることを確認します。

ID システムへの移植は、ユーザーがアプリケーションに Open Authentication (OAuth) を追加するのに役立ちます。 OAuth が有効になっているサンプルについては、こちらを参照してください。

次のステップ

このチュートリアルでは、SQL メンバーシップから ASP.NET Identity にユーザーを移植する方法を説明しましたが、プロファイル データは移植しませんでした。 次のチュートリアルでは、SQL メンバーシップから新しい ID システムへのプロファイル データの移植について説明します。

フィードバックをこの記事の下部に残すことができます。

この記事をレビューしてくれた Tom Dykstra と Rick Anderson に感謝します。