ASP.NET Core标识的自定义的存储提供程序Custom storage providers for ASP.NET Core Identity

作者:Steve SmithBy Steve Smith

ASP.NET Core标识是一种可扩展系统,可用于创建自定义存储提供程序并将其连接到你的应用。ASP.NET Core Identity is an extensible system which enables you to create a custom storage provider and connect it to your app. 本主题介绍如何创建 ASP.NET Core标识的自定义的存储提供程序。This topic describes how to create a customized storage provider for ASP.NET Core Identity. 它介绍了用于创建自己的存储提供程序的重要概念,但并不是分步演练。It covers the important concepts for creating your own storage provider, but isn't a step-by-step walkthrough.

查看或下载 GitHub 中的示例View or download sample from GitHub.

简介Introduction

默认情况下,ASP.NET Core标识系统中使用实体框架核心的 SQL Server 数据库存储用户信息。By default, the ASP.NET Core Identity system stores user information in a SQL Server database using Entity Framework Core. 对于许多应用程序而言,这种方法的效果很好。For many apps, this approach works well. 但是,你可能希望使用不同的持久性机制或数据架构。However, you may prefer to use a different persistence mechanism or data schema. 例如:For example:

  • 使用Azure 表存储或其他数据存储。You use Azure Table Storage or another data store.
  • 数据库表具有不同的结构。Your database tables have a different structure.
  • 你可能想要使用不同的数据访问方法,例如DapperYou may wish to use a different data access approach, such as Dapper.

在上述每种情况下,都可以为存储机制编写自定义的提供程序,并将该提供程序插入到应用程序中。In each of these cases, you can write a customized provider for your storage mechanism and plug that provider into your app.

ASP.NET Core 标识包含在 Visual Studio 中使用"单个用户帐户"选项的项目模板中。ASP.NET Core Identity is included in project templates in Visual Studio with the "Individual User Accounts" option.

在使用.NET Core CLI 时,添加-au Individual:When using the .NET Core CLI, add -au Individual:

dotnet new mvc -au Individual

ASP.NET Core标识体系结构The ASP.NET Core Identity architecture

ASP.NET Core标识包含类称为管理器和存储区。ASP.NET Core Identity consists of classes called managers and stores. 管理器是应用程序开发人员用来执行操作的高级类,如创建标识用户。Managers are high-level classes which an app developer uses to perform operations, such as creating an Identity user. 存储是用于指定如何保存实体(如用户和角色)的较低级别类。Stores are lower-level classes that specify how entities, such as users and roles, are persisted. 存储遵循存储库模式,并与持久性机制紧密耦合。Stores follow the repository pattern and are closely coupled with the persistence mechanism. 管理器与存储分离,这意味着,你可以在不更改应用程序代码的情况下替换持久性机制(配置除外)。Managers are decoupled from stores, which means you can replace the persistence mechanism without changing your application code (except for configuration).

下图显示了 web 应用如何与管理器进行交互,同时存储与数据访问层交互。The following diagram shows how a web app interacts with the managers, while stores interact with the data access layer.

ASP.NET Core 应用适用于管理器 (例如,UserManager、 RoleManager)。

若要创建自定义存储提供程序,请创建数据源、数据访问层以及与此数据访问层交互的存储类(上图中的绿色和灰色框)。To create a custom storage provider, create the data source, the data access layer, and the store classes that interact with this data access layer (the green and grey boxes in the diagram above). 不需要自定义管理器或与之交互的应用程序代码(上面的蓝色框)。You don't need to customize the managers or your app code that interacts with them (the blue boxes above).

在创建 UserManagerRoleManager 的新实例时,提供 user 类的类型并将 store 类的实例作为参数传递。When creating a new instance of UserManager or RoleManager you provide the type of the user class and pass an instance of the store class as an argument. 此方法使您可以插入到 ASP.NET Core的自定义的类。This approach enables you to plug your customized classes into ASP.NET Core.

重新配置应用程序以使用新的存储提供程序演示如何使用自定义存储实例化 UserManagerRoleManagerReconfigure app to use new storage provider shows how to instantiate UserManager and RoleManager with a customized store.

