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

; автор — Том ФитцМакен (Tom FitzMacken)

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

Пример реализации настраиваемого поставщика хранилища см. в разделе Реализация пользовательского поставщика хранилища mySQL ASP.NET Identity Storage.

Эта статья была обновлена для ASP.NET Identity 2.0.

Версии программного обеспечения, используемые в этом руководстве

  • Visual Studio 2013 с обновлением 2
  • Удостоверение ASP.NET 2

Введение

По умолчанию система удостоверений ASP.NET хранит сведения о пользователе в базе данных SQL Server и использует Entity Framework Code First для создания базы данных. Для многих приложений этот подход хорошо работает. Однако вы можете использовать другой тип механизма сохраняемости, например хранилище таблиц Azure, или у вас уже могут быть таблицы базы данных со структурой, отличной от реализации по умолчанию. В любом случае можно написать настраиваемый поставщик для механизма хранения и подключить его к приложению.

ASP.NET Identity включается по умолчанию во многих шаблонах Visual Studio 2013. Обновления для ASP.NET identity можно получить с помощью пакета Microsoft AspNet Identity EntityFramework NuGet.

Этот раздел включает следующие подразделы:

Общие сведения об архитектуре

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

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

Схема взаимодействия веб-приложения с руководителями

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

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

Общие сведения о хранимых данных

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

Данные Описание
Пользователи Зарегистрированные пользователи веб-сайта. Включает идентификатор пользователя и имя пользователя. Может включать хэш-пароль, если пользователи входят с учетными данными, которые относятся к вашему сайту (а не с использованием учетных данных с внешнего сайта, например Facebook), и метку безопасности, указывающую, изменилось ли что-либо в учетных данных пользователя. Также может включать адрес электронной почты, номер телефона, включена ли двухфакторная проверка подлинности, текущее количество неудачных попыток входа и блокировка учетной записи.
Утверждения пользователей Набор инструкций (или утверждений) о пользователе, представляющих его удостоверение. Может обеспечить большее выражение удостоверения пользователя, чем может быть достигнуто с помощью ролей.
Имена для входа пользователей Сведения о внешнем поставщике проверки подлинности (например, Facebook), который используется при входе пользователя.
Роли Группы авторизации для сайта. Включает идентификатор роли и имя роли (например, "Администратор" или "Сотрудник").

Создание уровня доступа к данным

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

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

Сведения о реализации репозиториев данных MySQL для ASP.NET Identity 2.0 см. в статье MySQLIdentity.sql.

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

Класс Описание Пример
Контекст Инкапсулирует сведения для подключения к механизму сохраняемости и выполнения запросов. Этот класс занимает центральное место на уровне доступа к данным. Для выполнения операций другим классам данных потребуется экземпляр этого класса. Вы также инициализируете классы хранилища с помощью экземпляра этого класса. База данных MySQL
Хранилище пользователей Сохраняет и извлекает сведения о пользователе (например, имя пользователя и хэш пароля). 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);
}

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

Настройка класса пользователя

При реализации собственного поставщика хранилища необходимо создать класс пользователя, эквивалентный классу IdentityUser в пространстве имен Microsoft.ASP.NET.Identity.EntityFramework :

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

Изображение класса Identity User

Интерфейс IUser<TKey> определяет свойства, которые UserManager пытается вызвать при выполнении запрошенных операций. Интерфейс содержит два свойства : Id и UserName. Интерфейс IUser<TKey> позволяет указать тип ключа для пользователя с помощью универсального параметра TKey . Тип свойства Id соответствует значению параметра TKey.

Платформа identity также предоставляет интерфейс IUser (без универсального параметра), если требуется использовать строковое значение для ключа.

Класс IdentityUser реализует IUser и содержит любые дополнительные свойства или конструкторы для пользователей веб-сайта. В следующем примере показан класс IdentityUser, использующий целое число для ключа. Для поля Id задано значение 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, предоставляющий методы для всех операций с данными пользователя. Этот класс эквивалентен классу UserStore<TUser> в пространстве имен Microsoft.ASP.NET.Identity.EntityFramework . В классе UserStore вы реализуете IUserStore TUserStore<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() { ... }
}

