Explorando Segurança do ASP.NET – WSAT

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.

Explorando Segurança do ASP.NET – Integração com Controles

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);
}

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);
}

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:

Propriedade Descrição
CreateUserUrl

Especifica um link até a página que faz o cadastro de um novo usuário.

DestinationPageUrl

Especifica um link para uma página que o usuário será redirecionado caso o login seja feito com sucesso.

DisplayRememberMe

Através de um valor booleano indica se um controle do tipo CheckBox será exibido. Se o usuário marcar esse CheckBox, será criado o cookie persistente no cliente, evitando assim de fazer o login toda vez que entra na aplicação.

MembershipProvider

Indica o nome do provider que o controle Login deve utilizar.

Password

Recupera a senha do usuário.

RememberMeSet

Indica se o cookie criado será ou não persistente.

Obs.: Quando a propriedade DisplayRememberMe é definida como True, a propriedade RememberMeSet é definida com o valor do controle CheckBox que é exibido ao usuário.

UserName

Recupera o login do usuário.

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>

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>

<asp:PasswordRecovery id="PasswordRecovery1" runat="server">
  <successtemplate>
    <table border="0" style="font-size:10pt;">
      <tr>
        <td>A senha foi enviada para o seu e-mail.</td>
      </tr>
    </table>
  </successtemplate>
  <mailDefinition
        From="atendimento@site.com.br
        Subject="Nova Senha"
        BodyFileName="RecuperacaoSenha.txt" />
</asp:PasswordRecovery>

Olá <% UserName %>,

A sua nova senha é <% Password %>.

Obrigado!

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:

Propriedade Descrição
DisableCreatedUser

Esta propriedade indica se o usuário recém cadastrado será desabilitado na fonte de dados e, quando isso ocorre, não é permitido que o usuário acesse a aplicação, mesmo digitando as credenciais válidas.

LoginCreatedUser

Indica se o usuário que acaba de ser criado já deve estar sendo considerado como logado. Definindo-a como False, o usuário não será autenticado logo depois de criado, ou seja, ele terá que ir até o login e lá efetuá-lo se desejar acessar a aplicação.

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.

Explorando Segurança do ASP.NET – 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:

Elemento roleManager
Atributo Tipo Valor Padrão Opcional Descrição
cacheRolesInCookie Booleano False Sim

Antes do provider ser acionado e fazer a validação na fonte de dados, tendo assim uma melhor performance.

Se estiver definido como True, uma lista contendo o nome das roles que o usuário está contido é armazenada dentro do cookie para o usuário corrente.

cookieName String “.ASPXROLES” Sim

Especifica o nome do cookie em que a lista de roles será armazenada.

cookiePath String “/” Sim

Path do cookie.

cookieProtection CookieProtection All Sim

Especifica o nível de proteção do cookie baseando-se no enumerador CookieProtection, que pode receber os seguintes valores:

  • All: Usa tanto a opção Encryption quanto Validation para proteger as informações contidas no cookie.
  • Encryption: Encripta as informações contidas no cookie.
  • None: Armazena as informações no cookie sem nenhuma proteção. A informação é armazenado em clean-text e não é validada quando é devolvida para o servidor.
  • Validation: Assegura-se que a informação não foi alterada antes de ser mandada de volta ao servidor.
cookieRequireSSL Booleano False Sim

Especifica se o cookie necessita de SSL para ser devolvido para o servidor. Se True, o cookie necessita de SSL para ser enviado para o servidor.

cookieSlidingExpiration Booleano True Sim

Especifica se o tempo de expiração do cookie será reiniciado periodicamente.

Se for definido como True, será iniciado com a data e hora corrente mais o valor definido no atributo cookieTimeout. Enquanto o usuário estiver usando a aplicação ASP.NET, o valor da data e hora do cookie é automaticamente atualizado, a menos que o tempo decorrido entre os períodos tenha sido superior ao informado na propriedade cookieTimeout.

cookieTimeout Inteiro 30 (minutos) Sim

