共用方式為


ASP.NET Identity 的自訂儲存體提供者概觀

作者:Tom FitzMacken

ASP.NET 身分識別是一種可延伸的系統,可讓您建立自己的儲存體提供者,並將它插入您的應用程式,而不需重新運作應用程式。 本主題描述如何建立 ASP.NET 身分識別的自訂儲存體提供者。 它涵蓋建立您自己的儲存體提供者的重要概念,但不是實作自訂儲存體提供者的逐步解說。

如需實作自訂儲存體提供者的範例,請參閱實作 自訂 MySQL ASP.NET 識別儲存體提供者

本主題已更新 ASP.NET Identity 2.0。

教學課程中使用的軟體版本

  • 更新 2 Visual Studio 2013
  • ASP.NET 身分識別 2

簡介

根據預設,ASP.NET 身分識別系統會將使用者資訊儲存在SQL Server資料庫中,並使用 Entity Framework Code First 建立資料庫。 對於許多應用程式而言,此方法運作良好。 不過,您可能偏好使用不同類型的持續性機制,例如 Azure 資料表儲存體,或者您可能已經有與預設實作結構非常不同的資料庫資料表。 不論是哪一種情況,您都可以為儲存機制撰寫自訂的提供者,並將該提供者插入您的應用程式。

ASP.NET 身分識別預設會包含在許多Visual Studio 2013範本中。 您可以透過 Microsoft AspNet Identity EntityFramework NuGet 套件取得 ASP.NET 身分識別的更新。

這個主題包括下列各節:

瞭解架構

ASP.NET 身分識別是由稱為管理員和存放區的類別所組成。 管理員是應用程式開發人員用來在 ASP.NET 身分識別系統中執行作業的高階類別,例如建立使用者。 存放區是較低層級的類別,可指定實體的保存方式,例如使用者和角色。 存放區與持續性機制緊密結合,但管理員會與存放區分離,這表示您可以取代持續性機制,而不會中斷整個應用程式。

下圖顯示 Web 應用程式如何與管理員互動,並儲存與資料存取層的互動。

顯示 Web 應用程式如何與管理員互動的圖表

若要建立 ASP.NET 身分識別的自訂儲存提供者,您必須建立資料來源、資料存取層,以及與此資料存取層互動的存放區類別。 您可以繼續使用相同的管理員 API 對使用者執行資料作業,但現在該資料會儲存至不同的儲存體系統。

您不需要自訂管理員類別,因為建立 UserManager 或 RoleManager 的新實例時,您會提供使用者類別的類型,並將存放區類別的實例當做引數傳遞。 此方法可讓您將自訂類別插入現有的結構。 您將瞭解如何在重新設定 應用程式以使用新的儲存體提供者一節中,使用自訂的存放區類別具現化 UserManager 和 RoleManager。

瞭解儲存的資料

若要實作自訂儲存體提供者,您必須瞭解與 ASP.NET 身分識別搭配使用的資料類型,並決定哪些功能與您的應用程式相關。

資料 描述
使用者 您網站的已註冊使用者。 包含使用者識別碼和使用者名稱。 如果使用者使用網站專屬的認證登入, (而不是使用來自 Facebook) 等外部網站的認證,以及安全性戳記來指出使用者認證中是否有任何變更,則可能包含雜湊密碼。 也可能包括電子郵件地址、電話號碼、是否啟用雙因素驗證、目前失敗登入的數目,以及帳戶是否已鎖定。
使用者宣告 一組語句 (或宣告) 代表使用者身分識別的使用者。 可以啟用使用者身分識別的更大運算式,而不是透過角色達成。
使用者登入 關於外部驗證提供者的資訊 (,例如 Facebook) 登入使用者時要使用的外部驗證提供者。
角色 網站的授權群組。 包含角色識別碼和角色名稱 (,例如 「管理員」 或 「Employee」) 。

建立資料存取層

本主題假設您已熟悉將使用的持續性機制,以及如何建立該機制的實體。 本主題不提供如何建立存放庫或資料存取類別的詳細資料;相反地,它會提供使用 ASP.NET 身分識別時所需做出之設計決策的一些建議。

設計自訂存放區提供者的存放庫時,您有許多自由。 您只需要針對您想要在應用程式中使用的功能建立存放庫。 例如,如果您未在應用程式中使用角色,則不需要為角色或使用者角色建立儲存體。 您的技術和現有基礎結構可能需要與 ASP.NET 身分識別的預設實作非常不同的結構。 在資料存取層中,您會提供邏輯來處理存放庫的結構。

如需 ASP.NET Identity 2.0 之資料存放庫的 MySQL 實作,請參閱 MySQLIdentity.sql

在資料存取層中,您會提供邏輯,將資料從 ASP.NET 身分識別儲存到資料來源。 自訂儲存體提供者的資料存取層可能包含下列類別來儲存使用者和角色資訊。