В этом примере конструктор, который принимает параметр с именем database типа ExampleDatabase, является лишь иллюстрацией передачи класса доступа к данным. Например, в реализации MySQL этот конструктор принимает параметр типа MySQLDatabase.

В классе UserStore используются классы доступа к данным, созданные для выполнения операций. Например, в реализации MySQL класс UserStore имеет метод CreateAsync, который использует экземпляр UserTable для вставки новой записи. Метод Insert для объекта userTable — это тот же метод, который был показан в предыдущем разделе.

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

public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
    ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
    return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}

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

При реализации собственного поставщика хранилища необходимо создать класс роли, эквивалентный классу IdentityRole в пространстве имен Microsoft.ASP.NET.Identity.EntityFramework :

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

Изображение класса Identity Role

Интерфейс IRole<TKey> определяет свойства, которые RoleManager пытается вызвать при выполнении запрошенных операций. Интерфейс содержит два свойства: Id и Name. Интерфейс IRole<TKey> позволяет указать тип ключа для роли с помощью универсального параметра TKey . Тип свойства Id соответствует значению параметра TKey.

Платформа identity также предоставляет интерфейс IRole (без универсального параметра), если требуется использовать строковое значение для ключа.

В следующем примере показан класс IdentityRole, который использует целое число для ключа. Для поля Id задано значение 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, который предоставляет методы для всех операций с данными с ролями. Этот класс эквивалентен классу RoleStore<TRole> в пространстве имен Microsoft.ASP.NET.Identity.EntityFramework. В классе 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. Этот класс необходимо реализовать только в том случае, если вы хотите использовать роли в системе. Конструктор, который принимает параметр с именем database типа ExampleDatabase, является лишь иллюстрацией того, как передать класс доступа к данным. Например, в реализации MySQL этот конструктор принимает параметр типа MySQLDatabase.

    Полную реализацию см. в статье RoleStore (MySQL).

Перенастройка приложения для использования нового поставщика хранилища

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

Замена поставщика хранилища по умолчанию в проекте MVC

  1. В окне Управление пакетами NuGet удалите пакет EntityFramework microsoft ASP.NET Identity . Этот пакет можно найти, выполнив поиск в разделе Установленные пакеты для Identity.EntityFramework.
    Изображение окна nu get packages Вам будет предложено удалить Entity Framework. Если он не нужен в других частях приложения, его можно удалить.

  2. В файле IdentityModels.cs в папке Models удалите или закомментируйте классы ApplicationUser и ApplicationDbContext . В приложении MVC можно удалить весь файл IdentityModels.cs. В приложении веб-формы удалите два класса, но сохраните вспомогательный класс, который также находится в файле IdentityModels.cs.

  3. Если поставщик хранилища находится в отдельном проекте, добавьте ссылку на него в веб-приложение.

  4. Замените все ссылки на using Microsoft.AspNet.Identity.EntityFramework; оператором using для пространства имен поставщика хранилища.

  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. Замените все ссылки на ApplicationUserна IdentityUser.

  8. Проект по умолчанию включает некоторые члены в классе пользователя, которые не определены в интерфейсе IUser; например Email, PasswordHash и GenerateUserIdentityAsync. Если класс пользователя не содержит этих членов, необходимо либо реализовать их, либо изменить код, в котором используются эти члены.

  9. Если вы создали экземпляры RoleManager, измените этот код, чтобы использовать новый класс RoleStore.

    var roleManager = new RoleManager<IdentityRole>(new RoleStore(context.Get<ExampleStorageContext>()));
    
  10. Проект по умолчанию предназначен для класса пользователя, который имеет строковое значение для ключа. Если класс пользователя имеет другой тип ключа (например, целое число), необходимо изменить проект для работы с типом. См . раздел Изменение первичного ключа для пользователей в ASP.NET удостоверении.

  11. При необходимости добавьте строку подключения в файл Web.config.

Другие ресурсы