Especifica o número de minutos antes do cookie expirar.

createPersistentCookie Booleano False Sim

Especifica se o cookie é um Session Cookie, ou seja, que é perdido quando o browser é fechado.

Quando é definido para True, o cookie é armazenado como persistente e, assim, o cookie estará visível entre múltiplas sessões do browser. A expiração do cookie é definido com a data e hora corrente mais o valor definido no atributo cookieTimeout.

defaultProvider String “AspNetSqlRoleProvider” Sim

O nome do provider que você vai utilizar. Lembrando que no elemento providers você pode especificar uma lista deles e, através do atributo defaultProvider, você especifica qual deles utilizar.

domain String   Sim

Especifica o valor da propriedade Domain do cookie.

enabled Booleano False Sim

Especifica se o gerencimento de roles está ou não habilitado.

maxCachedResults Inteiro 25 Sim

Especifica o número máximo de roles que será armazenados no cookie.

 

Elemento add – providers [ “filho” do roleManager ]
Atributo Tipo Valor Padrão Opcional Descrição
applicationName String ApplicationVirtualPath Sim

Especifica o nome da aplicação em que o Membership está sendo utilizado, possibilitando, desta forma, trabalhar com múltiplas aplicações utilizando a mesma base de dados, mas também podendo utilizar o mesmo nome da aplicação para outras aplicações ASP.NET.

Obs.: Atente-se quando você define como nome da aplicação o caracter /. Este caracter pega o nome do diretório virtual da aplicação corrente e atribui como nome da aplicação. Isso pode não refletir exatamente o seu ambiente de produção e, conseqüentemente, ter problemas em não encontrar os registros vinculados à aplicação.

commandTimeout Inteiro 30 (ADO.NET) Sim

Especifica o número em segundos para a configuração do SqlCommand. É através dele que definimos o tempo de espera entre a execução do comando e o erro.

connectionStringName String   Não

Especificamos o nome da ConnectionString que irá ser utilizada pelo provider. Esta Connection String é especificada no elemento connectionStrings.

description String   Sim

Uma breve descrição do provider.

name String   Não

Define o nome do provider, que deverá ser informado no atributo defaultProvider do elemento membership quando você quiser utilizá-lo.

type String   Não

Indica o tipo que contém a implementação deste provider, ou seja, a classe concreta que implementa a classe abstrata RoleProvider.

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>

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
}

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.

Método Descrição
AddUsersToRole

Adiciona um array de usuários em uma role específica.

Quando o provider for o SqlRoleProvider a atualização dos dados é transacionada, ou seja, se algum erro ocorrer durante o processo, como um desses usuários já estarem dentro desta role, a transação invoca o Rollback e nenhuma atualização é feita.

AddUsersToRoles

Dado um array de usuários e array de roles, este método faz com que para cada usuário do array o mesmo seja adicionado nas roles especificadas no array de roles.

Quando o provider for o SqlRoleProvider a atualização dos dados é transacionada, ou seja, se algum erro ocorrer durante o processo, como um desses usuários já estarem dentro desta role, a transação invoca o Rollback e nenhuma atualização é feita.

AddUserToRole

Adiciona um usuário em uma role específica.

AddUserToRoles

Adiciona um usuário em várias roles.

CreateRole

Cria uma nova role na fonte de dados.

DeleteCookie

Apaga o cookie onde as roles estão armazenadas.

DeleteRole

Exclui uma role da fonte de dados, retornando um valor booleano, indicando se a mesma foi ou não excluída.

Há um overload para este método onde você deve informar um parâmetro booleano indicando se uma Exception deve ou não ser disparada se houver membros relacionados com esta role e, conseqüentemente, a role não é excluída. Se for definido como False, então a Exception não é disparada e os membros (usuários) relacionados são excluídos.

FindUsersInRole

Dado uma role e um nome de usuário, o método retorna um array de strings, onde em cada elemento deste array contém o nome do usuário.

