Article Details                  
 
Programação em Asp.Net - parte VIII

Introdução da nova API para processar dados associados às contas de utilizadores (Membership API)

Programação em Asp.Net - parte VIII


Autor: Luís Abreu

Conteúdo: Introdução à programação em Asp.Net 2.0

Ferramentas: Visual Studio December CTP


Nos artigos anteriores foram analisados alguns dos novos controlos que acompanham a nova plataforma. Ao longo da série já falámos acerca dos vários controlos que permitem o acesso a fonte de dados e dos novos controlos do tipo data bound. Hoje vamos falar acerca das novidades associadas à segurança.


A versão 1.XX fornecia alguns mecanismos de segurança que auxiliavam os programadores na construção de sites. Dois dos conceitos que estão associados à segurança são a autorização e autenticação. Como é do conhecimento geral, autenticar um utilizador consiste em identificar o utilizador, ou seja, consistem em verificar que o utilizador é realmente quem ele diz ser. Este processo de validação consiste (tradicionalmente) na verificação de um par nome de utilizador/palavra chave. Por sua vez, a autorização consiste em saber se um utilizador (autenticado ou não) possui privilégios suficientes para conseguir a obter acesso a um determinado recurso.


Mecanismos de segurança da versão 1.XX


A primeira versão da framework possuía mecanismos que auxiliavam o programador na autenticação e na autorização de recursos. A plataforma permitia o recurso a um de três tipos de autenticação (configurados através do ficheiro web.config):



  • windows: este mecanismo, que é utilizado por defeito, efectua a autenticação do utilizador com base na conta actual que efectua o pedido. Normalmente, este tipo de mecanismo de autenticação é utilizado em intranets internas onde é possível gerir facilmente os utilizadores que utilizam a aplicação.

  • passport: baseado no serviço centralizado de autenticação fornecido pela Microsoft;

  • forms: este é (provavelmente) o mecanismo de autenticação mais utilizado. Nestas situações, é normal construirmos uma base de dados com um determinado esquema onde é mantida toda a informação associada aos utilizadores.


De todos os esquemas anteriores, o mais utilizado é, sem qualquer dúvida, o baseado em Forms. Este mecanismo é muito mais flexível do que os restantes, pelo que é ideal para a construção de sites Web. A utilização deste mecanismo obrigava à introdução de entradas específicas no ficheiro web.config. O excerto seguinte apresenta o código XML adequado a este tipo de situações:



<configuration>
<authentication mode="Forms">
<forms loginUrl="login.aspx" />
</authentication>
</configuration>

Como podemos verificar, estamos a configurar o modo de autenticação do site para o modo Forms e a definir a página responsável por autenticar o utilizador. Claro que este código não obriga um utilizador a efectuar a autenticação ao aceder ao site. Para tal, temos de entrar em linha de conta com o outro conceito associado à segurança: autorização. Se configurarmos a aplicação Web por forma a que esta não permite o acesso de utilizadores anónimos, então sempre que alguém efectuar um pedido de uma determinada página será confrontado com o respectivo formulário de autenticação. A adição das linhas seguintes ao ficheiro de configuração traduz o requisito anterior:



<authorizarion>
<deny users="?">
</authorizarion>

A partir desat altura, sempre que um utilizador aceder pela primeira vez ao site, será confrontado com a página de autenticação (de acordo com o exemplo apresentado, será a página login.aspx). Este comportamento é automaticamente aplicado pelo Asp.Net. O problema aqui reside no facto de ter de ser o programador a escrever todo o código que efectua a validação das credenciais do utilizador. Claro que a plataforma possui uma classe que permite auxiliar o programador na construção do cookie válido de identificação do utilizador, mas ainda é necessário escrever o código de acesso à base de dados por forma a efectuar a validação. Normalmente, este tipo de comportamento é descrito pelas linhas de código seguintes:



//normalmente este método está associado ao clique sobre o botão Confirmar do formulário
function Validate( object sender, EventArgs args )
{
//Validate é um método auxiliar que recebe login e password e acede à base de dados
//para validar as credenciais do utilizador
if( ValidateUser( login.Text, pass.Text ) )
{
FormsAuthentication.RedirectFromLoginPage( login.Text, false );
}
}

O principal problema do código anterior recai no facto da plataforma não fornecer o suporte adequado aos programadores em duas frentes principais: em primeiro lugar, não fornece nenhuma forma simples de utilizar este mecanismo de autenticação (o programador tem de construir a base de dados e depois tem ainda de adicionar todo o código que efectua a validação das credenciais do utilizador). Em segundo lugar, não existe um único controlo relacionado com a autenticação que simplifique a vida do programador (por outras palavras, não existe nenhum controlo que permita validar o utilizador, recuperar a palavra chave, etc).


Para além disso , este processo de autenticação tem um inconveniente (que pode ser fatal): necessita da utilização de cookies!


Novidades na versão 2.0


A próxima versão da plataforma possui muitas novidades que permitem resolver os problemas apontados previamente. Antes de falarmos da nova API de validação de utilizadores (algo que irá ocupar todo o artigo), queria introduzir uma novidade associada à forma como é mantida a sessão: a partir da próxima versão, já não é necessária a utilização de cookies! O atributo cookieless (do elemento <forms>) permite a definição da forma como são mantidos os dados associados à autenticação: o valor UseCookies permite manter o mesmo tipo de utilização da versão 1.XX. Por outro lado, o valor UseUri modifica o modo de funcionamento de forma a que o authentication ticket passe a ser armazenado no URI. Para além destes existem ainda mais duas opções AutoDetect (que permite detectar se o browser suporta ou não os cookies) e UseDeviceProfile (semelhante ao anterior; contudo, a utilização deste valor obriga a que a validação do suporte seja efectuada com base nas propriedades obtidas através da classe HttpBrowserCapabilities em vez de testar directamente o browser)


O conceito de providers é extremamente interessante. Ao desenvolver a nova versão da plataforma, a equipa de desenvolvimento deparou-se com um problema de resolução não trivial: como construir um sistema que permitisse guardar os dados associados ao utilizador e que permitisse a personalização de acordo com eventuais necessidades especificas de uma determinada aplicação? Após algum tempo, chegaram à conclusão que o padrão dos providers era adequado a este tipo de situações. Este padrão consiste na utilização de várias camadas que irã produzir uma indirecção que permite obter uma abstracção interessante. Esta abstracção permite ao programador escrever todo o seu código sem ter de se preocupar em saber donde provêm os dados que está a utilizar.


Para além de um conjunto de novos controlos que permitem efectuar muitas das acções que normalmente são necessárias quando criamos ou modificamos os dados de um utilizador, a framework fornece-nos ainda uma nova API e um conjunto de providers que são capazes de automaticamente manter todos os dados associados aos utilizadores de um site.


Para percebermos melhor a filosofia associada a este padrão, convém apresentarmos um exemplo prático. Todas operações disponibilizadas pela nova API associada aos utilizadores (designada de Membership API ) utiliza esta nova filosofia para executar os seus métodos. Assim, quando executamos o método Membership.ValidateUser, estamos a executar indirectamente o método ValidateUser da classe que foi configurada como provider no ficheiro web.config. A figura seguinte ilustra este exemplo:



Este padrão revelou-se tão bom que os principais blocos que necessitam de recorrer a uma base de dados utilizam esta técnica para persistirem os dados. Até ao final deste artigo, vamos falar sobre a validação de utilizadores e respectiva API que utiliza este padrão. Nos próximos artigos iremos também estudar os novos providers associados à gestão de papeis (ou, se preferirem, roles) e de perfis de utilizadores.


Configuração do bloco de Membership