ASP.NET Core标识将存储的数据类型ASP.NET Core Identity stores data types

ASP.NET Core标识数据类型进行详细介绍下列各节:ASP.NET Core Identity data types are detailed in the following sections:

用户Users

网站的已注册用户。Registered users of your web site. 可以扩展IdentityUser类型,也可以将其用作你自己的自定义类型的示例。The IdentityUser type may be extended or used as an example for your own custom type. 无需从特定类型继承即可实现您自己的自定义标识存储解决方案。You don't need to inherit from a particular type to implement your own custom identity storage solution.

用户声明User Claims

有关表示用户标识的用户的一组语句(或声明)。A set of statements (or Claims) about the user that represent the user's identity. 可以启用用户标识的更大表达式,而不能通过角色来实现。Can enable greater expression of the user's identity than can be achieved through roles.

用户登录User Logins

有关在用户登录时要使用的外部身份验证提供程序(如 Facebook 或 Microsoft 帐户)的信息。Information about the external authentication provider (like Facebook or a Microsoft account) to use when logging in a user. 示例Example

角色Roles

站点的授权组。Authorization groups for your site. 包括角色 Id 和角色名称(如 "管理员" 或 "Employee")。Includes the role Id and role name (like "Admin" or "Employee"). 示例Example

数据访问层The data access layer

本主题假定您熟悉要使用的持久性机制以及如何创建该机制的实体。This topic assumes you are familiar with the persistence mechanism that you are going to use and how to create entities for that mechanism. 本主题不提供有关如何创建存储库或数据访问类; 详细信息使用 ASP.NET Core标识时,它提供了有关设计决策的一些建议。This topic doesn't provide details about how to create the repositories or data access classes; it provides some suggestions about design decisions when working with ASP.NET Core Identity.

设计自定义存储提供程序的数据访问层时,有很多自由。You have a lot of freedom when designing the data access layer for a customized store provider. 你只需为你打算在应用程序中使用的功能创建持久性机制。You only need to create persistence mechanisms for features that you intend to use in your app. 例如,如果你未在应用中使用角色,则无需为角色或用户角色关联创建存储。For example, if you are not using roles in your app, you don't need to create storage for roles or user role associations. 你的技术和现有基础结构可能需要的 ASP.NET Core标识的默认实现不同的结构。Your technology and existing infrastructure may require a structure that's very different from the default implementation of ASP.NET Core Identity. 在数据访问层中,提供用于处理存储实现结构的逻辑。In your data access layer, you provide the logic to work with the structure of your storage implementation.

数据访问层提供逻辑来将数据从 ASP.NET Core标识保存到数据源。The data access layer provides the logic to save the data from ASP.NET Core Identity to a data source. 自定义存储提供程序的数据访问层可能包含以下类来存储用户和角色信息。The data access layer for your customized storage provider might include the following classes to store user and role information.

Context 类Context class

封装信息以连接到永久性机制并执行查询。Encapsulates the information to connect to your persistence mechanism and execute queries. 几个数据类需要此类的实例,通常通过依赖关系注入来提供此类实例。Several data classes require an instance of this class, typically provided through dependency injection. 示例Example.

用户存储User Storage

存储和检索用户信息(例如用户名和密码哈希)。Stores and retrieves user information (such as user name and password hash). 示例Example

角色存储Role Storage

存储和检索角色信息(如角色名称)。Stores and retrieves role information (such as the role name). 示例Example

UserClaims 存储UserClaims Storage

存储和检索用户声明信息(如声明类型和值)。Stores and retrieves user claim information (such as the claim type and value). 示例Example

UserLogins 存储UserLogins Storage

存储和检索用户登录信息(如外部身份验证提供程序)。Stores and retrieves user login information (such as an external authentication provider). 示例Example

UserRole 存储UserRole Storage

存储和检索为哪些用户分配了哪些角色。Stores and retrieves which roles are assigned to which users. 示例Example

提示: 仅实现你打算在应用程序中使用的类。TIP: Only implement the classes you intend to use in your app.

在数据访问类中,提供代码来执行持久性机制的数据操作。In the data access classes, provide code to perform data operations for your persistence mechanism. 例如,在自定义提供程序中,你可能具有以下代码,以便在store类中创建新用户:For example, within a custom provider, you might have the following code to create a new user in the store class:

