Пользовательские поставщики хранилища для ASP.NET Core Identity

Автор: Стив Смит (Steve Smith)

ASP.NET Core Identity — это расширяемая система, которая позволяет создавать настраиваемый поставщик хранилища и подключать его к приложению. В этом разделе описывается создание настраиваемого поставщика хранилища для ASP.NET Core Identity. В ней рассматриваются важные понятия для создания собственного поставщика хранилища, но это не пошаговые инструкции. Сведения о настройке модели см. в Identity разделе Identity "Настройка модели".

Введение

По умолчанию система ASP.NET Core Identity хранит сведения о пользователе в базе данных SQL Server с помощью Entity Framework Core. Для многих приложений этот подход хорошо работает. Однако вы можете использовать другой механизм сохраняемости или схему данных. Например:

  • Вы используете таблицу Azure служба хранилища или другое хранилище данных.
  • Таблицы базы данных имеют другую структуру.
  • Возможно, вы хотите использовать другой подход к доступу к данным, например Dapper.

В каждом из этих случаев можно написать настраиваемый поставщик для механизма хранения и подключить этого поставщика к приложению.

ASP.NET Core Identity входит в шаблоны проектов в Visual Studio с параметром "Отдельные учетные записи пользователей".

При использовании интерфейса командной строки .NET Core добавьте -au Individual:

dotnet new mvc -au Individual

Архитектура ядра ASP.NET Identity

ASP.NET Core Identity состоит из классов, называемых менеджерами и магазинами. Руководители — это высокоуровневые классы , которые разработчик приложений использует для выполнения операций, таких как создание Identity пользователя. Хранилища — это классы более низкого уровня, определяющие способ сохранения сущностей, таких как пользователи и роли. Магазины следуют шаблону репозитория и тесно связаны с механизмом сохраняемости. Диспетчеры отделены от магазинов, что означает, что можно заменить механизм сохраняемости без изменения кода приложения (за исключением конфигурации).

На следующей схеме показано, как веб-приложение взаимодействует с руководителями, а хранилища взаимодействуют с уровнем доступа к данным.

ASP.NET Core Apps work with Managers (for example, UserManager, RoleManager). Managers work with Stores (for example, UserStore) which communicate with a Data Source using a library like Entity Framework Core.

Чтобы создать настраиваемый поставщик хранилища, создайте источник данных, уровень доступа к данным и классы хранилища, взаимодействующие с этим уровнем доступа к данным (зеленые и серые поля на схеме выше). Вам не нужно настраивать диспетчеры или код приложения, взаимодействующие с ними (синие поля выше).

При создании нового экземпляра UserManager или RoleManager укажите тип пользовательского класса и передайте экземпляр класса store в качестве аргумента. Этот подход позволяет подключать настраиваемые классы к ASP.NET Core.

Перенастройка приложения для использования нового поставщика хранилища показывает, как создать экземпляр UserManager и RoleManager с помощью настраиваемого хранилища.

ASP.NET Core Identity хранит типы данных

ASP.NET основные Identity типы данных подробно описаны в следующих разделах:

Пользователи

Зарегистрированные пользователи веб-сайта. Тип IdentityUser может быть расширен или использован в качестве примера для собственного пользовательского типа. Вам не нужно наследовать от определенного типа для реализации собственного решения хранилища удостоверений.

Утверждения пользователей

Набор инструкций (или утверждений) о пользователе, представляющего удостоверение пользователя. Может включить большее выражение удостоверения пользователя, чем можно достичь с помощью ролей.

Имена входа пользователей

Сведения о внешнем поставщике проверки подлинности (например, Facebook или учетной записи Майкрософт) для использования при входе в систему пользователя. Пример

Роли

Группы авторизации для сайта. Включает идентификатор роли и имя роли (например, "Администратор" или "Сотрудник"). Пример

Уровень доступа к данным