A nova plataforma fornece-nos suporte a dois tipos de providers que automaticamente efectuam a gestão dos dados dos utilizadores e persistem essa informação em base de dados Access (AccessMembershipProvider) ou Sql Server (SqlMembershipProvider). Como seria de esperar, as definições que permitem escolher o tipo de provider utilizado têm de ser efectuadas no ficheiro web.config. A versão CTP de Dezembro continua a utilizar o provider de Access por defeito. Contudo, é de esperar que a partir da próxima versão (beta 2) seja utilizado o provider SqlMembershipProvider por defeito.


A nova secção <membership> permite definir quais os providers que estão disponiveis para serem utilizados pela aplicação Web. O seguinte excerto (retirado do ficheiro machine.config.comments) permite-nos observar a informação associada aos provicers que acompanham esta versão da framework:



<membership defaultProvider="AspNetAccessProvider" userIsOnlineTimeWindow="15" >
<providers>
<add name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
passwordAttemptThreshold="5"
passwordAttemptWindow="10"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
description="Stores and retrieves membership data from the local Microsoft SQL Server database"
/>
<add name="AspNetAccessProvider"
type="System.Web.Security.AccessMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="AccessFileName"
passwordAttemptThreshold="5"
passwordAttemptWindow="10"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
description="Stores and retrieves membership data from the local Microsoft Access database file"
/>
</providers>
</membership>


A propriedade defaultProvider é utilizada para indicar qual o provider que deve ser utilizado pela aplicação. Para além dos elementos apresentados, também é possível configurar o ficheiro web.config de forma a que um dos providers definidos por defeito seja removido (através da tag <remove>). Este tipo de acção pode ser necessário se quisermos modificar os dados de um dos providers fornecidos. Nestas situações, temos de remover o elemento e adicioná-lo novamente com as configurações pretendidas (a redefinição de um provider sem ser removido resulta num erro). Como seria de esperar, também podemos adicionar os nossos providers através do elemento <add>.


Todos os providers devem respeitar o interface definido pela classe MembershipProvider. A maior parte das propriedades definidas por essa classe podem ser definidas através de atributos associados à tag <add>(verificar no exemplo anterior). As principais propriedades destes elementos são as seguintes:



  • ApplicationName: permite definir o nome da aplicação utilizado pelo provider;

  • Description: permite associar uma descrição a um provider;

  • EnablePasswordReset: indica se o provider disponibiliza a possibilidade de efectuar o reset da palavra chave. Este método influencia a evocação do método Membership.ResetPassword;

  • EnablePasswordRetrieval: indica se é possível obter a palavra chave de um utilizador. Se atribuirmos o valor false a esta propriedade então não conseguimos invocar o método Membership.GetPassword para obtermos a palavra chave de um utilizador;

  • Name: utilizado para indicar o nome do provider;

  • PasswordAttemptThreshold: serve para definirmos o número de palavras chave ou respostas incorrectas até que a conta do utilizador seja trancada (esta propriedade é nova e não existia na beta 1). Convém salientar que o número final de tentativas resulta do somatório da combinação de palavras chave e respostas falhadas (por isso, basta introduzir três vezes a palavra chave errada e duas vezes a resposta para trancarmos a conta do utilizador actual) .Como era de esperar, o número de respostas falhadas só é utilizado se a propriedade RequiresQuestionAndAnswer estiver a true;

  • PasswordAttemptWindow: como o próprio nome indica, esta propriedade é utilizada para cronometrar o espaço temporal em que são efectuadas as tentativas de login. Por outras palavras, os valores definidos na propriedade PasswordAttemptThreshold é contabilizado numa janela temporal definida por esta propriedade. Quando chegamos ao fim do tempo indicado nesta propriedade, os contadores associados à propriedade PasswordAttemptThreshold são reinicializados a zero;

  • PasswordFormat: utilizado para definirmos o formato sobre o qual as palavras chave são armazenadas na base de dados. Os valores aplicáveis a esta propriedade são provenientes da enumeração MembershipPasswordFormat: Clear, Encrypted e Hashed. Os valores possíveis são bastante claros quanto à forma como os valores são armazenados na base de dados. Convém referir que apenas conseguimos recuperar uma palavra chave de um utilizador se a opção associada a esta propriedade for diferente de Hashed. Por outro lado, se pensarmos do ponto de vista da segurança, não restam dúvidas acerca da melhor forma de armazenar a palvra chave na base de dados (terá obviamente de ser sobre este formato);

  • RequiresQuestionAndAnswer: indica se é ou não necessário introduzir uma pergunta de recuperação e respectiva resposta. A utilização desta propriedade com o valor true obriga a que a recuperação da palavra chave seja precedida da validação deste par de valores (à pergunta efectuada tem de ser dada a resposta definida aquando da criação da conta de utilizador);

  • RequiresUniqueEmail: indica se o endereço de email associado à conta de utilizador deverá ser único.


