Visão geral de provedores de armazenamento personalizados para a Identidade do ASP.NET
por Tom FitzMacken
ASP.NET Identity é um sistema extensível que permite criar seu próprio provedor de armazenamento e conectá-lo ao seu aplicativo sem trabalhar novamente no aplicativo. Este tópico descreve como criar um provedor de armazenamento personalizado para ASP.NET Identity. Ele aborda os conceitos importantes para criar seu próprio provedor de armazenamento, mas não é passo a passo da implementação de um provedor de armazenamento personalizado.
Para obter um exemplo de implementação de um provedor de armazenamento personalizado, consulte Implementando um provedor de armazenamento de identidade ASP.NET MySQL personalizado.
Este tópico foi atualizado para ASP.NET Identity 2.0.
Versões de software usadas no tutorial
- Visual Studio 2013 com a Atualização 2
- ASP.NET Identity 2
Introdução
Por padrão, o sistema ASP.NET Identity armazena informações do usuário em um banco de dados SQL Server e usa o Entity Framework Code First para criar o banco de dados. Para muitos aplicativos, essa abordagem funciona bem. No entanto, talvez você prefira usar um tipo diferente de mecanismo de persistência, como o Armazenamento de Tabelas do Azure, ou já tenha tabelas de banco de dados com uma estrutura muito diferente da implementação padrão. Em ambos os casos, você pode escrever um provedor personalizado para o mecanismo de armazenamento e conectar esse provedor ao seu aplicativo.
ASP.NET Identity é incluída por padrão em muitos dos modelos de Visual Studio 2013. Você pode obter atualizações para ASP.NET Identity por meio do pacote NuGet do Microsoft AspNet Identity EntityFramework.
Este tópico inclui as seções a seguir:
- Entender a arquitetura
- Entender os dados armazenados
- Criar a camada de acesso a dados
- Personalizar a classe de usuário
- Personalizar o repositório de usuários
- Personalizar a classe de função
- Personalizar o repositório de funções
- Reconfigurar o aplicativo para usar o novo provedor de armazenamento
- Outras implementações de provedores de armazenamento personalizados
Entender a arquitetura
ASP.NET Identity consiste em classes chamadas gerentes e repositórios. Os gerentes são classes de alto nível que um desenvolvedor de aplicativos usa para executar operações, como a criação de um usuário, no sistema ASP.NET Identity. Os repositórios são classes de nível inferior que especificam como entidades, como usuários e funções, são persistentes. Os repositórios são intimamente acoplados ao mecanismo de persistência, mas os gerentes são dissociados dos repositórios, o que significa que você pode substituir o mecanismo de persistência sem interromper todo o aplicativo.
O diagrama a seguir mostra como seu aplicativo Web interage com os gerentes e os armazenamentos interagem com a camada de acesso a dados.
Para criar um provedor de armazenamento personalizado para ASP.NET Identity, você precisa criar a fonte de dados, a camada de acesso a dados e as classes de armazenamento que interagem com essa camada de acesso a dados. Você pode continuar usando as mesmas APIs do gerenciador para executar operações de dados no usuário, mas agora esses dados são salvos em um sistema de armazenamento diferente.
Você não precisa personalizar as classes de gerente porque ao criar uma nova instância de UserManager ou RoleManager, você fornece o tipo da classe de usuário e passa uma instância da classe de repositório como um argumento. Essa abordagem permite que você conecte suas classes personalizadas à estrutura existente. Você verá como instanciar UserManager e RoleManager com suas classes de repositório personalizadas na seção Reconfigurar aplicativo para usar o novo provedor de armazenamento.
Entender os dados armazenados
Para implementar um provedor de armazenamento personalizado, você deve entender os tipos de dados usados com ASP.NET Identity e decidir quais recursos são relevantes para seu aplicativo.
Dados | Descrição |
---|---|
Usuários | Usuários registrados do seu site. Inclui a ID de usuário e o nome de usuário. Pode incluir uma senha hash se os usuários fizerem logon com credenciais específicas do seu site (em vez de usar credenciais de um site externo como o Facebook) e o carimbo de segurança para indicar se alguma coisa mudou nas credenciais do usuário. Também pode incluir endereço de email, número de telefone, se a autenticação de dois fatores está habilitada, o número atual de logons com falha e se uma conta foi bloqueada. |
Declarações de usuário | Um conjunto de instruções (ou declarações) sobre o usuário que representa a identidade do usuário. Pode habilitar uma expressão maior da identidade do usuário do que pode ser obtida por meio de funções. |
Logons de usuário | Informações sobre o provedor de autenticação externa (como o Facebook) a ser usado ao fazer logon em um usuário. |
Funções | Grupos de autorização para seu site. Inclui a ID da função e o nome da função (como "Administração" ou "Funcionário"). |
Criar a camada de acesso a dados
Este tópico pressupõe que você esteja familiarizado com o mecanismo de persistência que você usará e como criar entidades para esse mecanismo. Este tópico não fornece detalhes sobre como criar os repositórios ou classes de acesso a dados; Em vez disso, ele fornece algumas sugestões sobre as decisões de design que você precisa tomar ao trabalhar com ASP.NET Identity.
Você tem muita liberdade ao projetar os repositórios para um provedor de repositório personalizado. Você só precisa criar repositórios para recursos que pretende usar em seu aplicativo. Por exemplo, se você não estiver usando funções em seu aplicativo, não precisará criar armazenamento para funções ou funções de usuário. Sua tecnologia e infraestrutura existente podem exigir uma estrutura muito diferente da implementação padrão de ASP.NET Identity. Em sua camada de acesso a dados, você fornece a lógica para trabalhar com a estrutura de seus repositórios.
Para obter uma implementação do MySQL de repositórios de dados para ASP.NET Identity 2.0, consulte MySQLIdentity.sql.
Na camada de acesso a dados, você fornece a lógica para salvar os dados de ASP.NET Identity para sua fonte de dados. A camada de acesso a dados para seu provedor de armazenamento personalizado pode incluir as seguintes classes para armazenar informações de usuário e função.
Classe | Descrição | Exemplo |
---|---|---|
Contexto | Encapsula as informações para se conectar ao mecanismo de persistência e executar consultas. Essa classe é central para sua camada de acesso a dados. As outras classes de dados exigirão uma instância dessa classe para executar suas operações. Você também inicializará suas classes de repositório com uma instância dessa classe. | MySQLDatabase |
Armazenamento do Usuário | Armazena e recupera informações do usuário (como nome de usuário e hash de senha). | UserTable (MySQL) |
Armazenamento de Funções | Armazena e recupera informações de função (como o nome da função). | RoleTable (MySQL) |
Armazenamento UserClaims | Armazena e recupera informações de declaração do usuário (como o tipo de declaração e o valor). | UserClaimsTable (MySQL) |
Armazenamento de UserLogins | Armazena e recupera informações de logon do usuário (como um provedor de autenticação externo). | UserLoginsTable (MySQL) |
Armazenamento userrole | Armazena e recupera a quais funções um usuário é atribuído. | UserRoleTable (MySQL) |
Novamente, você só precisa implementar as classes que pretende usar em seu aplicativo.
Nas classes de acesso a dados, você fornece código para executar operações de dados para seu mecanismo de persistência específico. Por exemplo, na implementação do MySQL, a classe UserTable contém um método para inserir um novo registro na tabela de banco de dados Usuários. A variável chamada _database
é uma instância da classe 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);
}
Depois de criar suas classes de acesso a dados, você deve criar classes de armazenamento que chamam os métodos específicos na camada de acesso a dados.
Personalizar a classe de usuário
Ao implementar seu próprio provedor de armazenamento, você deve criar uma classe de usuário equivalente à classe IdentityUser no namespace Microsoft.ASP.NET.Identity.EntityFramework :
O diagrama a seguir mostra a classe IdentityUser que você deve criar e a interface a ser implementada nessa classe.
A interface TKey> do IUser< define as propriedades que o UserManager tenta chamar ao executar operações solicitadas. A interface contém duas propriedades : Id e UserName. A interface TKey> do IUser< permite que você especifique o tipo da chave para o usuário por meio do parâmetro TKey genérico. O tipo da propriedade Id corresponde ao valor do parâmetro TKey.
A estrutura identity também fornece a interface IUser (sem o parâmetro genérico) quando você deseja usar um valor de cadeia de caracteres para a chave.
A classe IdentityUser implementa o IUser e contém quaisquer propriedades ou construtores adicionais para usuários em seu site. O exemplo a seguir mostra uma classe IdentityUser que usa um inteiro para a chave. O campo Id é definido como int para corresponder ao valor do 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 obter uma implementação completa, consulte IdentityUser (MySQL).
Personalizar o repositório de usuários
Você também cria uma classe UserStore que fornece os métodos para todas as operações de dados no usuário. Essa classe é equivalente à classe UserStore<TUser> no namespace Microsoft.ASP.NET.Identity.EntityFramework . Na classe UserStore, você implementa o TUser IUserStore<, o TKey> e qualquer uma das interfaces opcionais. Você seleciona quais interfaces opcionais implementar com base na funcionalidade que deseja fornecer em seu aplicativo.
A imagem a seguir mostra a classe UserStore que você deve criar e as interfaces relevantes.
O modelo de projeto padrão no Visual Studio contém código que pressupõe que muitas das interfaces opcionais foram implementadas no repositório de usuários. Se você estiver usando o modelo padrão com um repositório de usuários personalizado, deverá implementar interfaces opcionais no repositório de usuários ou alterar o código de modelo para não chamar mais métodos nas interfaces que você não implementou.
O exemplo a seguir mostra uma classe de repositório de usuários simples. O parâmetro genérico TUser usa o tipo da classe de usuário que geralmente é a classe IdentityUser que você definiu. O parâmetro genérico TKey usa o tipo de sua chave de usuário.
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() { ... }
}
Neste exemplo, o construtor que usa um parâmetro chamado banco de dados do tipo ExampleDatabase é apenas uma ilustração de como passar sua classe de acesso a dados. Por exemplo, na implementação do MySQL, esse construtor usa um parâmetro do tipo MySQLDatabase.
Em sua classe UserStore, você usa as classes de acesso a dados que criou para executar operações. Por exemplo, na implementação do MySQL, a classe UserStore tem o método CreateAsync que usa uma instância de UserTable para inserir um novo registro. O método Insert no objeto userTable é o mesmo método mostrado na seção anterior.
public Task CreateAsync(IdentityUser user)
{
if (user == null) {
throw new ArgumentNullException("user");
}
userTable.Insert(user);
return Task.FromResult<object>(null);
}
Interfaces a serem implementadas ao personalizar o repositório de usuários
A próxima imagem mostra mais detalhes sobre a funcionalidade definida em cada interface. Todas as interfaces opcionais herdam do IUserStore.
IUserStore
A interface TUser TUser, TKey> do IUserStore< é a única interface que você deve implementar em seu repositório de usuários. Ele define métodos para criar, atualizar, excluir e recuperar usuários.IUserClaimStore
A interface TUserClaimStore<TUser, TKey> define os métodos que você deve implementar em seu repositório de usuários para habilitar as declarações do usuário. Ele contém métodos ou adição, remoção e recuperação de declarações de usuário.IUserLoginStore
O TUserLoginStore<TUser define> os métodos que você deve implementar em seu repositório de usuários para habilitar provedores de autenticação externos. Ele contém métodos para adicionar, remover e recuperar logons de usuário e um método para recuperar um usuário com base nas informações de logon.IUserRoleStore
A interface TKey, TUser> do IUserRoleStore<define os métodos que você deve implementar em seu repositório de usuários para mapear um usuário para uma função. Ele contém métodos para adicionar, remover e recuperar as funções de um usuário e um método para marcar se um usuário for atribuído a uma função.IUserPasswordStore
A interface TUserPasswordStore<TUser, TKey> define os métodos que você deve implementar em seu repositório de usuários para persistir senhas com hash. Ele contém métodos para obter e definir a senha com hash e um método que indica se o usuário definiu uma senha.IUserSecurityStampStore
A interface TUserSecurityStampStore<TUser, TKey> define os métodos que você deve implementar em seu repositório de usuários para usar um carimbo de segurança para indicar se as informações da conta do usuário foram alteradas. Esse carimbo é atualizado quando um usuário altera a senha ou adiciona ou remove logons. Ele contém métodos para obter e definir o carimbo de segurança.IUserTwoFactorStore
A interface TUserTwoFactorStore<TUser, TKey> define os métodos que você deve implementar para implementar a autenticação de dois fatores. Ele contém métodos para obter e definir se a autenticação de dois fatores está habilitada para um usuário.IUserPhoneNumberStore
A interface TUserPhoneNumberStore<TUser define> os métodos que você deve implementar para armazenar números de telefone do usuário. Ele contém métodos para obter e definir o número de telefone e se o número de telefone foi confirmado.IUserEmailStore
A interface TUserEmailStore<TUser, TKey> define os métodos que você deve implementar para armazenar endereços de email do usuário. Ele contém métodos para obter e definir o endereço de email e se o email foi confirmado.IUserLockoutStore
A interface TUserLockoutStore<TUser, TKey> define os métodos que você deve implementar para armazenar informações sobre o bloqueio de uma conta. Ele contém métodos para obter o número atual de tentativas de acesso com falha, obter e definir se a conta pode ser bloqueada, obter e definir a data de término do bloqueio, incrementar o número de tentativas com falha e redefinir o número de tentativas com falha.IQueryableUserStore
A interface TUser TUser, TKey> do IQueryableUserStore< define os membros que você deve implementar para fornecer um repositório de usuários consultável. Ele contém uma propriedade que contém os usuários consultáveis.Você implementa as interfaces necessárias em seu aplicativo; por exemplo, as interfaces IUserClaimStore, IUserLoginStore, IUserRoleStore, IUserPasswordStore e IUserSecurityStampStore, conforme mostrado abaixo.
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 obter uma implementação completa (incluindo todas as interfaces), consulte UserStore (MySQL).
IdentityUserClaim, IdentityUserLogin e IdentityUserRole
O namespace Microsoft.AspNet.Identity.EntityFramework contém implementações das classes IdentityUserClaim, IdentityUserLogin e IdentityUserRole . Se você estiver usando esses recursos, talvez queira criar suas próprias versões dessas classes e definir as propriedades para seu aplicativo. No entanto, às vezes, é mais eficiente não carregar essas entidades na memória ao executar operações básicas (como adicionar ou remover a declaração de um usuário). Em vez disso, as classes de repositório de back-end podem executar essas operações diretamente na fonte de dados. Por exemplo, o método UserStore.GetClaimsAsync() pode chamar userClaimTable.FindByUserId(user. Id) método para executar uma consulta diretamente nessa tabela e retornar uma lista de declarações.
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}
Personalizar a classe de função
Ao implementar seu próprio provedor de armazenamento, você deve criar uma classe de função equivalente à classe IdentityRole no namespace Microsoft.ASP.NET.Identity.EntityFramework :
O diagrama a seguir mostra a classe IdentityRole que você deve criar e a interface a ser implementada nessa classe.
A interface TKey> do IRole< define as propriedades que o RoleManager tenta chamar ao executar operações solicitadas. A interface contém duas propriedades : Id e Nome. A interface TKey> do IRole< permite que você especifique o tipo da chave para a função por meio do parâmetro TKey genérico. O tipo da propriedade Id corresponde ao valor do parâmetro TKey.
A estrutura identity também fornece a interface IRole (sem o parâmetro genérico) quando você deseja usar um valor de cadeia de caracteres para a chave.
O exemplo a seguir mostra uma classe IdentityRole que usa um inteiro para a chave. O campo Id é definido como int para corresponder ao valor do 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 obter uma implementação completa, consulte IdentityRole (MySQL).
Personalizar o repositório de funções
Você também cria uma classe RoleStore que fornece os métodos para todas as operações de dados em funções. Essa classe é equivalente à classe TRole> RoleStore< no namespace Microsoft.ASP.NET.Identity.EntityFramework. Em sua classe RoleStore, você implementa a interface TRole<, TKey> e, opcionalmente , IQueryableRoleStore<TRole, TKey> .
O exemplo a seguir mostra uma classe de repositório de funções. O parâmetro genérico TRole usa o tipo da classe de função que geralmente é a classe IdentityRole que você definiu. O parâmetro genérico TKey usa o tipo de sua chave de função.
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>
A interface IRoleStore define os métodos a serem implementados em sua classe de repositório de funções. Ele contém métodos para criar, atualizar, excluir e recuperar funções.RoleStore<TRole>
Para personalizar o RoleStore, crie uma classe que implemente a interface IRoleStore. Você só precisa implementar essa classe se quiser usar funções em seu sistema. O construtor que usa um parâmetro chamado banco de dados do tipo ExampleDatabase é apenas uma ilustração de como passar sua classe de acesso a dados. Por exemplo, na implementação do MySQL, esse construtor usa um parâmetro do tipo MySQLDatabase.Para obter uma implementação completa, consulte RoleStore (MySQL).
Reconfigurar o aplicativo para usar o novo provedor de armazenamento
Você implementou seu novo provedor de armazenamento. Agora, você deve configurar seu aplicativo para usar esse provedor de armazenamento. Se o provedor de armazenamento padrão tiver sido incluído em seu projeto, você deverá remover o provedor padrão e substituí-lo pelo provedor.
Substituir o provedor de armazenamento padrão no projeto MVC
Na janela Gerenciar Pacotes NuGet , desinstale o pacote Microsoft ASP.NET Identity EntityFramework . Você pode encontrar esse pacote pesquisando nos pacotes instalados para Identity.EntityFramework.
Você será perguntado se também deseja desinstalar o Entity Framework. Se você não precisar dele em outras partes do aplicativo, poderá desinstalá-lo.No arquivo IdentityModels.cs na pasta Models, exclua ou comente as classes ApplicationUser e ApplicationDbContext . Em um aplicativo MVC, você pode excluir todo o arquivo IdentityModels.cs. Em um aplicativo Web Forms, exclua as duas classes, mas certifique-se de manter a classe auxiliar que também está localizada no arquivo IdentityModels.cs.
Se o provedor de armazenamento residir em um projeto separado, adicione uma referência a ele em seu aplicativo Web.
Substitua todas as referências a
using Microsoft.AspNet.Identity.EntityFramework;
por uma instrução using para o namespace do provedor de armazenamento.Na classe Startup.Auth.cs , altere o método ConfigureAuth para usar uma única instância do contexto apropriado.
public void ConfigureAuth(IAppBuilder app) { app.CreatePerOwinContext(ExampleStorageContext.Create); app.CreatePerOwinContext(ApplicationUserManager.Create); ...
Na pasta App_Start, abra IdentityConfig.cs. Na classe ApplicationUserManager, altere o método Create para retornar um gerenciador de usuários que usa seu repositório de usuários personalizado.
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { var manager = new ApplicationUserManager(new UserStore(context.Get<ExampleStorageContext>())); ... }
Substitua todas as referências a ApplicationUser por IdentityUser.
O projeto padrão inclui alguns membros na classe de usuário que não são definidos na interface IUser; como Email, PasswordHash e GenerateUserIdentityAsync. Se a classe de usuário não tiver esses membros, você deverá implementá-los ou alterar o código que usa esses membros.
Se você tiver criado qualquer instância do RoleManager, altere esse código para usar sua nova classe RoleStore.
var roleManager = new RoleManager<IdentityRole>(new RoleStore(context.Get<ExampleStorageContext>()));
O projeto padrão foi projetado para uma classe de usuário que tem um valor de cadeia de caracteres para a chave. Se sua classe de usuário tiver um tipo diferente para a chave (como um inteiro), você deverá alterar o projeto para trabalhar com seu tipo. Consulte Alterar chave primária para usuários no ASP.NET Identity.
Se necessário, adicione a cadeia de conexão ao arquivo Web.config.
Outros recursos
- Blog: Implementando ASP.NET Identity
- Tutorial e código GIT: Simple.Data Asp.Net Identity Provider
- Tutorial:Configurando as contas básicas de identidade e apontando-as para um banco de dados externo. Por @xivSolutions.
- Tutorial: Implementando um provedor de armazenamento de identidade do MySQL personalizado ASP.NET
- Entidades CodeFluent por SoftFluent
- Armazenamento de Tabelas do Azure por James Randall.
- Armazenamento de Tabelas do Azure: AspNet.Identity.TableStorage por @stuartleeks.
- CouchDB / Cloudant por Daniel Wertheim.
- Elastic Search: Elastic Identity by Bombsquad AB.
- MongoDB por Jonathan Sheely Jonathan Sheely.
- NHibernate.AspNet.Identity de Antônio Milesi Bastos.
- RavenDB por @tourismgeek.
- RavenDB.AspNet.Identity por ILMServices.
- Redis: Redis.AspNet.Identity
- Modelos T4 para gerar código EF para um repositório de usuários "database first": AspNet.Identity.EntityFramework