Lembrando que essa consulta é feita utilizando o operador LIKE, logo, se você passar o nome do usuário como “user” e existir na sua fonte de dados os usuários “user1”, “user2” e “user3”, eles serão retornados.

GetAllRoles

Retorna um array de strings contendo os nomes das roles contidas na fonte de dados.

GetRolesForUser

Retorna um array de strings contendo os nomes das roles em que um determinado usuário está contido.

Para este método existem dois overloads: o método que não recebe nenhum parâmetro e que retornará as roles em que o usuário que está logado no momento pertence; se optar por utilizar o outro overload, terá que passar o nome do usuário que deseja recuperar as roles em que ele está contido.

Obs.: Se o atributo cacheRolesInCookie do provider estiver definido como True, então essa verificação poderá ser feita no cache ao invés de ir até o provider.

GetUsersInRole

Dado um nome de role, é retornado um array de strings contendo os nomes de usuários que estão contidos dentro da role.

IsUserInRole

Dado um nome de uma role, este método retorna um valor booleano indicando se o usuário pertence ou não aquele role.

Para este método existem dois overloads: o método que recebe apenas como parâmetro o nome da role, que verificará se o usuário que está logado no momento pertence ou não àquela role; se optar por utilizar o outro overload, terá que passar, além do nome da role, o nome do usuário que deseja verificar se ele está ou não contido.

Obs.: Se o atributo cacheRolesInCookie do provider estiver definido como True, então essa verificação poderá ser feita no cache ao invés de ir até o provider.

RemoveUserFromRole

Dado um nome de role e nome de usuário, o usuário é removido desta role.

RemoveUserFromRoles

Dado um array de roles e nome de usuário, o usuário é removido destas roles.

Quando o provider for o SqlRoleProvider a atualização dos dados é transacionada, ou seja, se algum erro ocorrer durante o processo, como um desses usuários já estarem dentro desta role, a transação invoca o Rollback e nenhuma atualização é feita.

RemoveUsersFromRole

Dado um array de nomes de usuário e um nome de role, os usuários são removidos desta role.

Quando o provider for o SqlRoleProvider, a atualização dos dados é transacionada, ou seja, se algum erro ocorrer durante o processo, como um desses usuários já estarem dentro desta role, a transação invoca o Rollback e nenhuma atualização é feita.

RemoveUsersFromRoles

Dado um array de nomes de usuário e um array de nomes de usuários, os usuários são removidos destas roles.

Quando o provider for o SqlRoleProvider, a atualização dos dados é transacionada, ou seja, se algum erro ocorrer durante o processo, como um desses usuários já estarem dentro desta role, a transação invoca o Rollback e nenhuma atualização é feita.

RoleExists

Dado um nome de uma role, este método retorna um valor booleano indicando se o mesmo já existe ou não dentro da fonte 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");

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>

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>

Explorando Segurança do ASP.NET – 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
}

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:

Elemento membership
Atributo Tipo Valor Padrão Opcional Descrição
defaultProvider String AspNetSqlProvider Sim

O nome do provider que você vai utilizar. Lembrando que no elemento providers você pode especificar uma lista deles e é através do defaultProvider que você especifica qual deles utilizar.

userIsOnlineTimeWindow Inteiro 15 Sim

Define o tempo em que usuário é considerado como online, que é utilizado para calcular o número aproximado de usuários online.

hashAlgorithmType String SHA1 Sim

Utilizado para especificar o algoritmo de hashing que será usado pelo provider, sempre quando o mesmo precisar efetuar hashing em dados sigilosos.

 

Elemento add – providers [ “filho” do membership ]
Atributo Tipo Valor Padrão Opcional Descrição
applicationName String ApplicationVirtualPath Sim

Especifica o nome da aplicação em que o Membership está sendo utilizado, possibilitando desta forma, você trabalhar com múltiplas aplicações utilizando a mesma base de dados, mas também podendo utilizar o mesmo nome da aplicação para outras aplicações ASP.NET.

O provider que faz o uso do ActiveDirectory ignora esta propriedade.