public async Task<IdentityResult> CreateAsync(ApplicationUser user, 
    CancellationToken cancellationToken = default(CancellationToken))
{
    cancellationToken.ThrowIfCancellationRequested();
    if (user == null) throw new ArgumentNullException(nameof(user));

    return await _usersTable.CreateAsync(user);
}

用于创建用户的实现逻辑位于 _usersTable.CreateAsync 方法中,如下所示。The implementation logic for creating the user is in the _usersTable.CreateAsync method, shown below.

自定义用户类Customize the user class

实现存储提供程序时,创建一个与IdentityUser 类等效的用户类。When implementing a storage provider, create a user class which is equivalent to the IdentityUser class.

用户类至少必须包含 IdUserName 属性。At a minimum, your user class must include an Id and a UserName property.

IdentityUser 类定义 UserManager 在执行请求的操作时调用的属性。The IdentityUser class defines the properties that the UserManager calls when performing requested operations. Id 属性的默认类型是字符串,但您可以从 IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin, TUserToken> 继承,并指定不同的类型。The default type of the Id property is a string, but you can inherit from IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin, TUserToken> and specify a different type. 框架需要存储实现来处理数据类型转换。The framework expects the storage implementation to handle data type conversions.

自定义用户存储Customize the user store

创建一个 UserStore 类,该类提供用户的所有数据操作的方法。Create a UserStore class that provides the methods for all data operations on the user. 此类与UserStore<TUser>类等效。This class is equivalent to the UserStore<TUser> class. UserStore 类中,实现所需的 IUserStore<TUser> 和可选接口。In your UserStore class, implement IUserStore<TUser> and the optional interfaces required. 根据应用中提供的功能,选择要实现的可选接口。You select which optional interfaces to implement based on the functionality provided in your app.

可选接口Optional interfaces

可选接口继承自 IUserStore<TUser>The optional interfaces inherit from IUserStore<TUser>. 可以在示例应用中查看部分实现的示例用户存储。You can see a partially implemented sample user store in the sample app.

UserStore 类中,你将使用你创建的数据访问类来执行操作。Within the UserStore class, you use the data access classes that you created to perform operations. 使用依赖关系注入传入它们。These are passed in using dependency injection. 例如,在使用 Dapper 实现的 SQL Server 中,UserStore 类具有 CreateAsync 方法,该方法使用 DapperUsersTable 的实例插入新记录:For example, in the SQL Server with Dapper implementation, the UserStore class has the CreateAsync method which uses an instance of DapperUsersTable to insert a new record:

public async Task<IdentityResult> CreateAsync(ApplicationUser user)
{
    string sql = "INSERT INTO dbo.CustomUser " +
        "VALUES (@id, @Email, @EmailConfirmed, @PasswordHash, @UserName)";

    int rows = await _connection.ExecuteAsync(sql, new { user.Id, user.Email, user.EmailConfirmed, user.PasswordHash, user.UserName });

    if(rows > 0)
    {
        return IdentityResult.Success;
    }
    return IdentityResult.Failed(new IdentityError { Description = $"Could not insert user {user.Email}." });
}