В этом разделе предполагается, что вы знакомы с механизмом сохраняемости, используемым и как создавать сущности для этого механизма. В этом разделе не содержатся сведения о создании репозиториев или классов доступа к данным; Он предоставляет некоторые предложения по проектированию решений при работе с ASP.NET Core Identity.

При проектировании уровня доступа к данным для настраиваемого поставщика хранилища у вас много свободы. Необходимо создать только механизмы сохраняемости для функций, которые вы планируете использовать в приложении. Например, если вы не используете роли в приложении, вам не нужно создавать хранилище для ролей или связей ролей пользователей. Для вашей технологии и существующей инфраструктуры может потребоваться структура, которая отличается от реализации по умолчанию ASP.NET Core Identity. На уровне доступа к данным вы предоставляете логику для работы со структурой реализации хранилища.

Уровень доступа к данным предоставляет логику для сохранения данных из ASP.NET Core Identity в источник данных. Уровень доступа к данным для настраиваемого поставщика хранилища может включать следующие классы для хранения сведений о пользователе и роли.

Context - класс

Инкапсулирует сведения для подключения к механизму сохраняемости и выполнения запросов. Для нескольких классов данных требуется экземпляр этого класса, который обычно предоставляется путем внедрения зависимостей. Пример.

Служба хранилища пользователя

Хранит и извлекает сведения о пользователе (например, имя пользователя и хэш паролей). Пример

Роль служба хранилища

Хранит и извлекает сведения о роли (например, имя роли). Пример

Служба хранилища UserClaims

Хранит и извлекает сведения о утверждении пользователя (например, тип утверждения и значение). Пример

Служба хранилища UserLogins

Хранит и извлекает сведения о входе пользователя (например, внешний поставщик проверки подлинности). Пример

UserRole служба хранилища

Хранит и извлекает роли, которым назначены пользователи. Пример

СОВЕТ. Реализуйте только классы, которые вы планируете использовать в приложении.

В классах доступа к данным предоставьте код для выполнения операций с данными для механизма сохраняемости. Например, в пользовательском поставщике может потребоваться следующий код для создания нового пользователя в классе хранилища :

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 В классе используются классы доступа к данным, созданные для выполнения операций. Они передаются при использовании внедрения зависимостей. Например, в SQL Server с реализацией Dapper класс имеет CreateAsync метод, UserStore который использует экземпляр 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> определяет методы, которые вы реализуете для поддержки двухфакторной проверки подлинности. Он содержит методы получения и настройки включения двухфакторной проверки подлинности для пользователя.
  • IUser Телефон NumberStore
    Интерфейс 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 имен содержит реализацииIdentity классов UserClaim IdentityUserLoginи IdentityUserRole. Если вы используете эти функции, вам может потребоваться создать собственные версии этих классов и определить свойства для приложения. Однако иногда это более эффективно, чтобы не загружать эти сущности в память при выполнении основных операций (таких как добавление или удаление утверждения пользователя). Вместо этого классы внутреннего хранилища могут выполнять эти операции непосредственно в источнике данных. Например, UserStore.GetClaimsAsync метод может вызывать userClaimTable.FindByUserId(user.Id) метод для выполнения запроса в этой таблице напрямую и возвращать список утверждений.

Настройка класса роли

При реализации поставщика хранилища ролей можно создать пользовательский тип роли. Он не должен реализовывать конкретный интерфейс, но он должен иметь свойство, и обычно оно будет иметь IdName свойство.

Ниже приведен пример класса роли:

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. Замените все ссылки на Microsoft.AspNetCore.EntityFramework.Identity инструкцию using для пространства имен поставщика хранилища.
  4. Измените метод, чтобы использовать пользовательские AddIdentity типы. Для этого можно создать собственные методы расширения. Пример см Identity. в разделе ServiceCollectionExtensions .
  5. При использовании ролей обновите RoleManagerRoleStore класс.
  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
}
var builder = WebApplication.CreateBuilder(args);

// Add identity types
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddDefaultTokenProviders();

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

// additional configuration

builder.Services.AddRazorPages();

var app = builder.Build();

Ссылки