O provider de Access (AccessMembershipProvider) não utiliza as propriedades PasswordAttemptThreshold e PasswordAttemptWindow para efectuar o bloqueio das contas. Contudo, o provider de Sql (SqlMembershipProvider) já consegue utilizar essas propriedades durante a validação das contas do utilizador chegando mesmo a bloquear as contas se o utilizador ultrapassar o limite de passwords incorrectas estabelecido no ficheiro de configuração.


Se recorrermos ao provider associado ao Access, não temos de fazer nada para utilizarmos este mecanismo porque a plataforma cria automaticamente a base de dados necessária quando aceder pela primeira vez para executar uma das muitas acções possiveis. Por outro lado, a utilização de uma das ferramentas administrativas permite-nos configurar contas de uma forma muito simples e rápida.


Nota: infelizmente existe um bug associado à versão CTP de Dezembro que nos impede de utilizar estas ferramentas. Assim, a ferramenta web de administração e o novo tab (existente nas propriedades dos directórios virtuais no IIS )que nos permite efectuar configurações a partir das propriedades do site virtual disponiveis na versão beta 1 não estão disponiveis nesta versão.


Utilização da Membership API


Esta nova API permite-nos efectuar todas as operações associadas à criação/modificação/eliminação de contas de utilizadores a partir de código. A classe Membership representa o ponto de entrada para a utilização desta API. Como referimos nos parágrafos anteriores, esta classe é utilizada apenas como fachada. Isto deve-se ao facto da plataforma recorrer ao provider definido no ficheiro de configuração para efectuar as acções pedidas através da classe Membership (por outras palavras, sempre que evocamos um método da classe Membership estamos a chamar indirectamente o método associado da classe provider definida no ficheiro de configuração).


Criação de utilizadores


A criação de utilizadores é efectuada através do método CreateUser. Existem vários overloads deste método, sendo que o mais simples apenas recebe o nome de utilizador e respectiva palavra chave. Para além desta versão, existem ainda mais três que permitem receber o email, a pergunta de confirmação e respectiva resposta e ainda um outro parâmetro (do tipo MembershipCreateStatus) que é utilizado para indicar a razão de um eventual problema existente durante a criação da conta do utilizador. Todos estes métodos retornam um elemento do tipo MembershipUser que é utilizado para encapsular as propriedades associadas à conta de um utilizador.


MembershipUser: toda a informação sobre a conta de um utilizador


A classe MembershipUser é utilizada para representar os dados associados a uma conta do utilizador. Esta classe possui várias propriedades e métodos que nos permitem manipular os dados associados a uma determinada conta:



  • Comment: utilizado para associar um determinado comentário a esta conta de utilizador;

  • CreationDate: permite definir ou obter a data de criação da conta do utilizador;

  • Email: permite obter ou modificar a conta de email associada a este utilizador;

  • IsApproved: utilizado para indicar se o utilizador pode ou não fazer login;

  • IsLockedOut: esta propriedade de leitura indica se a conta do utilizador está bloqueada;

  • IsOnline: utilizado para indicar se o utilizador está online;

  • LastActivityDate: permite-nos obter a data associada à última visita efectuada a uma das páginas da aplicação;

  • LastLockoutDate: retorna a última data associada ao bloqueio da conta do utilizador;

  • LastLoginDate: permite-nos obter a data associada à última vez que o utilizador efectuou o login na aplicação;

  • LastPasswordChangeDate: indica a data em que o utilizador procedeu pela última vez à alteração da palavra chave;

  • PasswordQuestion: permite obter a pergunta de recuperação;

  • ProviderName: utilizado para obter o nome do provider que obteve os dados;

  • ProviderUserKey: Esta propriedade, que substitui a propriedade UserId existente na versão beta 1, permite obter o identificador da conta actual (este valor é especifico do provider utilizado);

  • UserName: permite-nos obter o nome associado a esta conta;