自定义用户存储时要实现的接口Interfaces to implement when customizing user store

  • IUserStoreIUserStore
    IUserStore<TUser>接口是必须在用户存储中实现的唯一接口。The IUserStore<TUser> interface is the only interface you must implement in the user store. 它定义用于创建、更新、删除和检索用户的方法。It defines methods for creating, updating, deleting, and retrieving users.
  • IUserClaimStoreIUserClaimStore
    IUserClaimStore<TUser>接口定义为启用用户声明而实现的方法。The IUserClaimStore<TUser> interface defines the methods you implement to enable user claims. 它包含添加、删除和检索用户声明的方法。It contains methods for adding, removing and retrieving user claims.
  • IUserLoginStoreIUserLoginStore
    IUserLoginStore<TUser>定义为启用外部身份验证提供程序而实现的方法。The IUserLoginStore<TUser> defines the methods you implement to enable external authentication providers. 它包含添加、删除和检索用户登录名的方法,以及用于根据登录信息检索用户的方法。It contains methods for adding, removing and retrieving user logins, and a method for retrieving a user based on the login information.
  • IUserRoleStoreIUserRoleStore
    IUserRoleStore<TUser>接口定义将用户映射到角色时所实现的方法。The IUserRoleStore<TUser> interface defines the methods you implement to map a user to a role. 它包含添加、删除和检索用户角色的方法,以及用于检查是否向用户分配了角色的方法。It contains methods to add, remove, and retrieve a user's roles, and a method to check if a user is assigned to a role.
  • IUserPasswordStoreIUserPasswordStore
    IUserPasswordStore<TUser>接口定义用于保存哈希密码的方法。The IUserPasswordStore<TUser> interface defines the methods you implement to persist hashed passwords. 它包含用于获取和设置哈希密码的方法,以及一个指示用户是否已设置密码的方法。It contains methods for getting and setting the hashed password, and a method that indicates whether the user has set a password.
  • IUserSecurityStampStoreIUserSecurityStampStore
    IUserSecurityStampStore<TUser>接口定义你实现的方法,以使用安全戳记来指示用户的帐户信息是否已更改。The IUserSecurityStampStore<TUser> interface defines the methods you implement to use a security stamp for indicating whether the user's account information has changed. 当用户更改密码或添加或删除登录名时,此戳记会更新。This stamp is updated when a user changes the password, or adds or removes logins. 它包含用于获取和设置安全标记的方法。It contains methods for getting and setting the security stamp.
  • IUserTwoFactorStoreIUserTwoFactorStore
    IUserTwoFactorStore<TUser>接口定义为支持双因素身份验证而实现的方法。The IUserTwoFactorStore<TUser> interface defines the methods you implement to support two factor authentication. 它包含的方法可用于获取和设置是否为用户启用了双因素身份验证。It contains methods for getting and setting whether two factor authentication is enabled for a user.
  • IUserPhoneNumberStoreIUserPhoneNumberStore
    IUserPhoneNumberStore<TUser>接口定义为存储用户电话号码而实现的方法。The IUserPhoneNumberStore<TUser> interface defines the methods you implement to store user phone numbers. 它包含的方法可用于获取和设置电话号码,以及电话号码是否已确认。It contains methods for getting and setting the phone number and whether the phone number is confirmed.
  • IUserEmailStoreIUserEmailStore
    IUserEmailStore<TUser>接口定义用于存储用户电子邮件地址的方法。The IUserEmailStore<TUser> interface defines the methods you implement to store user email addresses. 它包含用于获取和设置电子邮件地址以及是否已确认电子邮件的方法。It contains methods for getting and setting the email address and whether the email is confirmed.
  • IUserLockoutStoreIUserLockoutStore
    IUserLockoutStore<TUser>接口定义用于存储有关锁定帐户的信息的方法。The IUserLockoutStore<TUser> interface defines the methods you implement to store information about locking an account. 它包含用于跟踪失败的访问尝试和锁定的方法。It contains methods for tracking failed access attempts and lockouts.
  • IQueryableUserStoreIQueryableUserStore
    IQueryableUserStore<TUser>接口定义为提供可查询用户存储而实现的成员。The IQueryableUserStore<TUser> interface defines the members you implement to provide a queryable user store.

只需实现应用程序中所需的接口。You implement only the interfaces that are needed in your app. 例如:For example:

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

IdentityUserClaim、IdentityUserLogin 和 IdentityUserRoleIdentityUserClaim, IdentityUserLogin, and IdentityUserRole

Microsoft.AspNet.Identity.EntityFramework命名空间包含IdentityUserClaimIdentityUserLoginIdentityUserRole 类的实现。The Microsoft.AspNet.Identity.EntityFramework namespace contains implementations of the IdentityUserClaim, IdentityUserLogin, and IdentityUserRole classes. 如果使用这些功能,可能需要创建自己版本的这些类并定义应用的属性。If you are using these features, you may want to create your own versions of these classes and define the properties for your app. 但是,在执行基本操作(如添加或删除用户的声明)时,如果不将这些实体加载到内存中,则可能会更有效。However, sometimes it's more efficient to not load these entities into memory when performing basic operations (such as adding or removing a user's claim). 而后端存储类可以直接对数据源执行这些操作。Instead, the backend store classes can execute these operations directly on the data source. 例如,UserStore.GetClaimsAsync 方法可以调用 userClaimTable.FindByUserId(user.Id) 方法来直接对该表执行查询并返回声明列表。For example, the UserStore.GetClaimsAsync method can call the userClaimTable.FindByUserId(user.Id) method to execute a query on that table directly and return a list of claims.