Obs.: Atente-se quando você define como nome da aplicação o caracter /. Este caracter pega o nome do diretório virtual da aplicação corrente e atribui como nome da aplicação. Isso pode não refletir exatamente o seu ambiente de produção e, conseqüentemente, ter problemas em não encontrar os registros vinculados à aplicação.

commandTimeout Inteiro 30 (ADO.NET) Sim

Especifica o número em segundos para a configuração do SqlCommand. É através dele que definimos o tempo de espera entre a execução do comando e o erro.

connectionStringName String   Não

Especifica o nome da ConnectionString que irá ser utilizada pelo provider. Esta Connection String é especificada no elemento connectionStrings.

description String   Sim

Uma breve descrição do provider.

enablePasswordRetrieval Boolean False Sim

Especifica se será ou não permitido a recuperação do password do usuário. Se True, o provider permitirá a recuperação.

enablePasswordReset Boolean True Sim

Especifica se será ou não permitido reinicialização da senha do usuário. Se True, o provider permitirá a reinicialização.

maxInvalidPasswordAttempts Inteiro 5 Sim

Define o número de tentativas de acesso até que a conta do usuário seja bloqueada. O resultado dá-se da soma de tentativas inválidas de recuperação da palavra-chave ou senha.

minRequiredNonalphanumericCharacters Inteiro 1 Sim

Define o número de caracteres especiais que a senha deverá conter, não podendo ser definido com um valor menor que 0 ou maior que 128 ou ainda maior que o valor estipulado no atributo minRequiredPasswordLength.

minRequiredPasswordLength Inteiro 1 Sim

Define o número mínimo de caracteres para a senha. Também não pode ser definido com um valor menor que 0 ou maior que 128.

name String   Não

Define o nome do provider, que deverá ser informado no atributo defaultProvider do elemento membership quando você quiser utilizá-lo.

passwordAttemptWindow Inteiro 10 Sim

Define o intervalo de tempo em que é efetuada a contabilidade de tentativas de login falhadas. Quando chegamos ao fim do intervalo especificado neste atributo e o número de tentativas falhadas não atingiu o valor estipulado no atributo maxInvalidPasswordAttempts, os contadores são reinicializados.

passwordFormat String Hashed Sim

Define o formato de armazenamento de senhas na fonte de dados. Os possíveis valores para este atributo estão definidos no enumerador MembershipPasswordFormat e são exibidos abaixo:

  • Clear: As senhas não são criptografadas.
  • Encrypted: Senhas são criptografadas usando as configurações especificadas no elemento de configuração machineKey.
  • Hashed: As senhas são criptografadas utilizando o algoritmo de hash SHA1.

Obs.: Como algoritmos hash são irreversíveis, somente conseguiremos recuperar a senha se este atributo for diferente de Hashed.

passwordStrengthRegularExpression String   Sim

Especifica uma expressão regular para validar uma senha e esta expressão deverá funcionar com a classe RegEx.

requiresQuestionAndAnswer Boolean True Sim

Indica se é ou não necessário informar uma pergunta e sua respectiva palavra-chave para que isso funcione como uma espécie de “lembrete” para o usuário.

requiresUniqueEmail Boolean True Sim

Especifica se o endereço de e-mail do usuário deve ser único.

type String   Não

Indica o tipo que contém a implementação deste provider, ou seja, a classe concreta que implementa a classe abstrata MembershipProvider.

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>

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.

Método Descrição
CreateUser

Adiciona um novo usuário na base de dados. Este método é sobrecarregado.

Como o próprio nome diz, ele adiciona um novo usuário na base de dados e retorna um objeto do tipo MembershipUser (veremos sobre ele mais abaixo) se o usuário for criado com sucesso. Se, por algum motivo, a criação do usuário falhar, uma Exception do tipo MembershipCreateUserException é atirada. Além disso, você também pode, através de um parâmetro de saída do tipo MembershipCreateStatus, recuperar o motivo pelo qual a criação falhou.

DeleteUser

