标识的自定义存储提供程序

ASP.NET Core标识的自定义的存储提供程序

作者:Steve Smith

ASP.NET Core标识是一种可扩展系统,可用于创建自定义存储提供程序并将其连接到你的应用。 本主题介绍如何创建 ASP.NET Core标识的自定义的存储提供程序。 它介绍了用于创建自己的存储提供程序的重要概念,但并不是分步演练。

查看或下载 GitHub 中的示例

简介

默认情况下,ASP.NET Core标识系统中使用实体框架核心的 SQL Server 数据库存储用户信息。 对于许多应用程序而言,这种方法的效果很好。 但是,你可能希望使用不同的持久性机制或数据架构。 例如:

  • 使用Azure 表存储或其他数据存储。
  • 数据库表具有不同的结构。
  • 你可能想要使用不同的数据访问方法,例如Dapper

在上述每种情况下,都可以为存储机制编写自定义的提供程序,并将该提供程序插入到应用程序中。

ASP.NET Core 标识包含在 Visual Studio 中使用"单个用户帐户"选项的项目模板中。

在使用.NET Core CLI 时,添加-au Individual:

dotnet new mvc -au Individual

ASP.NET Core标识体系结构

ASP.NET Core标识包含类称为管理器和存储区。 管理器是应用程序开发人员用来执行操作的高级类,如创建标识用户。 存储是用于指定如何保存实体(如用户和角色)的较低级别类。 存储遵循存储库模式,并与持久性机制紧密耦合。 管理器与存储分离,这意味着,你可以在不更改应用程序代码的情况下替换持久性机制(配置除外)。

下图显示了 web 应用如何与管理器进行交互,同时存储与数据访问层交互。

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

若要创建自定义存储提供程序,请创建数据源、数据访问层以及与此数据访问层交互的存储类(上图中的绿色和灰色框)。 不需要自定义管理器或与之交互的应用程序代码(上面的蓝色框)。

在创建 UserManagerRoleManager 的新实例时,提供 user 类的类型并将 store 类的实例作为参数传递。 此方法使您可以插入到 ASP.NET Core的自定义的类。

重新配置应用程序以使用新的存储提供程序演示如何使用自定义存储实例化 UserManagerRoleManager

ASP.NET Core标识将存储的数据类型

ASP.NET Core标识数据类型进行详细介绍下列各节:

用户

网站的已注册用户。 可以扩展IdentityUser类型,也可以将其用作你自己的自定义类型的示例。 无需从特定类型继承即可实现您自己的自定义标识存储解决方案。

用户声明

有关表示用户标识的用户的一组语句(或声明)。 可以启用用户标识的更大表达式,而不能通过角色来实现。

用户登录

有关在用户登录时要使用的外部身份验证提供程序(如 Facebook 或 Microsoft 帐户)的信息。 示例

角色

站点的授权组。 包括角色 Id 和角色名称(如 "管理员" 或 "Employee")。 示例

数据访问层

本主题假定您熟悉要使用的持久性机制以及如何创建该机制的实体。 本主题不提供有关如何创建存储库或数据访问类; 详细信息使用 ASP.NET Core标识时,它提供了有关设计决策的一些建议。

设计自定义存储提供程序的数据访问层时,有很多自由。 你只需为你打算在应用程序中使用的功能创建持久性机制。 例如,如果你未在应用中使用角色,则无需为角色或用户角色关联创建存储。 你的技术和现有基础结构可能需要的 ASP.NET Core标识的默认实现不同的结构。 在数据访问层中,提供用于处理存储实现结构的逻辑。

数据访问层提供逻辑来将数据从 ASP.NET Core标识保存到数据源。 自定义存储提供程序的数据访问层可能包含以下类来存储用户和角色信息。

Context 类

封装信息以连接到永久性机制并执行查询。 几个数据类需要此类的实例,通常通过依赖关系注入来提供此类实例。 示例

用户存储

存储和检索用户信息(例如用户名和密码哈希)。 示例

角色存储

存储和检索角色信息(如角色名称)。 示例

UserClaims 存储

存储和检索用户声明信息(如声明类型和值)。 示例

UserLogins 存储

存储和检索用户登录信息(如外部身份验证提供程序)。 示例

UserRole 存储

存储和检索为哪些用户分配了哪些角色。 示例

提示: 仅实现你打算在应用程序中使用的类。

在数据访问类中,提供代码来执行持久性机制的数据操作。 例如,在自定义提供程序中,你可能具有以下代码,以便在store类中创建新用户:

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 方法中,如下所示。

自定义用户类

实现存储提供程序时,创建一个与IdentityUser 类等效的用户类。

用户类至少必须包含 IdUserName 属性。

IdentityUser 类定义 UserManager 在执行请求的操作时调用的属性。 Id 属性的默认类型是字符串,但您可以从 IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin, TUserToken> 继承,并指定不同的类型。 框架需要存储实现来处理数据类型转换。

自定义用户存储

创建一个 UserStore 类,该类提供用户的所有数据操作的方法。 此类与UserStore<TUser>类等效。 UserStore 类中,实现所需的 IUserStore<TUser> 和可选接口。 根据应用中提供的功能,选择要实现的可选接口。