自定义 role 类Customize the role class

实现角色存储提供程序时,可以创建自定义角色类型。When implementing a role storage provider, you can create a custom role type. 它不需要实现特定接口,但它必须具有 Id,并且通常具有 Name 属性。It need not implement a particular interface, but it must have an Id and typically it will have a Name property.

下面是一个示例 role 类:The following is an example role class:

using System;

namespace CustomIdentityProviderSample.CustomProvider
{
    public class ApplicationRole
    {
        public Guid Id { get; set; } = Guid.NewGuid();
        public string Name { get; set; }
    }
}

自定义角色存储Customize the role store

你可以创建一个 RoleStore 类,该类提供针对角色的所有数据操作的方法。You can create a RoleStore class that provides the methods for all data operations on roles. 此类与RoleStore<TRole>类等效。This class is equivalent to the RoleStore<TRole> class. RoleStore 类中,你可以实现 IRoleStore<TRole>,还可以选择 IQueryableRoleStore<TRole> 接口。In the RoleStore class, you implement the IRoleStore<TRole> and optionally the IQueryableRoleStore<TRole> interface.

  • IRoleStore<TRole>IRoleStore<TRole>
    IRoleStore<TRole>接口定义要在角色存储类中实现的方法。The IRoleStore<TRole> interface defines the methods to implement in the role store class. 它包含用于创建、更新、删除和检索角色的方法。It contains methods for creating, updating, deleting, and retrieving roles.
  • RoleStore<TRole>RoleStore<TRole>
    若要自定义 RoleStore,请创建实现 IRoleStore<TRole> 接口的类。To customize RoleStore, create a class that implements the IRoleStore<TRole> interface.

重新配置应用程序以使用新的存储提供程序Reconfigure app to use a new storage provider

实现存储提供程序后,可将应用程序配置为使用该访问接口。Once you have implemented a storage provider, you configure your app to use it. 如果你的应用程序使用了默认提供程序,请将其替换为你的自定义提供程序。If your app used the default provider, replace it with your custom provider.

  1. 删除 Microsoft.AspNetCore.EntityFramework.Identity NuGet 包。Remove the Microsoft.AspNetCore.EntityFramework.Identity NuGet package.
  2. 如果存储提供程序驻留在单独的项目或包中,请添加对它的引用。If the storage provider resides in a separate project or package, add a reference to it.
  3. 使用存储提供程序的命名空间的 using 语句替换对 Microsoft.AspNetCore.EntityFramework.Identity 的所有引用。Replace all references to Microsoft.AspNetCore.EntityFramework.Identity with a using statement for the namespace of your storage provider.
  4. ConfigureServices 方法中,将 AddIdentity 方法更改为使用您的自定义类型。In the ConfigureServices method, change the AddIdentity method to use your custom types. 出于此目的,你可以创建自己的扩展方法。You can create your own extension methods for this purpose. 有关示例,请参阅IdentityServiceCollectionExtensionsSee IdentityServiceCollectionExtensions for an example.
  5. 如果你使用的是角色,请将 RoleManager 更新为使用你 RoleStore 的类。If you are using Roles, update the RoleManager to use your RoleStore class.
  6. 更新应用程序配置的连接字符串和凭据。Update the connection string and credentials to your app's configuration.

示例:Example:

public void ConfigureServices(IServiceCollection services)
{
    // Add identity types
    services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddDefaultTokenProviders();

    // Identity Services
    services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
    services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
    string connectionString = Configuration.GetConnectionString("DefaultConnection");
    services.AddTransient<SqlConnection>(e => new SqlConnection(connectionString));
    services.AddTransient<DapperUsersTable>();

    // additional configuration
}

引用References