Exclui um usuário na base de dados dado um nome de usuário. Este método é sobrecarregado.

Este método retorna um parâmetro booleano, indicando se o usuário foi ou não excluído com sucesso. Também é possível passar como parâmetro para este método um valor booleano indicando se os dados relacionados com este usuário serão também excluídos. O padrão é True.

FindUsersByEmail

Dado um e-mail para ser procurado, o método retornará (paginando) uma coleção do tipo MembershipUserCollection, onde cada elemento desta coleção é do tipo MembershipUser.

O SQL Server executa a busca utilizando o operador LIKE e você pode passar para o mesmo os caracteres de busca para que possa ser analisado de acordo com a sua necessidade. Lembrando também que há um overload para este método onde você pode especificar os parâmetros pageIndex, pageSize e totalRecords para retornar os dados paginados para a sua aplicação e ter uma melhor performance se houver uma quantidade considerável de registros na base de dados.

FindUsersByName

Dado um nome de usuário para ser procurado, o método retornará (paginando) uma coleção do tipo MembershipUserCollection, onde cada elemento desta coleção é do tipo MembershipUser.

O SQL Server executa a busca utilizando o operador LIKE e você pode passar para o mesmo os caracteres de busca para que possa ser analisado de acordo com a sua necessidade. Lembrando também que há um overload para este método que você pode especificar os parâmetros pageIndex, pageSize e totalRecords para retornar os dados paginados para a sua aplicação e, ter uma melhor performance se houver uma quantidade considerável de registros na base de dados.

GeneratePassword

Gera uma senha randômica dado um tamanho máximo de caracteres e o número de caracteres não alfanuméricos.

Este método é comumente utilizado para gerar senhas randômicas e também é chamado pelo método ResetPassword para reinicializar a senha do usuário, ou seja, criar uma espécie de “senha temporária”. Se existir no mínimo um caracter alfanumérico, eles não poderão ser caracteres não “imprimíveis” ou caracteres ocultos. Eis aqui as possibilidades: !@#$%^&*()_-+=[{]};:<>|./?

GetAllUsers

O método retornará (paginando) uma coleção do tipo MembershipUserCollection contendo todos os usuários da base de dados, onde cada elemento desta coleção é do tipo MembershipUser.

Lembrando também que há um overload para este método que você pode especificar os parâmetros pageIndex, pageSize e totalRecords para retornar os dados paginados para a sua aplicação e, ter uma melhor performance se houver uma quantidade considerável de registros na base de dados.

GetNumberOfUsersOnline

Retorna o número de usuários online para a aplicação corrente, ou seja, a qual está definida na configuração do provider.

O critério para o usuário ser considerado como online é a data da última atividade ser maior que a hora corrente menos o valor especificado no atributo UserIsOnlineTimeWindow. A data da última atividade é atualizada para a data corrente quando as credenciais do usuário são passadas para o método ValidateUser ou UpdateUser ou quando você chama o método GetUser ou o mesmo método sobrecarregado, passando o parâmetro userIsOnline como True.

GetUser

Retorna um objeto do tipo MembershipUser que representa um usuário.

Se chamar o método GetUser sem nenhum parâmetro, o método retornará o usuário logado. Se optar por passar o nome do usuário para este mesmo método, mas utilizando uma sobrecarga dele, ele retornará os dados deste usuário especificado.

GetUserNameByEmail

Retorna um objeto do tipo MembershipUser baseando-se em um e-mail informado como parâmetro.

UpdateUser

Atualiza na base de dados as informações de um determinado usuário.

Esse método recebe um objeto do tipo MembershipUser. Este objeto pode ser um que recuperamos anteriormente da base de dados ou até mesmo um novo que acabamos de instanciar e definir seus valores.

ValidateUser

Dado um nome de usuário e senha, este método retorna um valor booleano indicando se é ou não um usuário válido.

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.

Propriedade Descrição
Comment

Comentários adicionais sobre o usuário.

CreationDate

Data/hora em que o usuário foi criado dentro da base de dados.