Esta classe possui também alguns métodos interessantes:



  • ChangePassword: permite alterar a palavra chave da conta;

  • ChangePasswordQuestionAndAnswer: Actualiza a pergunta de recuperação e respectiva resposta;

  • GetPassword: método que possui dois overloads que permite obter a palavra chave de uma conta de um utilizador. A recuperação de uma palavra chave depende do formato definido na propriedade PasswordFormat: se o valor desta propriedade for Hashed, então será gerada uma excepção se tentarmos utilizar este método;

  • ResetPassword: efectua o reset da palavra chave de uma conta. A escolha do método (possuí dois overloads) depende da propriedade RequiresQuestionAndAnswer associada ao provider. Ao ser gerada uma nova palavra chave, esta será enviada ao utilizador;

  • UnlockUser: método que permite desloquear uma conta de um utilizador.


Modificação dos dados da conta de um utilizador


Como é possível verificar, a modificação dos dados associados à conta de um utilizador está divida pelas duas classes apresentadas até ao momento. O método Membership.UpdateUser permite-nos modificar as principais propriedades associadas à conta de um utilizador. Este método recebe um elemento do tipo MembershipUser devidamente inicializado e é responsável por efectuar todas as alterações possiveis a essa conta de utilizador. Contudo, existem algumas propriedades que apenas podem ser modificadas através da execução de métodos especifícos da classe MembershipUser (como por exemplo, a palavra chave).


Este tipo de comportamento poderá trazer algumas dificuldades à actualização de dados associados a uma determinada conta se estivermos a pensar em utilizar um dos novos controlos de visualização para efectuarmos essas operações.


Eliminação dos dados associados a uma conta


Como seria de esperar, as eliminações de contas de utilizador estão a cargo da classe Membership. O método DeleteUser, que possui dois overloads, permite efectuarmos a eliminação dos dados associados à conta de um utilizador. O primeiro overload deste método limita-se a eliminar apenas os dados associados à conta de utilizador enquanto que o segundo apaga toda a informação associada ao utilizador.


Obtenção de utilizadores


Para além de permitir adicionar, modificar e eliminar contas de utilizadores da base de dados, a classe Membership disponibiliza ainda alguns métodos que permitem a obtenção de utilizadores:



  • FindUsersByEmail: este método retorna uma colecção de utilizadores que possuem o email indicado;

  • FindUsersByName: retorna um conjunto de utilizadores cujo nome está de acordo com o nome introduzido;

  • GetAllUsers: como é óbvio, este método retorna todos os utilizadores existentes na base de dados.


Todos os métodos anteriores possuem overloads que nos permitem obter os dados de acordo com páginas de dados.


Validação dos dados associados à conta de um utilizador


Antes de passarmos a um exemplo prático, falta falarmos um pouco sobre aquela que será a funcionalidade mais utilizada desta classe: validação das credenciais de um utilizador. A validação das credenciais de um utilizador é efectuada através da verificação da combinação nome de utilizador/palavra chave. A classe Membership possui um método designado de ValidateUser que é utilizado para efectuar esse tipo de operações.


Agora que já vimos as principais funcionalidades associadas a esta classe, está na hora de passarmos à utilização prática destes conceitos. A aplicação WebSite1 que acompanha este artigo utiliza todas as operações referidas até agora de forma a ilustrar a utilização prática destas funcionalidades. A página de validação das credenciais do utilizador recorre a este código para efectuar a validação do par nome de utilizador/palavra chave:



private void ProcessConfirmar( object sender, EventArgs args )
{
if(Membership.ValidateUser( nomeUtilizador.Text, password.Text ))
{
FormsAuthentication.SetAuthCookie( nomeUtilizador.Text, false );
Response.Redirect( "Page1.aspx" );
}

}

Como é possível observar, já não temos de escrever código para validar a informação relativa à validade dos dados introduzidos pelo utilizador; toda essa informação está encapsulada pelo provider e respectivos métodos. Para obtermos a lista de utilizadores, recorremos aos novos controlos GridView e ObjectDataSource. A conjugação destes dois controlos permite-nos não ter de recorrer a código para visualizarmos os dados associados às contas dos utilizadores. As páginas de introdução e alteração de dados foram implementadas de forma semelhante (ou seja, voltámos a nos aproveitar dos novos controlos de visualização dos dados - que jeito que eles dão!).


Utilização do provider de Sql Server


Como referimos anteriormente, a framework fornece-nos dois providers que podem ser utilizados para armazenarmos os dados das contas dos utilizadores em base de dados Access ou Sql Server. A utilização do SqlMembershipProvider é semelhante à do AccessMembershipProvider. A diferença reside na necessidade de, antes de utilizarmos o provider de Sql Server, termos de proceder à configuração da base de dados (isto não era necessário com o provider para base de dados de Access). Este tipo de configuração deve ser efectuado através do utilitário aspnet_regsql. Este utilitário pode receber vários parâmetros que permitem configurar por completo a instalação das tabelas necessárias ao suporte das várias funcionalidades (para além do suporte às funcionalidades associadas às contas de utilizadores, podemos ainda configurar outras funcionalidades, como por exemplo, o suporte a roles e a profiles). Provavelmente o melhor será executar simplesmente o utilitário (sem recorrer a estas opções) fazendo assim com que seja executado um Wizard que irá guiar-nos ao longo da instalação (a utilização deste assistente irá resultar na instalação completa de todas as funcionalidades disponiveis).


Após configurarmos a base de dados, temos de modificar o ficheiro web.config por forma a que seja utilizado o provider de Sql Server:



<connectionStrings>
<remove name="LocalSqlServer"/>
<add name="LocalSqlServer" connectionString="data source=127.0.0.1;User Id=sa; Password=proj;Initial Catalog=aspnetdb" providerName="System.Data.SqlClient"/>
</connectionStrings>

Como é possível observar, temos de remover a ligação por defeito e adicioná-la novamente com novos parâmetros (neste caso, optei por efectuar a ligação através de nome de utilizador e palavra chave). Se quiséssemos, poderíamos ter alterado a definição do provider, fazendo assim com que esse provider passasse a apontar para outra connection string.


Como foi referido anteriormente, este provider é ligeiramente mais poderoso do que o AccessMembershipProvider. Isto deve-se ao facto deste interface implementar todas as funcionalidades expostas pelo interface descrito pela classe MembershipProvider (por exemplo, esta classe utiliza os mecanismos de locking associados ao bloqueamento das contas).


Conclusões finais


Ao longo deste artigo apresentei a nova API responsável por efectuar a gestão de contas dos utilizadores. No próximo artigo, iremos apresentar os novos controlos de segurança que nos permitem automatizar ainda mais este processo. Para além disso, se houver tempo, irei apresentar ainda uma implementação personalizada de um provider para obtenção de dados.


O código que acompanha este artigo irá ser disponibilizado na secção de downloads do site (está a aguardar autorização). Por favor enviem-me as vossas opiniões/sugestões/críticas/correcções para progC@netmadeira.com.


Fiquem bem e boa programação! Até à próxima.


Leiam o meu blog em: http://weblogs.pontonetpt.com/luisabreu



Written By: labreu
Date Posted: 4/14/2006
Number of Views: 671

Return