可选接口

可选接口继承自 IUserStore<TUser> 可以在示例应用中查看部分实现的示例用户存储。

UserStore 类中,你将使用你创建的数据访问类来执行操作。 使用依赖关系注入传入它们。 例如,在使用 Dapper 实现的 SQL Server 中,UserStore 类具有 CreateAsync 方法,该方法使用 DapperUsersTable 的实例插入新记录:

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}." });
}

自定义用户存储时要实现的接口

  • IUserStore
    IUserStore<TUser>接口是必须在用户存储中实现的唯一接口。 它定义用于创建、更新、删除和检索用户的方法。
  • IUserClaimStore
    IUserClaimStore<TUser>接口定义为启用用户声明而实现的方法。 它包含添加、删除和检索用户声明的方法。
  • IUserLoginStore
    IUserLoginStore<TUser>定义为启用外部身份验证提供程序而实现的方法。 它包含添加、删除和检索用户登录名的方法,以及用于根据登录信息检索用户的方法。
  • IUserRoleStore
    IUserRoleStore<TUser>接口定义将用户映射到角色时所实现的方法。 它包含添加、删除和检索用户角色的方法,以及用于检查是否向用户分配了角色的方法。
  • IUserPasswordStore
    IUserPasswordStore<TUser>接口定义用于保存哈希密码的方法。 它包含用于获取和设置哈希密码的方法,以及一个指示用户是否已设置密码的方法。
  • IUserSecurityStampStore
    IUserSecurityStampStore<TUser>接口定义你实现的方法,以使用安全戳记来指示用户的帐户信息是否已更改。 当用户更改密码或添加或删除登录名时,此戳记会更新。 它包含用于获取和设置安全标记的方法。
  • IUserTwoFactorStore
    IUserTwoFactorStore<TUser>接口定义为支持双因素身份验证而实现的方法。 它包含的方法可用于获取和设置是否为用户启用了双因素身份验证。
  • IUserPhoneNumberStore
    IUserPhoneNumberStore<TUser>接口定义为存储用户电话号码而实现的方法。 它包含的方法可用于获取和设置电话号码,以及电话号码是否已确认。
  • IUserEmailStore
    IUserEmailStore<TUser>接口定义用于存储用户电子邮件地址的方法。 它包含用于获取和设置电子邮件地址以及是否已确认电子邮件的方法。
  • IUserLockoutStore
    IUserLockoutStore<TUser>接口定义用于存储有关锁定帐户的信息的方法。 它包含用于跟踪失败的访问尝试和锁定的方法。
  • IQueryableUserStore
    IQueryableUserStore<TUser>接口定义为提供可查询用户存储而实现的成员。

只需实现应用程序中所需的接口。 例如:

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

IdentityUserClaim、IdentityUserLogin 和 IdentityUserRole

Microsoft.AspNet.Identity.EntityFramework命名空间包含IdentityUserClaimIdentityUserLoginIdentityUserRole 类的实现。 如果使用这些功能,可能需要创建自己版本的这些类并定义应用的属性。 但是,在执行基本操作(如添加或删除用户的声明)时,如果不将这些实体加载到内存中,则可能会更有效。 而后端存储类可以直接对数据源执行这些操作。 例如,UserStore.GetClaimsAsync 方法可以调用 userClaimTable.FindByUserId(user.Id) 方法来直接对该表执行查询并返回声明列表。

自定义 role 类

实现角色存储提供程序时,可以创建自定义角色类型。 它不需要实现特定接口,但它必须具有 Id,并且通常具有 Name 属性。

下面是一个示例 role 类:

using System;

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

自定义角色存储

你可以创建一个 RoleStore 类,该类提供针对角色的所有数据操作的方法。 此类与RoleStore<TRole>类等效。 RoleStore 类中,你可以实现 IRoleStore<TRole>,还可以选择 IQueryableRoleStore<TRole> 接口。

  • IRoleStore<TRole>
    IRoleStore<TRole>接口定义要在角色存储类中实现的方法。 它包含用于创建、更新、删除和检索角色的方法。
  • RoleStore<TRole>
    若要自定义 RoleStore,请创建实现 IRoleStore<TRole> 接口的类。

重新配置应用程序以使用新的存储提供程序

实现存储提供程序后,可将应用程序配置为使用该访问接口。 如果你的应用程序使用了默认提供程序,请将其替换为你的自定义提供程序。

  1. 删除 Microsoft.AspNetCore.EntityFramework.Identity NuGet 包。
  2. 如果存储提供程序驻留在单独的项目或包中,请添加对它的引用。
  3. 使用存储提供程序的命名空间的 using 语句替换对 Microsoft.AspNetCore.EntityFramework.Identity 的所有引用。
  4. ConfigureServices 方法中,将 AddIdentity 方法更改为使用您的自定义类型。 出于此目的,你可以创建自己的扩展方法。 有关示例,请参阅IdentityServiceCollectionExtensions
  5. 如果你使用的是角色,请将 RoleManager 更新为使用你 RoleStore 的类。
  6. 更新应用程序配置的连接字符串和凭据。

示例:

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
}

引用

目录