Email

E-mail do usuário.

IsApproved

Indica se o usuário pode ou não se autenticar.

Mesmo informando o nome de usuário e senha válidos e esta propriedade estiver definida como False, o método ValidateUser retornará False.

IsLockedOut

Indica se o usuário está barrado devido a tentativas incorretas de acessar a aplicação.

IsOnline

Indica se o usuário está ou não online.

LastActivityDate

Recupera a data/hora do último acesso/atividade dentro da aplicação.

LastLockoutDate

Recupera a data/hora do último vez que esse usuário foi barrado.

LastLoginDate

Recupera a data/hora da última vez em que o usuário fez o login com sucesso na aplicação.

LastPasswordChangedDate

Recupera a data/hora da última vez em que o usuário alterou a sua senha.

PasswordQuestion

Recupera uma determinada questão para servir como “lembrete” da senha do usuário.

ProviderName

Recupera o nome do provider que o mesmo está utilizando.

ProviderUserKey

Recupera o identificador do usuário dentro da base de dados.

Na base de dados, o identificador é criado e armazenado como um tipo de dado chamado uniqueidentifier. Como a propriedade ProviderUserKey retorna um Object se quiser ter algo mais tipado, terá que convertê-lo para o objeto Guid, que corresponde ao tipo de dado uniqueidentifier da base de dados.

UserName

Recupera o nome do usuário.

 

Método Descrição
ChangePassword

Altera a senha do usuário na base de dados.

Este método recebe dois parâmetros: senha atual e a nova senha e, retorna um valor booleano indicando se a alterção aconteceu com sucesso. Vale lembrar que as senhas informadas neste método devem estar de acordo com as especificações informadas na configuração do provider no arquivo Web.Config.

ChangePasswordQuestionAndAnswer

Altera a pergunta e resposta que ajudam o usuário a lembrar a senha.

GetPassword

Recupera a senha de um determinado usuário.

Se a propriedade EnablePasswordRetrieval estiver definida como False, uma Exception será atirada. Se o atributo passwordFormat do provider estiver definido como Hashed, a senha antiga não será recuperada, mas uma nova senha será gerada e retornada através do método ResetPassword.

ResetPassword

Reinicializa a senha do usuário e a retorna ao chamador.

Se o atributo enablePasswordReset estiver definido como False e o método ResetPassword for chamado, uma Exception será atirada. Agora, se o atributo requiresQuestionAndAnswer estiver definido como True, você pode utilizar o método ResetPassword, desde que utilize a sobrecarga do mesmo, onde você deve fornecer a palavra-chave para o usuário.

UnlockUser

Desbloqueia um usuário para o mesmo poder voltar a acessar a aplicação. Um valor booleano é retornado indicando se o desbloqueio foi ou não realizado com sucesso.

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();

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.

 

Explorando Segurança do ASP.NET – AJAX

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:

Método Descrição
validateUser

Este método recebe dois parâmetros: login e senha, e se encarrega de validar junto ao Membership padrão que encontra-se configurado para a aplicação, se as credenciais informadas são ou não válidas, retornando um valor booleano com essa indicação.

login

Este método possui basicamente a mesma funcionalidade que o validateUser, mas com uma diferença: além de validar as credenciais e estas estiverem válidas, ele já devolve para o browser o cookie de autenticação especificado no FormsAuthentication. É importante dizer que este método também recebe um parâmetro booleano que indica se o cookie será ou não persistente.

