Desenvolvimento - ASP. NET
Entendendo e Implementando Segurança no ASP.NET 2.0
As novas API's de segurança do ASP.NET 2.0 estão realmente mais completas e com muitas das coisas que fazíamos anteriormente com ASP.NET 1.x já encapsuladas. A finalidade deste artigo é dar um overview completo destas novas API's, começando em como está desenhada a arquitetura da mesma e passando pelo Membership e Roles.
por Israel AéceO ASP.NET 2.0 inclui uma porção de novos serviços de persistência de dados em um banco de dados. Essa nova arquitetura é chamada de Provider Model e nos dá uma enorme flexibilidade, onde podemos trocar a fonte de dados/persistência e a aplicação continua trabalhando normalmente. Este provider é um módulo do software que estamos desenvolvendo que fornece uma interface genérica para uma fonte de dados, onde abstraem a base de dados. Além disso ser flexível, a qualquer momento podemos trocar a base de dados, essa arquitetura também é extensível, ou seja, se mais tarde quisermos customizar algo, podemos fazer isso sem maiores problemas.
A idéia dentro deste padrão é ter uma classe abstrata, onde dentro dela teremos métodos e propriedades que devem ser implementados nas classes concretas e, através de configurações no arquivo Web.Config, definimos a classe concreta que iremos utilizar para a aplicação. A questão é que essa classe concreta é instanciada em runtime, pois o ASP.NET recupera isso do arquivo de configuração e se encarrega de criar a instância correta da classe para uma determinada funcionalidade.
Essa arquitetura já não é lá muito nova. Se analisarmos o ASP.NET Fóruns ou até mesmo o PetShop 3.0, veremos que eles utilizam uma técnica bem parecida, onde fazem o uso dos padrões Abstract Factory e Factory Method (padrões Criacionais) para garantir essa genericidade. Isso faz com que devemos ter uma classe abstrata por trás a qual as classes concretam as implementam e o runtime se encarrega de criar a instância correta da classe concreta baseando-se nas configurações do arquivo Web.Config.
Essa arquitetura é usada extensivamente dentro do .NET Framework 2.0 (ASP.NET), onde temos classes abstratas para cada situação diferente e, para um determinado repositório de dados, uma classe concreta já implementada. Através da tabela abaixo veremos o nome da funcionalidade, a classe base e as classes concretas que se enquandram dentro do escopo do artigo:
|
Reparem que para uma determinada classe abstrata, como por exemplo MembershipProvider, já temos, por padrão, algumas classes que a Microsoft implementou para já utilizarmos a funcionalidade. Um exemplo disso é a classe SqlMembershipProvider, a qual utiliza uma base de dados SQL Server 2000 ou superior para disponibilizar o recurso. Como foi dito acima, não se restringe somente à isso. Temos classes para gerencimento de estado, Web Parts, Site Map, Profile, etc., que não foram citados/explicados por não fazerem parte do escopo deste artigo. Se no futuro precisarmos customizar alguma das funcionalidades da tabela acima para uma base de dados, como por exemplo Oracle, basta criarmos uma classe herdando de MembershipProvider ou RoleProvider e implementar os métodos e propriedades exclusivamente para aquela base de dados. Finalmente, para ilustrar essa arquitetura, é mostrado através da figura abaixo o design das classes, já com as devidas denotações de herança entre elas:
Figura 1 - Design das classes utilizando Provider Model. |
Além das classes que vimos acima, temos ainda duas classes estáticas (compartilhadas) que são, também, parte das principais. São estas classes que são expostas para nós, desenvolvedores, utilizarmos e fazermos a chamada aos métodos e propriedades de forma genérica. Essas classes são: Membership e Roles, as quais estão contidas dentro do Namespace System.Web.Security. No caso da classe Membership, existe um membro interno chamado s_Provider do tipo MembershipProvider (a classe base para as classes concretas de Membership, como por exemplo o SqlMembershipProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize é executado. Ele se encarrega de extrair os providers do arquivo Web.Config e instanciá-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar. O funcionamento é também semelhante para a classe Roles.
Por questões de infraestrutura, utilizaremos no decorrer deste artigo o banco de dados SQL Server 2000. Mas então como prepará-lo para fazer o uso desta funcionalidade? Pois bem, para que o mesmo possa ser utilizado para isso, é necessário criarmos dentro do banco de dados a infraestrutura (Tabelas, Índices, Stored Procedures e Triggers) necessária para a utilização do recurso, que no caso serão Membership e Roles.
Quando instalamos no .NET Framework 2.0, em seu diretório %WinDir%\Microsoft.NET\Framework\v2.0.50727 é instalado um utilitário chamado aspnet_regsql.exe. Este utilitário, dados alguns parâmetros, faz todo o trabalho da criação da infraestrutura dentro de um determinado banco de dados SQL Server. Veremos na tabela abaixo os parâmetros que ele aceita:
|
Abaixo é mostrado (através do prompt de comando do Visual Studio .NET 2005) alguns exemplos do uso do utilitário aspnet_regsql.exe:
|
Na primeira linha adicionamos as funcionalidades de Membership, Roles, Profile e WebEvents no banco de dados chamado "BancoDados". Já na segunda opção adicionamos todas as funcionalidades, só que agora utilizando as credenciais do Windows. E, por último, estamos removendo toda a infraestrutura da base de dados. Quando criamos uma destas funcionalidades dentro da base de dados ele inclui uma porção de Tabelas, Stored Procedures, Triggers para que a mesma seja atendida. Se analisarmos o design da base de dados depois disso, veremos a seguinte estrutura:
Figura 2 - Design da base de dados para suportar as funcionalidades de Membership e Roles. |
O Arquivo ASPNETDB.MDF
Quando utilizamos Membership ou qualquer uma destas funcionalidades, se não especificarmos um provider e você não tiver um banco de dados pré-definido para o uso das mesmas, o ASP.NET cria automaticamente dentro do diretório App_Data dentro da aplicação um banco de dados chamado ASPNETDB.MDF, que faz uso do SQL Server 2005 Express Edition. Essa criação se dá quando iniciamos o WSAT - Web Site Administration Tool, onde depois de iniciarmos o provider, o arquivo MDF é criado dentro da pasta App_Data.
Com isso não seria necessário você criar a infraestrutura em uma base de dados assim como mostramos acima, pois o arquivo ASPNETDB.MDF já terá todo o schema necessário para as funcionalidades. Se o volume de usuários não é tão alto e o sistema pouco complexo, ter o ASPNETDB.MDF já resolve a necessidade. Mas quando o nível de usuários aumenta e de alguma forma você precisa fazer o relacionamento da tabela de Usuários com outras tabelas do sistema, é mais interessante ter isso unificado, ou seja, dentro de uma única base de dados.
Membership e MembershipUser
A classe estática Membership, encontrada dentro do Namespace System.Web.Security, é usada pelo ASP.NET para validar as credenciais do usuário e gerenciar as configurações do mesmo. Esta classe faz uso da classe FormsAuthentication (já existente nas versões anteriores do ASP.NET) para criar e gerenciar a autenticação do usuário dentro da aplicação Web.
Falando um pouco sobre o seu funcionamento: existe um membro interno chamado s_Provider do tipo MembershipProvider (a classe base para as classes concretas de Membership, como por exemplo o SqlMembershipProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize é executado. Ele se encarrega de extrair os providers do arquivo Web.Config e instanciá-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar.
Como esta classe encapsula o acesso ao provider específico, ela fornece facilidades como: criação de novos usuários, validação de usuários, gerenciamento de usuários em seu repositório (SQL Server, ActiveDirectory, etc). Como já vimos anteriormente o funcionamento interno desta classe, podemos perceber que o ASP.NET confia no provider especificado no Web.Config para se comunicar com a fonte de dados. Como já sabemos, o .NET Framework inclui a classe SqlMembershipProvider para acesso e persistência dos dados no SQL Server e é este que vamos utilizar no decorrer dos exemplos deste artigo.
Mas esta arquitetura é extensível, ou seja, a qualquer momento eu posso criar o meu próprio provider de Membership, onde eu queira customizá-lo para um outro repositório qualquer. E, para que isso seja possível, é necessário herdarmos da classe abstrata chamada MembershipProvider e customizá-la de acordo com o nosso repositório. Para ilustar o cenário, imaginemos que desejamos ter um provider de Membership sendo persistido em um arquivo XML:
public class XmlMembershipProvider : MembershipProvider { // customizo aqui todos os métodos e // propriedades necessárias } Public Class XmlMembershipProvider Inherits MembershipProvider " customizo aqui todos os métodos e " propriedades necessárias End Class |
|||
C# | VB.NET |
Depois do provider criado, basta definí-lo nas configurações do Web.Config e começar a utilizá-lo.
A configuração no arquivo Web.Config
Por padrão, todas as aplicações ASP.NET 2.0 já utilizam o Membership. Isso porque dentro do arquivo de configuração machine.config já temos um provider chamado AspNetSqlProvider que utiliza o SqlMembershipProvider. Este provider aponta para um banco de dados chamado ASPNETDB.MDF que encontra-se dentro da pasta App_Data da aplicação e é habilitado quando marcamos a opção AspNetSqlProvider no Web Site Administration Tool. Como o intúito do artigo é mostrar como configurar isso manualmente, vamos especificar o provider em nossa aplicação e configurá-lo para atender as nossas necessidades e, para isso, vamos até o arquivo Web.Config da aplicação para efetuar tal customização.
Já que não queremos utilizar o provider e o banco de dados ASPNETDB.MDF fornecido por padrão pela plataforma, devemos recorrer ao arquivo Web.Config para configurar o provider da forma que precisarmos. Para esta configuração temos os seguintes elementos: membership e providers, que deverão estar declarados dentro do elemento system.web. Estes elementos, por sua vez, possuem uma série de atributos que definem as configurações e possíveis comportamentos e validações que o nosso provider irá desempenhar.
Para cada provider que é adicionado dentro do elemento providers especificamos os atributos de configuração deste provider e, através de atributos definidos no elemento membership, definimos configurações a nível "global de providers". Veremos cada um dos possíveis atributos (voltado para o uso do SqlMembershipProvider) e seus respectivos valores nas tabelas abaixo:
|
|
Agora que já conhecemos todos os parâmetros possíveis dos elementos acima, veremos abaixo um exemplo de como estar configurando isso dentro do arquivo Web.Config da aplicação ASP.NET:
<?xml version="1.0"?> <configuration> <connectionStrings> <clear/> <add name="SqlConnectionString" connectionString="Data Source=local;Initial Catalog=DBTest;Integrated Security=True;"/> </connectionStrings> <system.web> <membership defaultProvider="SqlMembershipProvider"> <providers> <clear/> <add name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SqlConnectionString" applicationName="NomeAplicacao" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="8" /> </providers> </membership> </system.web> </configuration> |
|||
Web.Config |
Algumas considerações do código acima: temos uma coleção de connectionStrings, onde através do elemento connectionStrings as informamos e no atributo connectionStringName especificamos o nome do conexão que iremos utilizar; outro ponto importante é o elemento add que é "filho" do elemento providers: como podemos ter uma coleção de providers, podemos adicionar quantos desejarmos e sempre fazemos isso adicionando um novo elemento add e as suas respectivas configurações.
Agora que já temos conhecimento suficiente para criar a infraestrutura e configurar um determinado provider, vamos analisar a classe Membership e seus respectivos membros. Veremos agora como manipular e gerir usuários dentro de uma base de dados SQL Server e, mais tarde, quando estivermos falando sobre os controles que o ASP.NET fornece, veremos como implementar a segurança em uma aplicação utilizando FormsAuthentication.
Não vou me preocupar aqui em mostrar e explicar as propriedades da classe Membership justamente porque são propriedades de somente leitura e apenas devolvem os valores que configuramos no Web.Config para o provider. Nos concentraremos apenas nos métodos, pois são o mais importante e nos dão todas as funcionalidades de manutenção e criação de usuários na base de dados.
|
Em alguns dos métodos acima comentamos sobre o objeto chamado MembershipUser. Veremos abaixo a lista de métodos e propriedades do mesmo com uma breve descrição.
|
|
Depois da teoria veremos abaixo um trecho curto que mostra como chamar esses métodos e propriedades via código:
// Criando Usuário MembershipCreateStatus status = MembershipCreateStatus.UserRejected; MembershipUser user = Membership.CreateUser( "IsraelAece", "P@$$w0rd", "israel@projetando.net", "Questão Password", "Resposta Password", true, out status); Response.Write(string.Format("Status Criação: {0}", status.ToString()); // Recuperando Usuário MembershipUser user = Membership.GetUser("IsraelAece"); if(user != null) { Response.Write(string.Format("E-mail: {0}", user.Email)); } // Excluindo Usuário if(Membership.Delete("IsraelAece")) { Response.Write("Usuário excluído com sucesso."); } // Populando um GridView this.GridView1.AutoGenerateColumns = true; this.GridView1.DataSource = Membership.GetAllUsers(); this.GridView1.DataBind(); " Criando Usuário Dim status As MembershipCreateStatus = MembershipCreateStatus.UserRejected Dim user As MembershipUser = _ Membership.CreateUser( _ "IsraelAece", _ "P@$$w0rd", _ "israel@projetando.net", _ "Questão Password", _ "Resposta Password", _ True, _ status) Response.Write(String.Format("Status Criação: {0}", status.ToString()) " Recuperando Usuário Dim user As MembershipUser = Membership.GetUser("IsraelAece") If Not IsNothing(user) Then Response.Write(String.Format("E-mail: {0}", user.Email)) End If " Excluindo Usuário If Membership.Delete("IsraelAece") Then Response.Write("Usuário excluído com sucesso.") End If " Populando um GridView Me.GridView1.AutoGenerateColumns = True Me.GridView1.DataSource = Membership.GetAllUsers() Me.GridView1.DataBind() |
|||
C# | VB.NET |
Integração com outras tabelas
Umas das principais dúvidas é como integrar a tabela de aspnet_Membership com outras tabelas do banco de dados, como por exemplo, uma tabela com o endereço, R.G., C.P.F., entre outros dados. Neste caso cria-se uma coluna chamada UserId do tipo uniqueidentifier nesta tabela "filha" que será uma ForeignKey da tabela aspnet_Membership. Claro que o objeto MembershipUser ainda continuará com as mesmas propriedades e, apesar de um pouco complicado, você teria que sobrescrever o provider para poder contemplar esse novo design do seu objeto.
Para ilustrar, analise a imagem abaixo. Foi criada a relação entre as tabelas através do aspnet_Membership.UserId x Colaboradores.ColaboradorID para firmar o relacionamento entre as tabelas:
Figura 5 - Relacionamento com a tabela aspnet_Membership. |
A onda do momento é o Framework Atlas para processamento de códigos server-side, sem a necessidade da reconstrução completa da página. Isso torna a aplicação Web muito parecida com aplicações Windows, deixando-a muito mais interativa.
Devido a isso, nas últimas versões do Atlas (ainda em versões não Release), a Microsoft implementou métodos dentro de um determinado objeto para trabalhar diretamente com a API de Membership, sem a necessidade de um refresh e a reconstrução da página toda. O objeto que disponibiliza estes serviços chama-se Sys.Services.AuthenticationService e é um objeto desenvolvido em Javascript. Este, por sua vez, têm três métodos, os quais analisaremos as suas utilidades na tabela abaixo:
|
Quando adicionamos um projeto ASP.NET Atlas no Visual Studio .NET 2005, a autenticação via Altas vem desabilitada no Web.Config e é necessário habilitarmos para fazer o uso do objeto de autenticação. Para se certificar disso, é necessário que o arquivo Web.Config da aplicação ASP.NET Atlas contenha as seguintes entradas:
<sectionGroup name="microsoft.web" type="Microsoft.Web.Configuration.MicrosoftWebSectionGroup"> <!-- outras seções --> <section name="webServices" type="Microsoft.Web.Configuration.WebServicesSection" requirePermission="false" /> <section name="authenticationService" type="Microsoft.Web.Configuration.AuthenticationServiceSection" requirePermission="false" /> </sectionGroup> <!-- Certifique-se de que o Provider padrão esteja configurado corretamente e apontando para uma base de dados válida. As seções de configurações de SqlMembershipProvider foram suprimidas para poupar espaço, mas não muda nada em relação aos exemplos anteriores. --> <webServices enableBrowserAccess="true" /> <authenticationService enabled="true" /> |
|||
Web.Config |
Notem que há um elemento chamado webServices que neste cenário, é responsável por efetuar a autenticação. Na página que fará a autenticação via Atlas você notará que não se tem código em VB.NET ou C#, tudo será escrito utilizando Javascript. Acredito que, como já aconteceu com alguns controles, nas próximas versões isso já esteja encapsulado em controles drag-and-drop. Para iniciarmos o entendimento da estrutura cliente, a página irá conter um controle do tipo ScriptManager, o qual tem a finalidade de expor (código cliente) grande parte das funcionalidades e controles do Atlas.
É através do controle ScriptManager que temos acesso ao objeto Sys.Services.AuthenticationService. Agora que já temos acesso à ele, resta codificar para efetuarmos o login do usuário. Como qualquer outro sistema de Login, é necessário termos dois controles TextBox e um Button. Teremos também Label para exibirmos a mensagem de autenticado/não autenticado para o usuário saber se foi ou não autenticado com sucesso. O código é mostrado abaixo:
<html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <atlas:ScriptManager ID="scriptManager" runat="server" EnableScriptComponents="false" > </atlas:ScriptManager> </head> <body> <form id="form1" runat="server"> <span id="TextoLogado" > Web.Config |
No exemplo acima utilizamos o método login para autenticar o usuário e, via Javascript, fazemos as manipulações necessárias para assim que o método login retornar o valor (com sucesso ou não), o layout é ajustado, exibindo uma mensagem de que o usuário foi autenticado com sucesso.
Informamos ao método login, além do login e senha do usuário (recuperado dos TextBoxes), um parâmetro booleano que indica se o cookie vai ou não ser persistente. O último parâmetro é a função de callback, a qual será disparada assim que o método de login finalizar o processamento, já que o mesmo é processado assincronamente. Já para o método logout (que irá chamar o método Logout da classe FormsAuthentication), você passa apenas o método que será disparado assim que o método for finalizado, e é neste método que você fará a manipulação do layout para dar um feedback ao usuário que, no caso acima, apenas alteramos o valor do Label para "Não Logado.".
Este exemplo foi baseado na versão de 06/04/2006 do Atlas e os códigos mostrados acima são baseados no exemplo exibido nos QuickStarts do Atlas. É possível que nas versões futuras do Atlas este objeto de autenticação client-side possa mudar.
Roles
As Roles ou papéis em português nos ajudam a gerenciar as autorizações que um determinado usuário tem dentro de uma aplicação. Essas roles nos permitem customizar o acesso a recursos da aplicação ou até mesmo da página. Imaginem a seguinte situação: você tem em sua intranet uma aplicação onde os usuários se autenticam e têm acesso à aplicação ASP.NET. Dentro desta aplicação existe uma página ASPX que lista os registros de uma determinada tabela da base de dados, mas somente quem é "Administrador" pode excluir informações desta tabela. Mas podemos ir além disso, ou seja, podemos até mesmo especificar que usuários não contidos na role "Administrador" não consigam abrir a página ExtratoPagamentos.aspx.
Com o uso de roles você consegue definir regras para isso, ou seja, você pode criar suas próprias regras durante o desenvolvimento, onde você pode dizer que somente usuários que estão contidos na role "Administrador" podem clicar no botão de exclusão do registro. Obviamente que somente depois de devidamente autenticado o usuário tem acesso a aplicação, já que roles não trabalham com usuários anônimos. Cada usuário pode estar contido em uma ou muitas roles, dando assim uma maior flexibilidade.
A organização de roles na aplicação ajuda bastante a definir a arquitetura da segurança da mesma, mas há também um diagrama na UML, chamado de Use Cases, onde é possível extrairmos possível usuários e roles que a aplicação poderá vir a ter. Como já notamos a importância disso, a Microsoft implementou (assim como o Membership) o modelo de Provider Model em sua arquitetura e, com isso, nos disponibilizou o que chamamos de Role Management API. Esta API não está restrita a trabalharmos com páginas ou diretórios, mas a mesma fornece uma série de métodos para que, programaticamente, consigamos ajustar alguns recursos a roles específicas.
Para aqueles que já estão familiarizados com a arquitetura do Windows, verão uma grande semelhança entre as roles e os grupos de usuários do Windows. Já que utilizamos a arquitetura de Provider Models, a API pode ser tanto utilizada com a classe SqlRoleProvider quanto para a classe WindowsTokenRoleProvider. A primeira delas usa o SQL Server para o armazenamento das roles dos usuários. Já a segunda utiliza os grupos do Windows para trabalhar, mas lembrando que só é interessante o seu uso se for em uma intranet, pois o uso dela em uma aplicação que será vista por toda a internet é completamente inviável. Já para internet, devemos utilizar o FormsAuthentication para estarmos gerenciando os usuários dentro da aplicação e a API de Roles está completamente integrada com isso. Assim como no Membership, utilizaremos aqui a classe SqlRoleProvider (que usa um banco de dados dentro do SQL Server) para os exemplos de uso e configurações.
A configuração no arquivo Web.Config
A forma de configuração do Role Provider é bem parecida com a do Membership, pois temos que especificar todas as configurações que desejarmos para a API no arquivo Web.Config através do elemento roleManager. Este elemento também possui um elemento filho chamado providers, o qual recebe uma coleção dos providers que podem ser utilizados pela aplicação. Através da tabela abaixo analisaremos atributo por atributo destas configurações e para o que cada um deles serve:
|
|
Agora que já conhecemos todos os parâmetros possíveis dos elementos acima, veremos abaixo um exemplo de como estar configurando isso dentro do arquivo Web.Config da aplicação ASP.NET:
<?xml version="1.0"?> <configuration> <connectionStrings> <clear/> <add name="SqlConnectionString" connectionString="Data Source=local;Initial Catalog=DBTest;Integrated Security=True;"/> </connectionStrings> <system.web> <roleManager defaultProvider="SqlRoleProvider" enabled="true" cacheRolesInCookie="true" cookieName=".ASPROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" cookieSlidingExpiration="true" cookieProtection="All" > <providers> <add name="SqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="SqlConnectionString" applicationName="NomeAplicacao" /> </providers> </roleManager> </system.web> </configuration> |
|||
Web.Config |
A classe Roles
A classe estática Roles, encontrada dentro do Namespace System.Web.Security, é usada pelo ASP.NET para criar e gerenciar as roles. Esta classe faz uso da classe FormsAuthentication (já existente nas versões anteriores do ASP.NET) para criar e gerenciar a autorização do usuário dentro da aplicação Web.
Falando um pouco sobre o seu funcionamento: existe um membro interno chamado s_Provider do tipo RoleProvider (a classe base para as classes concretas de RoleProvider, como por exemplo o SqlRoleProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize é executado. Ele se encarrega de extrair os providers do arquivo Web.Config e instancia-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar. Indo um pouco mais abaixo, existe uma classe (também dentro do Namespace System.Web.Security) chamada RolePrincipal. Essa classe implementa a interface IPrincipal e representa o contexto de segurança da requisição HTTP corrente. Quando as roles estão habilitadas na aplicação, o módulo RoleManagerModule define a propriedade User da classe HttpContext da requisição corrente, com uma instância da classe RolePrincipal.
A classe RolePrincipal expõe as identidades de segurança da requisição HTTP e se encarrega de executar as checagens de roles. Se a propriedade CacheRolesInCookie estiver definida como True, a RolePrincipal tenta recuperar as roles que estão armazenadas em um cookie. Já, se esta opção estiver definida como False, a classe RolePrincipal recupera as roles do usuário através do provider especificado no arquivo Web.Config.
Como esta classe encapsula o acesso ao provider específico, ela fornece facilidades como: criação de novas roles, verificação de usuários, gerenciamento de roles em seu repositório (SQL Server, Grupos do Windows, etc). Como já vimos anteriormente o funcionamento interno desta classe, podemos perceber que o ASP.NET confia no provider especificado no Web.Config para se comunicar com a fonte de dados. Como já sabemos, o .NET Framework inclui a classe SqlRoleProvider para acesso e persistência dos dados no SQL Server e é este que vamos utilizar no decorrer dos exemplos deste artigo.
Mas esta arquitetura é extensível, ou seja, a qualquer momento eu posso criar o meu próprio provider de Roles, onde eu queira customizá-lo para um outro repositório qualquer. E, para que isso seja possível, é necessário herdarmos da classe abstrata chamada RoleProvider e customizá-la de acordo com o nosso repositório. Para ilustar o cenário, imaginemos que desejamos ter um provider de RoleProvider sendo persistido em um arquivo XML:
public class XmlRoleProvider : RoleProvider { // customizo aqui todos os métodos e // propriedades necessárias } Public Class XmlRoleProvider Inherits RoleProvider " customizo aqui todos os métodos e " propriedades necessárias End Class |
|||
C# | VB.NET |
Não vou me preocupar aqui em mostrar e explicar as propriedades da classe Roles justamente porque são propriedades de somente leitura e apenas devolvem os valores que configuramos no Web.Config para o provider. Nos concentraremos apenas nos métodos, pois é o mais importante, os quais nos dão todas as funcionalidades de manutenção e criação de roles na base de dados.
|
Depois da teoria, veremos abaixo um trecho curto de código que mostra como chamar esses métodos e propriedades via código:
// Criando Role if(!Roles.RoleExists("Administradores")) { Roles.CreateRole("Administradores"); } // Definindo Usuário para uma Role if(!Roles.IsUserInRole("IsraelAece", "Administradores")) { Roles.AddUserToRole("IsraelAece", "Administradores"); } // Recuperando Roles de um Usuário string[] roles = Roles.GetRolesForUser("IsraelAece"); foreach(string role in roles) { Response.Write(string.Format("Role: {0}<br>", role)); } // Excluindo Usuário da Role if(Roles.Delete("Administradores", false)) { Response.Write("Role excluída com sucesso."); } // Habilitando Recursos this.btnLiberarCredito.Visible = Roles.IsUserInRole("Administradores"); " Criando Role If Not Roles.RoleExists("Administradores") Then Roles.CreateRole("Administradores") End If " Definindo Usuário para uma Role If Not Roles.IsUserInRole("IsraelAece", "Administradores") Then Roles.AddUserToRole("IsraelAece", "Administradores") End If " Recuperando Roles de um Usuário Dim roles() As String = Roles.GetRolesForUser("IsraelAece") For Each role As String in roles Response.Write(string.Format("Role: {0}<br>", role)) Next " Excluindo Usuário da Role If Roles.Delete("Administradores", False) Then Response.Write("Role excluída com sucesso.") End If " Habilitando Recursos Me.btnLiberarCredito.Visible = Roles.IsUserInRole("Administradores") |
|||
C# | VB.NET |
Além disso, ainda temos alguns outros lugares (não menos importantes) para utilizarmos as roles; um deles é no arquivo Web.Config. É nele que definimos páginas ou diretórios da aplicação que são restritas à determinadas roles. Vale lembrar que também é permitido termos vários arquivos Web.Config"s nas pastas internas onde, para cada uma, implementamos um arquivo de configuração definindo as roles, podendo sobrescrever as roles especificadas no arquivo Web.Config da raiz.
Para exemplificar o uso das roles no arquivo Web.Config, analise o código abaixo:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <location path="ExtratoConta.aspx"> <system.web> <authorization> <deny users="?" /> <allow roles="*" /> </authorization> </system.web> </location> <location path="DarCredito.aspx"> <system.web> <authorization> <deny users="?" /> <allow roles="Gerentes, Administradores" /> </authorization> </system.web> </location> </configuration> |
|||
Web.Config |
Nas especificações acima a página ExtratoConta.aspx está proibida para usuários anônimos (não autenticados) e permitida para todos os usuários autenticados (independentemente da role). Já a página DarCredito.aspx está proibida para usuários anônimos e permitida somente para usuários que estão contidos dentro da role "Gerentes" ou "Administradores".
Mas o uso ainda não pára por aí. O ASP.NET 2.0 fornece um arquivo chamado Web.sitemap. Este arquivo, que em sua estrutura é um XML, tem a finalidade de carregar controles hierárquicos, como por exemplo: Menu, TreeView, SiteMapPath (controles contidos na Tab Navigation) da ToolBox do Visual Studio .NET 2005. Este arquivo é responsável por toda a navegação da aplicação e no elemento siteMapNode existe um atributo chamado roles, onde passamos as roles em que o usuário autenticado deve estar contido. Se não estiver, o item não chega à ser exibido para o usuário. Abaixo é mostrado um exemplo de um arquivo Web.sitemap com estas configurações:
<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <siteMapNode url="Default.aspx" title="Home"> <siteMapNode url="" title="ContaCorrente" title="Conta Corrente"> <siteMapNode url="ExtratoConta.aspx" title="Extrato" /> <siteMapNode url="DarCredito.aspx" title="Crédito" roles="Gerentes, Administradores" /> </siteMapNode> </siteMapNode> </siteMap> |
|||
Web.sitemap |
O ASP.NET 2.0 contém um conjunto completo de controles para trabalharmos com a segurança, contendo inclusive uma Tab chamada Security dentro da ToolBox do Visual Studio .NET 2005 para armazenar estes controles. Estes controles estão fortemente integrados com o Membership, e fornecem uma porção de funcionalidades que são utilizadas comumente dentro de uma aplicação Web, como por exemplo: alteração de senhas, recuperação de senhas, criação de usuários, etc. A imagem abaixo mostra os controles disponíveis dentro do Visual Studio .NET 2005, e analisaremos cada um deles mais abaixo:
Figura 1 - Controles de segurança do Visual Studio .NET 2005. |
Login
O controle Login é um Composite Control, ou seja, ele é composto por vários outros controles que fazem o trabalho de validação do usuário. Entre esses controles podemos citar TextBoxes, controles de validação (RequiredFieldValidator) e um Button. Os controles RequiredFieldValidators, junto com os demais controles, são devidamente agrupados para que os mesmos não interfiram nos possíveis seções que temos na página onde ele estará hospedado. Esses validadores são necessários para evitar que o usuário submeta o formulário sem informar o Login e Senha. Fora isso, este mesmo controle ainda permite uma série de outros recursos, os quais veremos mais adiante.
Este controle usa o Membership Provider para validar o usuário na fonte de dados especificada no arquivo Web.Config. É importante dizer que este controle utiliza o provider e, se desejar que esse seu controle trabalhe com um outro provider, pode especificá-lo na propriedade MembershipProvider do mesmo. Para nos certificarmos disso podemos recorrer à ferramenta .NET Reflector e visualizar o método interno chamado AuthenticateUsingMembershipProvider da classe Login. Repare que ele recupera o provider do arquivo Web.Config e, através do método ValidateUser, valida o usuário junto a fonte de dados:
private void AuthenticateUsingMembershipProvider(AuthenticateEventArgs e) { MembershipProvider provider1 = LoginUtil.GetProvider(this.MembershipProvider); e.Authenticated = provider1.ValidateUser(this.UserNameInternal, this.PasswordInternal); } Private Sub AuthenticateUsingMembershipProvider(ByVal e As AuthenticateEventArgs) Dim provider1 As MembershipProvider = LoginUtil.GetProvider(Me.MembershipProvider) e.Authenticated = provider1.ValidateUser(Me.UserNameInternal, Me.PasswordInternal) End Sub |
|||
C# | VB.NET |
O controle Login ainda fornece um evento que permite-nos interceptar e customizar a autenticação do usuário dentro da aplicação. Isso é possível graças ao evento chamado Authenticate, o qual tem em sua assinatura um argumento do tipo AuthenticateEventArgs. Este argumento provê uma propriedade booleana chamada Authenticated, qual indicará ao provider se já autenticamos o usuário. Um exemplo disso é mostrado abaixo:
private void OnAuthenticate(object sender, AuthenticateEventArgs e) { e.Authenticated = ValidacaoCustomizada(Login1.UserName, Login1.Password); } Private Sub Login1_Authenticate(ByVal sender As Object, ByVal e As AuthenticateEventArgs) Handles Login.Authenticate e.Authenticated = ValidacaoCustomizada(Login1.UserName, Login1.Password) End Sub |
|||
C# | VB.NET |
Ainda existem mais dois eventos que merecem algum comentário: LoggingIn e LoggedIn. O primeiro deles ocorre quando o usuário submete os dados para validação junto ao provider. Em outras palavras, ocorre quando o usuário pressiona o botão do controle Login. Esse evento manda em sua assinatura um argumento do tipo LoginCancelEventArgs, o qual contém uma propriedade chamada Cancel, onde você pode, em um último momento, evitar que a autenticação seja feita. Já o LoggedIn ocorre quando, as credenciais do usuário já foram validadas pelo provider e o cookie já foi criado e enfileirado para ser enviado ao browser na próxima resposta. A ordem de disparo destes eventos são: LoggingIn, Authenticate e por fim LoggedIn. Ainda temos algumas propriedades interessantes no controle Login, as quais poderemos visualizar abaixo:
|
LoginView
Este controle tem uma característica interessante: ele exibe um template para customizarmos um conteúdo para um determinado status ou role em que o usuário se encontra em um determinado momento. Dentro dele temos alguns templates, os quais merecem uma explicação:
AnonymousTemplate: especifica um template para usuários que não estão logados na aplicação, e usuários logados nunca visualizarão este template.
LoggedInTemplate: especifica um template padrão para usuários que estão logados na aplicação mas não contidos em qualquer role que tenha um template próprio.
RoleGroups: especifica um template para usuários que estão logados na aplicação, porém podemos ter aqui templates para determinadas roles e, conseqüentemente, somente usuários contidos naquela role poderão visualizar. Este controle mantém uma propriedade chamada RoleGroups do tipo RoleGroupCollection, onde podemos/devemos definir as roles através de objetos do tipo RoleGroup.
Abaixo é mostrado um exemplo de como estar utilizando o controle LoginView:
<asp:LoginView id="LoginView1" runat="server"> <AnonymousTemplate> É necessário logar na aplicação. </AnonymousTemplate> <LoggedInTemplate> Seja bem vindo <asp:LoginName id="LoginName1" runat="Server" />. </LoggedInTemplate> <RoleGroups> <asp:RoleGroup Roles="Admin"> <ContentTemplate> Sr(a). Administrador, <asp:LoginName id="LoginName2" runat="Server" /> </ContentTemplate> </asp:RoleGroup> </RoleGroups> </asp:LoginView> |
|||
ASPX |
PasswordRecovery
Este controle tem a finalidade de recuperar a senha de um determinado usuário. É um controle composto por TextBox, RequiredFieldValidator e um Button onde, dado um username, o provider busca na fonte de dados e o devolve. Essa devolução é realizada através de envio de e-mail para o endereço cadastrado previamente.
Para que a senha possa ser recuperada o seu provider não pode estar configurado para salvar a mesma como Hashed pois, como sabemos, é um algoritmo que não é possível reversão e, além disso, a propriedade EnablePasswordRetrieval do Membership deve estar definida como True. Se por acaso a forma de armazenamento da senha estiver como Hashed, gera então uma nova senha e envia esta para o usuário.
Agora somente nos resta configurar o e-mail para enviar ao usuário. Para isso, utilizaremos o elemento mailSettings no arquivo Web.Config. Abaixo é exibido um exemplo de como proceder nas configurações:
<system.net> <mailSettings> <smtp deliveryMethod="Network"> <network host="mail.site.com.br" port="25" /> </smtp> </mailSettings> </system.net> |
|||
Web.Config |
<asp:PasswordRecovery id="PasswordRecovery1" runat="server"> <successtemplate> <table border="0"> ASPX |
Olá <% UserName %>, A sua nova senha é <% Password %>. Obrigado! |
|||
RecuperacaoSenha.txt |
UserName e Password são palavras reservadas que, antes de enviar o e-mail, o ASP.NET automaticamente substitui os respectivos dados pelos valores efetivos.
LoginStatus
O controle LoginStatus fornece-nos dois estados: logado e não logado, e o mesmo consegue obter essa informação através da propriedade IsAuthenticated da classe Page. Este controle pode exibir um Link ou uma Imagem (dependendo das propriedades LoginImageUrl e LogoutImageUrl). Quando o usuário não está logado, o controle fornece um link até a página de login da aplicação. Se o usuário estiver logado, este controle fornece um link para a página de logout da aplicação. No caso do Logout internamente ele se encarrega de chamar o método SignOut da classe FormsAuthentication.
Há também um comportamento muito interessante dentro deste controle, que é a propriedade LogoutAction, a qual recebe um enumerador do tipo LogoutAction. Esta propriedade vai dizer ao controle o que ele deve fazer quando o usuário efetuar o logout na aplicação. O enumerador LogoutAction tem três opções, as quais são explicadas abaixo:
Redirect: redireciona o usuário para a página especificada na propriedade LogoutPageUrl e, se esta estiver vazia, o usuário é redirecionado para a página de login da aplicação, configurada no arquivo Web.Config.
RedirectToLoginPage: redireciona o usuário para a página de login da aplicação, configurada no arquivo Web.Config.
Refresh: apenas atualiza a página atual.
LoginName
O controle LoginName apenas exibe o valor contido dentro da propriedade System.Web.UI.Page.User.Identity.Name. Lembrando que, se a propriedade estiver definida com um valor vazio, o controle não é exibido. Uma restrição que há em cima deste controle é que ele não pode ser usado fora da tag form, como por exemplo no title de uma página.
CreateUserWizard
Este controle (CompositeControl) fornece uma interface para a criação de um novo usuário, utilizando a arquitetura do Membership. Como o controle confia no provider informado, ele irá obrigar o usuário a digitar informações como E-mail, Question, Answer. Se a definição da propriedade AutoGeneratePassword estiver definida como True o próprio ASP.NET se encarrega de gerar a senha aleatória e salvá-la na fonte de dados.
O controle CreateUserWizard permite-nos enviar um e-mail automaticamente ao usuário assim que o processo for concluído e, para isso, é necessário a configuração do elemento mailDefinition, bem como o SMTP no arquivo Web.Config. Há ainda algumas propriedades opcionais interessantes:
|
ChangePassword
O controle ChangePassword possibilita ao usuário trocar uma senha por outra que ele desejar. Para que isso aconteça, é disponibilizado neste controle três TextBox, onde temos os seguintes campos a serem preenchidos: senha atual, nova senha e confirmação da nova senha. Este controle também permite a configuração do elemento mailDefinition para o envio da nova senha para o e-mail do usuário. function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i WSAT - Web Site Administration Tool
WSAT, Web Site Administration Tool, é uma aplicação ASP.NET fornecida junto com o Visual Studio .NET 2005 ou Visual Web Developer (versão Express). Esta aplicação fornece uma interface amigável para configurarmos as API"s de Membership e Roles de uma determinada aplicação ASP.NET. O WSAT se encarrega de configurar o arquivo Web.Config (se o arquivo não existir, ele mesmo o cria) automaticamente na medida em que vamos alterando e definindo alguns valores. Além disso, se você optar pelo uso do SqlMembershipProvider, é automaticamente criado um banco de dados chamado ASPNETDB.MDF dentro do diretório App_Data da aplicação corrente, já incluindo todo o schema para suportar as API"s que falamos anterioramente.
Para administrar sua aplicação ASP.NET através do WSAT, as credenciais do usuário que está executando o Visual Studio ou Visual Web Developer deve ter permissões de escrita e leitura no arquivo Web.Config e também à pasta App_Data da aplicação que está sendo administrada.
O WSAT está dividido em algumas Tabs: Application, Security e Provider. Veremos cada uma delas mais detalhadamente abaixo:
Tab Application
Nesta tab, você pode gerenciar as configurações mais comuns relacionadas à uma aplicação Web. Entre essas configurações temos:
Application Settings: esta opção permite-nos gerenciar as entradas de chave/valor, onde colocamos alguns valores que são utilizados pela aplicação para não deixarmos em hard-code, que comumente são: timeouts, URL para Web Services, endereços de arquivos e/ou diretórios usados dentro da aplicação. Tendo os valores aqui, faz com que se, algum dia precisarmos mudá-los, não será necessário recompilar a aplicação. Mas cuidado: os dados, por padrão, ficam em clean-text, ou seja, não será criptografado. Logo, não armazene dados sensíveis neste local.
Simple Mail Transfer Protocol (SMTP): se a aplicação exige o envio de e-mails você pode estar configurando o SMTP nesta seção. Para exemplificar o uso disso, lembre-se do controle PasswordRecovery que, dado um login, o ASP.NET se encarrega de enviar um e-mail para o usuário contendo a senha.
Application Status: você pode estar "desligando" a sua aplicação temporariamente para efetuar alguma manutenção.
Debug e Trace: nesta seção você pode estar definindo alguns parâmetros para configurar o Debug e Trace da sua aplicação, podendo habilitar ou desabilitar e também aplicar alguns filtros.
Figura 1 - Tab Application. |
Tab Security
A Tab Security traz informações e faz o gerenciamento a respeito das roles da aplicação corrente para recursos específicos, o que permite restringir acesso à determinados usuários que estão contidos dentro de uma role específica. Além disso, permite também o gerencimento dos usuários da aplicação, podendo criar, alterar ou excluir usuários. É também fornecido um Security Setup Wizard, que permite-nos configurar um nível básico de segurança através deste wizard.
Temos duas formas de autenticação: Forms e Windows. Veremos sobre cada um logo abaixo:
Forms: este tipo de autenticação é utilizada para aplicações que são acessadas através da internet. Esta estrutura utiliza a infraestrutura de Membership e Roles para gerenciar os usuários e, através das roles, definir os recursos que ele pode ou não ter acesso.
Windows: este tipo já é voltado para uma rede local (intranet), onde podemos aproveitar a arquitetura do Windows para autenticar os usuários e, através dos grupos, gerenciarmos os recursos à que eles têm acesso. Neste cenário não é necessário a criação de uma página de login, pois as credenciais que serão utilizadas já foram informadas quando o usuário fez o login no Windows.
Figura 2 - Tab Security. |
Como podemos ver na imagem acima, na seção Users podemos gerenciar os usuários da aplicação, inserindo, alterando ou excluindo-os. Ainda nesta seção há uma opção chamada Select authentication type, onde você define o tipo de autenticação que a sua aplicação usará. Na seção Roles você pode estar criando novas roles e ainda gerenciar os usuários que ela contém. É possível também através do link Disable Roles, habilitar ou não essa estrutura para a aplicação. Finalmente, na seção Access Rules, você pode estar definindo os acessos a determinados recursos da aplicação.
E, como podem reparar, o WSAT fará uso das classes Membership e Roles definidas para a aplicação internamente, via arquivo Web.Config.
Tab Provider
Na aba Provider você especifica o provider de Membership e Role que a sua aplicação irá utilizar. Logo, você pode ter vários providers especificados no seu arquivo Web.Config que a interface do WSAT interpreta e as exibe para que você possa selecionar.
Há duas opções nesta aba: Select a single provider for all site management data e Select a different provider for each feature (advanced). A primeira delas é usada quando utilizamos um mesmo provider para todas as funcionalidades. Já na segunda, podemos especificar diferentes providers para cada funcionalidade. As imagens abaixo mostram um exemplo destas configurações:
Figura 3 - Tab Provider. |
Figura 4 - Tab Provider. |
Configurando a Aplicação
Para finalizar, vamos "compilar" tudo o que vimos até aqui e colocar em um arquivo Web.Config para termos uma idéia de como as configurações devem ficar para utilizar os providers SqlMembership e SqlRoleProvider:
<?xml version="1.0"?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <connectionStrings> <clear/> <add name="SqlConnectionString" connectionString="Data Source=local;Initial Catalog=DB;Integrated Security=SSPI;"/> </connectionStrings> <system.web> <authentication mode="Forms"> <forms loginUrl="~/MembershipLogin.aspx" name=".NomeCookie"/> </authentication> <membership defaultProvider="SqlMembershipProvider"> <providers> <clear/> <add name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SqlConnectionString" applicationName="NomeAplicacao" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="8" /> </providers> </membership> <roleManager cacheRolesInCookie="true" defaultProvider="SqlRoleProvider"> <providers> <clear /> <add connectionStringName="SqlConnectionString" applicationName="NomeAplicacao" name="SqlRoleProvider" type="System.Web.Security.SqlRoleProvider" /> </providers> </roleManager> </system.web> <system.net> <mailSettings> <smtp deliveryMethod="Network"> <network host="mail.site.com.br" port="25" /> </smtp> </mailSettings> </system.net> <location path="ExtratoConta.aspx"> <system.web> <authorization> <deny users="?" /> <allow roles="*" /> </authorization> </system.web> </location> <location path="DarCredito.aspx"> <system.web> <authorization> <deny users="?" /> <allow roles="Gerentes, Administradores" /> </authorization> </system.web> </location> </configuration> |
|||
Web.Config |
Conclusão:
Como pudemos ver no decorrer das seções deste artigo, as API"s de Membership e Role Management nos fornecem uma arquitetura bastante flexível, robusta e extensível. Como tudo é configurado através do arquivo de configuração, o processo fica ainda mais fácil. Com o auxílio do WSAT - Web Site Administration Tool, tudo o que vimos via código, o WSAT o encapsula, evitando que criemos uma interface para gerenciar isso. Finalmente, vimos que a Microsoft deu um grande passo nestas seções, quais são indispensáveis para qualquer aplicação Web.