Información general sobre los proveedores de almacenamiento personalizado para ASP.NET Identity
por Tom FitzMacken
ASP.NET Identity es un sistema extensible que permite crear su propio proveedor de almacenamiento y conectarlo a la aplicación sin volver a trabajar en ella. En este tema se describe cómo crear un proveedor de almacenamiento personalizado para ASP.NET Identity. Trata los conceptos importantes para crear su propio proveedor de almacenamiento, pero no es un tutorial paso a paso de la implementación de un proveedor de almacenamiento personalizado.
Para obtener un ejemplo de implementación de un proveedor de almacenamiento personalizado, consulte Implementación de un proveedor de almacenamiento de identidades de MySQL personalizado ASP.NET.
Este tema se actualizó para ASP.NET Identity 2.0.
Versiones de software usadas en el tutorial
- Visual Studio 2013 con la actualización 2
- ASP.NET Identity 2
Introducción
De forma predeterminada, el sistema ASP.NET Identity almacena información de usuario en una base de datos de SQL Server y usa Entity Framework Code First para crear la base de datos. Para muchas aplicaciones, este enfoque funciona bien. Sin embargo, es posible que prefiera usar un tipo diferente de mecanismo de persistencia, como Azure Table Storage, o puede que ya tenga tablas de base de datos con una estructura muy diferente a la implementación predeterminada. En cualquier caso, puede escribir un proveedor personalizado para el mecanismo de almacenamiento y conectar ese proveedor a la aplicación.
ASP.NET Identity se incluye de forma predeterminada en muchas de las plantillas de Visual Studio 2013. Puede obtener actualizaciones de ASP.NET Identity a través de paquete NuGet EntityFramework de Microsoft AspNet Identity.
Este tema incluye las siguientes secciones:
- Descripción de la arquitectura
- Descripción de los datos que se almacenan
- Creación de la capa de acceso a datos
- Personalización de la clase de usuario
- Personalización del almacén de usuario
- Personalización de la clase de rol
- Personalización del almacén de roles
- Volver a configurar la aplicación para usar el nuevo proveedor de almacenamiento
- Otras implementaciones de proveedores de almacenamiento personalizados
Descripción de la arquitectura
ASP.NET Identity consta de clases denominadas administradores y almacenes. Los administradores son clases de alto nivel que un desarrollador de aplicaciones usa para realizar operaciones, como la creación de un usuario, en el sistema ASP.NET Identity. Los almacenes son clases de nivel inferior que especifican cómo se conservan las entidades, como los usuarios y los roles. Los almacenes están estrechamente acoplados con el mecanismo de persistencia, pero los administradores se desacoplan de los almacenes, lo que significa que puede reemplazar el mecanismo de persistencia sin interrumpir toda la aplicación.
En el siguiente diagrama se muestra cómo interactúa la aplicación web con los administradores, y los almacenes interactúan con la capa de acceso a datos.
Para crear un proveedor de almacenamiento personalizado para ASP.NET Identity, debe crear el origen de datos, la capa de acceso a datos y las clases de almacén que interactúan con esta capa de acceso a datos. Puede seguir usando las mismas API de administrador para realizar operaciones de datos en el usuario, pero ahora esos datos se guardan en un sistema de almacenamiento diferente.
No es necesario personalizar las clases de administrador porque al crear una nueva instancia de UserManager o RoleManager se proporciona el tipo de la clase de usuario y se pasa una instancia de la clase de almacén como argumento. Este enfoque le permite conectar las clases personalizadas a la estructura existente. Verá cómo crear instancias de UserManager y RoleManager con las clases de almacén personalizadas en la sección Volver a configurar la aplicación para usar el nuevo proveedor de almacenamiento.
Descripción de los datos almacenados
Para implementar un proveedor de almacenamiento personalizado, debe comprender los tipos de datos que se usan con ASP.NET Identity y decidir qué características son relevantes para la aplicación.
Data | Descripción |
---|---|
Usuarios | Usuarios registrados del sitio web. Incluye el identificador de usuario y el nombre de usuario. Puede incluir una contraseña con hash si los usuarios inician sesión con credenciales específicas de su sitio (en lugar de usar credenciales de un sitio externo como Facebook) y una marca de seguridad para indicar si algo ha cambiado en las credenciales de usuario. También puede incluir la dirección de correo electrónico, el número de teléfono, si la autenticación en dos fases está habilitada, el número actual de inicios de sesión con errores y si se ha bloqueado una cuenta. |
Notificaciones de usuario | Un conjunto de instrucciones (o notificaciones) sobre el usuario que representa la identidad del usuario. Puede habilitar una expresión mayor de la identidad del usuario que se puede lograr a través de roles. |
Inicios de sesión de usuario | Información sobre el proveedor de autenticación externo (como Facebook) que se usará al iniciar sesión en un usuario. |
Roles | Grupos de autorización para el sitio. Incluye el identificador de rol y el nombre del rol (como "Administración" o "Empleado"). |
Creación de la capa de acceso a datos
En este tema se supone que está familiarizado con el mecanismo de persistencia que va a usar y cómo crear entidades para ese mecanismo. En este tema no se proporcionan detalles sobre cómo crear los repositorios o clases de acceso a datos; en su lugar, se proporcionan algunas sugerencias sobre las decisiones de diseño que debe tomar al trabajar con ASP.NET Identity.
Tiene mucha libertad al diseñar los repositorios para un proveedor de almacén personalizado. Solo tiene que crear repositorios para características que quiera usar en la aplicación. Por ejemplo, si no usa roles en la aplicación, no es necesario crear almacenamiento para roles ni roles de usuario. La tecnología y la infraestructura existente pueden requerir una estructura muy diferente de la implementación predeterminada de ASP.NET Identity. En la capa de acceso a datos, se proporciona la lógica para trabajar con la estructura de los repositorios.
Para obtener una implementación de MySQL de repositorios de datos para ASP.NET Identity 2.0, consulte MySQLIdentity.sql.
En la capa de acceso a datos, proporcione la lógica para guardar los datos de ASP.NET Identity en el origen de datos. La capa de acceso a datos del proveedor de almacenamiento personalizado puede incluir las siguientes clases para almacenar información de usuario y rol.
Clase | Descripción | Ejemplo |
---|---|---|
Context | Encapsula la información para conectarse al mecanismo de persistencia y ejecutar consultas. Esta clase es fundamental para la capa de acceso a datos. Las demás clases de datos requerirán una instancia de esta clase para realizar sus operaciones. También inicializará las clases de almacén con una instancia de esta clase. | MySQLDatabase |
Almacenamiento de usuario | Almacena y recupera información de usuario (como el nombre de usuario y el hash de contraseña). | UserTable (MySQL) |
Almacenamiento de estado | Almacena y recupera información de rol (por ejemplo, el nombre del rol). | RoleTable (MySQL) |
Almacenamiento de UserClaims | Almacena y recupera información de notificación de usuario (como el tipo de notificación y el valor). | UserClaimsTable (MySQL) |
Almacenamiento de UserLogins | Almacena y recupera información de inicio de sesión de usuario (como un proveedor de autenticación externo). | UserLoginsTable (MySQL) |
Almacenamiento de UserRole | Almacena y recupera los roles a los que se asigna un usuario. | UserRoleTable (MySQL) |
De nuevo, solo tiene que implementar las clases que quiere usar en la aplicación.
En las clases de acceso a datos, se proporciona código para realizar operaciones de datos para el mecanismo de persistencia concreto. Por ejemplo, dentro de la implementación de MySQL, la clase UserTable contiene un método para insertar un nuevo registro en la tabla de base de datos Users. La variable denominada _database
es una instancia de la clase 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);
}
Después de crear las clases de acceso a datos, debe crear clases de almacén que llamen a los métodos específicos en la capa de acceso a datos.
Personalización de la clase de usuario
Al implementar su propio proveedor de almacenamiento, debe crear una clase de usuario equivalente a la clase IdentityUser en el espacio de nombres Microsoft.ASP.NET.Identity.EntityFramework:
En el siguiente diagrama se muestra la clase IdentityUser que debe crear y la interfaz que se va a implementar en esta clase.
La interfaz IUser<TKey> define las propiedades a las que el UserManager intenta llamar al realizar operaciones solicitadas. La interfaz contiene dos propiedades: Id y UserName. La interfaz IUser<TKey> permite especificar el tipo de clave para el usuario a través del parámetro genérico TKey. El tipo de la propiedad Id coincide con el valor del parámetro TKey.
El marco identity también proporciona la interfaz IUser (sin el parámetro genérico) cuando se quiere usar un valor de cadena para la clave.
La clase IdentityUser implementa IUser y contiene propiedades o constructores adicionales para los usuarios del sitio web. En el siguiente ejemplo se muestra una clase IdentityUser que usa un entero para la clave. El campo Id se establece en int para que coincida con el valor del parámetro genérico.
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
}
Para obtener una implementación completa, consulte IdentityUser (MySQL).
Personalización del almacén de usuarios
También se crea una clase UserStore que proporciona los métodos para todas las operaciones de datos en el usuario. Esta clase es equivalente a la clase UserStore<TUser> del espacio de nombres Microsoft.ASP.NET.Identity.EntityFramework. En la clase UserStore, se implementan las IUserStore<TUser, TKey> y cualquiera de las interfaces opcionales. Seleccione las interfaces opcionales que se van a implementar en función de la funcionalidad que desea proporcionar en la aplicación.
En la siguiente imagen se muestra la clase UserStore que debe crear y las interfaces pertinentes.
La plantilla de proyecto predeterminada de Visual Studio contiene código que supone que muchas de las interfaces opcionales se han implementado en el almacén de usuarios. Si usa la plantilla predeterminada con un almacén de usuarios personalizado, debe implementar interfaces opcionales en el almacén de usuarios o modificar el código de plantilla para que ya no llame a métodos en las interfaces que no haya implementado.
En el siguiente ejemplo se muestra una clase simple de almacén de usuarios. El parámetro genérico TUser toma el tipo de la clase de usuario que normalmente es la clase IdentityUser que definió. El parámetro genérico TKey toma el tipo de la clave de usuario.
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() { ... }
}
En este ejemplo, el constructor que toma un parámetro denominado base de datos de tipo ExampleDatabase es solo una ilustración de cómo pasar la clase de acceso a datos. Por ejemplo, en la implementación de MySQL, este constructor toma un parámetro de tipo MySQLDatabase.
Dentro de la clase UserStore, se usan las clases de acceso a datos que creó para realizar operaciones. Por ejemplo, en la implementación de MySQL, la clase UserStore tiene el método CreateAsync que usa una instancia de UserTable para insertar un nuevo registro. El método Insert en el objeto userTable es el mismo método que se mostró en la sección anterior.
public Task CreateAsync(IdentityUser user)
{
if (user == null) {
throw new ArgumentNullException("user");
}
userTable.Insert(user);
return Task.FromResult<object>(null);
}
Interfaces para implementar al personalizar el almacén de usuarios
En la siguiente imagen se muestran más detalles sobre la funcionalidad definida en cada interfaz. Todas las interfaces opcionales heredan de IUserStore.
IUserStore
La interfaz IUserStore<TUser, TKey> es la única interfaz que debe implementar en el almacén de usuarios. Define métodos para crear, actualizar, eliminar y recuperar usuarios.IUserClaimStore
La interfaz IUserClaimStore<TUser, TKey> define los métodos que debe implementar en el almacén de usuarios para habilitar las notificaciones de usuario. Contiene métodos o agrega, quita y recupera notificaciones de usuario.IUserLoginStore
El IUserLoginStore<TUser, TKey> define los métodos que debe implementar en el almacén de usuarios para habilitar proveedores de autenticación externos. Contiene métodos para agregar, quitar y recuperar inicios de sesión de usuario y un método para recuperar un usuario en función de la información de inicio de sesión.IUserRoleStore
La interfaz IUserRoleStore<TKey, TUser> define los métodos que debe implementar en el almacén de usuarios para asignar un usuario a un rol. Contiene métodos para agregar, quitar y recuperar los roles de un usuario y un método para comprobar si un usuario está asignado a un rol.IUserPasswordStore
La interfaz IUserPasswordStore<TUser, TKey> define los métodos que debe implementar en el almacén de usuarios para conservar las contraseñas con hash. Contiene métodos para obtener y establecer la contraseña con hash y un método que indica si el usuario ha establecido una contraseña.IUserSecurityStampStore
La interfaz IUserSecurityStampStore<TUser, TKey> define los métodos que debe implementar en el almacén de usuarios para usar una marca de seguridad para indicar si ha cambiado la información de la cuenta del usuario. Esta marca se actualiza cuando un usuario cambia la contraseña o agrega o quita los inicios de sesión. Contiene métodos para obtener y establecer la marca de seguridad.IUserTwoFactorStore
La interfaz IUserTwoFactorStore<TUser, TKey> define los métodos que debe implementar para implementar la autenticación en dos fases. Contiene métodos para obtener y establecer si la autenticación en dos fases está habilitada para un usuario.IUserPhoneNumberStore
La interfaz IUserPhoneNumberStore<TUser, TKey> define los métodos que debe implementar para almacenar números de teléfono de usuario. Contiene métodos para obtener y establecer el número de teléfono y si se confirma el número de teléfono.IUserEmailStore
La interfaz IUserEmailStore<TUser, TKey> define los métodos que debe implementar para almacenar direcciones de correo electrónico de usuario. Contiene métodos para obtener y establecer la dirección de correo electrónico y si se confirma el correo electrónico.IUserLockoutStore
La interfaz IUserLockoutStore<TUser, TKey> define los métodos que debe implementar para almacenar información sobre el bloqueo de una cuenta. Contiene métodos para obtener el número actual de intentos de acceso erróneos, obtener y establecer si la cuenta se puede bloquear, obtener y establecer la fecha de finalización del bloqueo, incrementar el número de intentos erróneos y restablecer el número de intentos erróneos.IQueryableUserStore
La interfaz IQueryableUserStore<TUser, TKey> define los miembros que debe implementar para proporcionar un almacén de usuarios consultable. Contiene una propiedad que contiene los usuarios consultables.Implemente las interfaces necesarias en la aplicación; por ejemplo, las interfaces IUserClaimStore, IUserLoginStore, IUserRoleStore, IUserPasswordStore e IUserSecurityStampStore, como se muestra a continuación.
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
}
Para obtener una implementación completa (incluidas todas las interfaces), consulte UserStore (MySQL).
IdentityUserClaim, IdentityUserLogin e IdentityUserRole
El espacio de nombres Microsoft.AspNet.Identity.EntityFramework contiene implementaciones de las clases de IdentityUserClaim, IdentityUserLogin y IdentityUserRole. Si usa estas características, puede crear sus propias versiones de estas clases y definir las propiedades de la aplicación. Sin embargo, a veces es más eficaz no cargar estas entidades en la memoria al realizar operaciones básicas (como agregar o quitar la notificación de un usuario). En su lugar, las clases de almacén de back-end pueden ejecutar estas operaciones directamente en el origen de datos. Por ejemplo, el método UserStore.GetClaimsAsync() puede llamar al método userClaimTable.FindByUserId(user.Id) para ejecutar una consulta en esa tabla directamente y devolver una lista de notificaciones.
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}
Personalización de la clase de rol
Al implementar su propio proveedor de almacenamiento, debe crear una clase de rol equivalente a la clase IdentityRole en el espacio de nombres Microsoft.ASP.NET.Identity.EntityFramework:
En el siguiente diagrama se muestra la clase IdentityRole que debe crear y la interfaz para implementar en esta clase.
La interfaz IRole<TKey> define las propiedades a las que RoleManager intenta llamar al realizar operaciones solicitadas. La interfaz contiene dos propiedades: id. y nombre. La interfaz IRole<TKey> permite especificar el tipo de clave para el rol a través del parámetro genérico TKey. El tipo de la propiedad Id coincide con el valor del parámetro TKey.
El marco identity también proporciona la interfaz IRole (sin el parámetro genérico) cuando se quiere usar un valor de cadena para la clave.
En el ejemplo siguiente se muestra una clase IdentityRole que usa un entero para la clave. El campo Id se establece en int para que coincida con el valor del parámetro genérico.
public class IdentityRole : IRole<int>
{
public IdentityRole() { ... }
public IdentityRole(string roleName) { ... }
public int Id { get; set; }
public string Name { get; set; }
}
Para obtener una implementación completa, consulte IdentityRole (MySQL).
Personalización del almacén de roles
También se crea una clase RoleStore que proporciona los métodos para todas las operaciones de datos en roles. Esta clase es equivalente a la clase RoleStore<TRole> en el espacio de nombres Microsoft.ASP.NET.Identity.EntityFramework. En la clase RoleStore, implementará la IRoleStore<TRole, TKey> y, si lo desea, la interfaz IQueryableRoleStore<TRole, TKey>.
En el ejemplo siguiente se muestra una clase de almacén de roles. El parámetro genérico TRole toma el tipo de la clase de rol que normalmente es la clase IdentityRole que definió. El parámetro genérico TKey toma el tipo de la clave de rol.
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>
La interfaz IRoleStore define los métodos que se van a implementar en la clase de almacén de roles. Contiene métodos para crear, actualizar, eliminar y recuperar roles.RoleStore<TRole>
Para personalizar RoleStore, cree una clase que implemente la interfaz IRoleStore. Solo tiene que implementar esta clase si desea usar roles en el sistema. El constructor que toma un parámetro denominado base de datos de tipo ExampleDatabase es solo una ilustración de cómo pasar la clase de acceso a datos. Por ejemplo, en la implementación de MySQL, este constructor toma un parámetro de tipo MySQLDatabase.Para obtener una implementación completa, consulte RoleStore (MySQL) .
Volver a configurar la aplicación para usar el nuevo proveedor de almacenamiento
Ha implementado el nuevo proveedor de almacenamiento. Ahora, debe configurar la aplicación para que use este proveedor de almacenamiento. Si el proveedor de almacenamiento predeterminado se incluyó en el proyecto, debe quitar el proveedor predeterminado y reemplazarlo por el proveedor.
Reemplazar el proveedor de almacenamiento predeterminado en el proyecto de MVC
En la ventana Administrar paquetes NuGet, desinstale el paquete EntityFramework de Microsoft ASP.NET Identity. Para encontrar este paquete, busque los paquetes instalados para Identity.EntityFramework.
Se le preguntará si también desea desinstalar Entity Framework. Si no lo necesita en otras partes de la aplicación, puede desinstalarla.En el archivo IdentityModels.cs de la carpeta Models, elimine o comente las clases ApplicationUser y ApplicationDbContext. En una aplicación MVC, puede eliminar todo el archivo IdentityModels.cs. En una aplicación de Web Forms, elimine las dos clases, pero asegúrese de mantener la clase auxiliar que también se encuentra en el archivo IdentityModels.cs.
Si el proveedor de almacenamiento reside en un proyecto independiente, agregue una referencia a él en la aplicación web.
Reemplace todas las referencias a
using Microsoft.AspNet.Identity.EntityFramework;
por una instrucción using para el espacio de nombres del proveedor de almacenamiento.En la clase Startup.Auth.cs, cambie el método ConfigureAuth para usar una sola instancia del contexto adecuado.
public void ConfigureAuth(IAppBuilder app) { app.CreatePerOwinContext(ExampleStorageContext.Create); app.CreatePerOwinContext(ApplicationUserManager.Create); ...
En la carpeta App_Start, abra IdentityConfig.cs. En la clase ApplicationUserManager, cambie el método Create para devolver un administrador de usuarios que use el almacén de usuarios personalizado.
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { var manager = new ApplicationUserManager(new UserStore(context.Get<ExampleStorageContext>())); ... }
Reemplace todas las referencias a ApplicationUser por IdentityUser.
El proyecto predeterminado incluye algunos miembros de la clase de usuario que no están definidos en la interfaz IUser; como Email, PasswordHash y GenerateUserIdentityAsync. Si la clase de usuario no tiene estos miembros, debe implementarlos o cambiar el código que usa estos miembros.
Si ha creado instancias de RoleManager, cambie ese código para usar la nueva clase RoleStore.
var roleManager = new RoleManager<IdentityRole>(new RoleStore(context.Get<ExampleStorageContext>()));
El proyecto predeterminado está diseñado para una clase de usuario que tiene un valor de cadena para la clave. Si la clase de usuario tiene un tipo diferente para la clave (por ejemplo, un entero), debe cambiar el proyecto para que funcione con el tipo. Consulte Cambiar la clave principal de los usuarios en ASP.NET Identity.
Si es necesario, agregue la cadena de conexión al archivo Web.config.
Otros recursos
- Blog: Implementación de ASP.NET Identity
- Tutorial y código GIT: Proveedor de identidades Simple.Data Asp.Net
- Tutorial:Configuración de las cuentas de identidad básicas y apuntarlas a una base de datos externa. Por @xivSolutions.
- Tutorial: Implementación de un proveedor de almacenamiento de identidades de MySQL personalizado ASP.NET
- Entidades CodeFluent por SoftFluent
- Azure Table Storage por James Randall.
- Azure Table Storage: AspNet.Identity.TableStorage por @stuartleeks.
- CouchDB / Cloudant, por Daniel Wertheim.
- Elastic Search: Elastic Identity por Bombsquad AB.
- MongoDB por Jonathan Sheely Jonathan Sheely.
- NHibernate.AspNet.Identity por Antônio Milesi Bastos.
- RavenDB por @tourismgeek.
- RavenDB.AspNet.Identity por ILMServices.
- Redis: redis.AspNet.Identity
- Plantillas de T4 para generar código EF para un almacén de usuarios "primero en la base de datos": AspNet.Identity.EntityFramework