類別 描述 範例
內容 封裝資訊以連線到您的持續性機制並執行查詢。 此類別是您的資料存取層的核心。 其他資料類別將需要此類別的實例來執行其作業。 您也會使用這個類別的實例來初始化存放區類別。 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。 IUser < TKey >介面可讓您透過一般TKey參數指定使用者的金鑰類型。 Id 屬性的類型符合 TKey 參數的值。

當您想要使用索引鍵的字串值時,Identity 架構也會提供 IUser 介面 (,而不需要泛型參數) 。

IdentityUser 類別會實作 IUser,並包含網站上使用者的任何其他屬性或建構函式。 下列範例顯示使用索引鍵整數的 IdentityUser 類別。 [識別碼] 欄位會設定為 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 類型參數之 參數的建構函式只是如何傳入資料存取類別的圖例。 例如,在 MySQL 實作中,此建構函式會採用 MySQLDatabase 類型的參數。

在您的 UserStore 類別中,您會使用您建立的資料存取類別來執行作業。 例如,在 MySQL 實作中,UserStore 類別具有 CreateAsync 方法,它會使用 UserTable 的實例來插入新記錄。 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 >介面會定義您必須實作的方法,才能實作雙因素驗證。 它包含取得和設定是否為使用者啟用雙因素驗證的方法。

  • 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 命名空間包含 IdentityUserClaimIdentityUserLoginIdentityUserRole 類別的實作。 如果您使用這些功能,建議您建立自己的這些類別版本,並定義應用程式的屬性。 不過,有時候在執行基本作業時,執行基本作業 (時,不要將這些實體載入記憶體更有效率,例如新增或移除使用者的宣告) 。 相反地,後端存放區類別可以直接在資料來源上執行這些作業。 例如,UserStore.GetClaimsAsync () 方法可以呼叫 userClaimTable.FindByUserId (使用者。識別碼) 方法,直接在該資料表上執行查詢,並傳回宣告清單。

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 在執行要求作業時嘗試呼叫的屬性。 介面包含兩個屬性 :識別碼和名稱。 IRole < TKey >介面可讓您透過一般TKey參數指定角色的金鑰類型。 Id 屬性的類型符合 TKey 參數的值。

當您想要使用索引鍵的字串值時,Identity 架構也會提供 IRole 介面 (,而不需要泛型參數) 。

下列範例示範使用索引鍵整數的 IdentityRole 類別。 [識別碼] 欄位會設定為 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 類型的參數具名 資料庫的 建構函式,只是如何傳入資料存取類別的圖例。 例如,在 MySQL 實作中,此建構函式採用 MySQLDatabase 類型的參數。

    如需完整的實作,請參閱 RoleStore (MySQL)

重新設定應用程式以使用新的儲存體提供者

您已實作新的儲存體提供者。 現在,您必須將應用程式設定為使用此儲存體提供者。 如果專案中包含預設儲存體提供者,您必須移除預設提供者,並將它取代為您的提供者。

取代 MVC 專案中的預設儲存體提供者

  1. 在 [ 管理 NuGet 套件 ] 視窗中,卸載 Microsoft ASP.NET Identity EntityFramework 套件。 您可以在 Identity.EntityFramework 的已安裝套件中搜尋,以尋找此套件。
    Nu Get 套件視窗的影像 系統會詢問您是否也想要卸載 Entity Framework。 如果您在應用程式的其他部分中不需要它,您可以將其卸載。

  2. 在 Models 資料夾中的 IdentityModels.cs 檔案中,刪除或批註 ApplicationUserApplicationDbCoNtext 類別。 在 MVC 應用程式中,您可以刪除整個 IdentityModels.cs 檔案。 在Web Form應用程式中,刪除這兩個類別,但請確定您保留也位於 IdentityModels.cs 檔案中的協助程式類別。

  3. 如果您的儲存體提供者位於不同的專案中,請在 Web 應用程式中新增其參考。

  4. 以儲存體提供者命名空間的 using 語句取代 的所有參考 using Microsoft.AspNet.Identity.EntityFramework;

  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. IdentityUser取代ApplicationUser的所有參考。

  8. 預設專案包含使用者類別中未定義于 IUser 介面中的某些成員;例如Email、PasswordHash 和 GenerateUserIdentityAsync。 如果您的使用者類別沒有這些成員,您必須實作這些成員或變更使用這些成員的程式碼。

  9. 如果您已建立 RoleManager 的任何實例,請將該程式碼變更為使用新的 RoleStore 類別。

    var roleManager = new RoleManager<IdentityRole>(new RoleStore(context.Get<ExampleStorageContext>()));
    
  10. 預設專案是針對具有索引鍵字串值的使用者類別所設計。 如果您的使用者類別具有不同類型的索引鍵 (,例如整數) ,您必須變更專案以使用您的類型。 請參閱 變更 ASP.NET 身分識別中使用者的主要金鑰

  11. 如有需要,請將連接字串新增至Web.config檔案。

其他資源