logout Efetua a saída do usuário, limpando o cookie.

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" />

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" 
            style="visibility:hidden;color:Green" 
            visible="false">
            <b>Logado.</b>
        </span> 
        <span 
            id="TextoNaoLogado" 
            style="visibility:visible;color:Red">
            <b>Não logado.</b>
        </span>
        <br /><br />        
        
        Login: <input type="text" id="txtLogin" />
        <br />
        Senha: <input type="password" id="txtSenha" />
        <br /><br />
        <button id="btnEnviar" onclick="OnSubmitLogin()">Efetuar Login</button>

        
            var login = document.getElementById('txtLogin');
            var senha = document.getElementById('txtSenha');
            var lblLogado = document.getElementById('TextoLogado');
            var lblNaoLogado = document.getElementById('TextoNaoLogado');
            var buttonLoginLogout = document.getElementById('btnEnviar');            
                
            function OnSubmitLogin() { 
                Sys.Services.AuthenticationService.login(
                    login.value, 
                    senha.value, 
                    false, 
                    OnLoginComplete); 
                    
                return false;
            }
            
            function OnLoginComplete(result) {                
                senha.value = '';
                if (result) {
                    login.value = '';
                    lblLogado.style.visibility = "visible";
                    lblNaoLogado.style.visibility = "hidden";  
                   
                    buttonLoginLogout.innerText = "Efetuar Logout";         
                    buttonLoginLogout.onclick = OnSubmitLogout;
                }
                else {
                    lblLogado.style.visibility = "hidden";
                    lblNaoLogado.style.visibility = "visible"; 
                }
            }
           
            function OnSubmitLogout() {  
                Sys.Services.AuthenticationService.logout(OnLogoutComplete); 
                return false;
            }
           
            function OnLogoutComplete(result) {
                lblLogado.style.visibility = "hidden";
                lblNaoLogado.style.visibility = "visible";  
                buttonLoginLogout.innerText = "Efetuar Login";  
                buttonLoginLogout.onclick = OnSubmitLogin;
            }
        
   </form>
</body>
</html>

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.

Explorando Segurança do ASP.NET – Arquitetura

O 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:

Funcionalidade Classe Base Classes Concretas Descrição
Membership MembershipProvider SqlMembershipProvider
ActiveDirectoryMembershipProvider
Responsável por gerenciar os usuários de uma aplicação ASP.NET.
Roles RoleProvider SqlRoleProvider
WindowsTokenRoleProvider
AuthorizationStoreRoleProvider
Utilizado para gerir os papéis dos usuários dentro da aplicação ASP.NET.

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.NETFrameworkv2.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:

Parâmetro Descrição
-? Exibe o Help do utilitário.
-W Roda o utilitário em modo Wizard. Esse é o padrão caso nenhum outro parâmetro seja informado. Com isso, você terá um wizard (que é mostrado abaixo) para acompanhá-lo durante o processo.

-C Especifica a ConnectionString para o servidor onde o banco de dados SQL Server está instalado. Se desejar, você pode informar isso separadamente (mostrado mais abaixo).
-S Nome do servidor onde o SQL Server está.
-U Login de acesso dentro do SQL Server (é necessário informar o password ou Integrated Security).
-P Password para o Login especificado através do parâmetro -U.
-E Autentica o usuário com as credenciais do usuário corrente logado no Windows.
-sqlexportonly Usado para criar um arquivo de script para adicionar ou remover tais funcionalidades.
-A all|m|r|p|c|w Adiciona a infraestrutura dentro da base de dados para uma determinada funcionalidade. Além do -A, ainda há o parâmetro complementar onde você deve informar qual será a funcionalidade que deseja adicionar:

all – Todas
m – Membership
r – Role management
p – Profile
c – Web Parts personalization
w – Web events

-R all|m|r|p|c|w Remove a infraestrutura dentro da base de dados para uma determinada funcionalidade. Além do -R, ainda há o parâmetro complementar onde você deve informar qual será a funcionalidade que deseja remover:

all – Todas
m – Membership
r – Role management
p – Profile
c – Web Parts personalization
w – Web events

Abaixo é mostrado (através do prompt de comando do Visual Studio .NET 2005) alguns exemplos do uso do utilitário aspnet_regsql.exe:

C:aspnet_regsql.exe -S localhost -U NomeUsuario -P P@$$w0rd -d BancoDados -A mrpw
C:aspnet_regsql.exe -S localhost -E -d BancoDados -A all
C:aspnet_regsql.exe -S localhost -E -d BancoDados -R all

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.