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 – 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 – 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 – 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.

Microsoft Anti-Cross Site Scripting Library V1.0

Há poucos dias atrás, a Microsoft lançou o “Anti-Cross Site Scripting Library V1.0”. Trata-se de uma biblioteca que substitui os métodos HttpUtility.HtmlEncode e HttpUtility.UrlEncode e, com a mesma finalidade, de evitar ataques XSS nas aplicações Web/ASP.NET.

A diferença é que com esta nova biblioteca, os métodos trabalham com os caracteres permitidos “white-listing” ao invés dos caracteres não permitidos “black-listing”, como é o caso dos métodos da classe HttpUtility. Isso fará com que, ao chamar os métodos, o que não estiver contido dentro deste “conjunto” de caracteres válidos, será codificado. Para maiores informações a respeito, façam o download e analisem o documento que vem junto com o pacote.

Encriptando seções do Web.Config

No ASP.NET 2.0, temos uma nova versão do utilitário de linha de comando aspnet_regiis.exe. Consequentemente temos novas funcionalidades adicionadas a ele e, uma delas, é a possibilidade de encriptarmos seções do arquivo Web.Config de uma determinada aplicação ASP.NET. A sua sintaxe para uso é simples:

C:>aspnet_regiis -prov DataProtectionConfigurationProvider -pef appSettings C:WebSite1

A seguir a explicação dos parametros:

-prov: indica o provider que quer utilizar para encriptar a seção.
-pef + seção + diretório físico: indica a seção do arquivo Web.Config que será encriptada, fornecendo em seguida, o caminho físico do diretório da aplicação.

Construindo uma Área Restrita

Sempre que construímos uma aplicação Web, é muito comum criarmos áreas administrativas e restritas, onde os usuários poderão gerenciar o conteúdo das páginas, pedidos, clientes, entre muitas outras coisas. Neste artigo explicarei como fazermos para criarmos uma área restrita em sua aplicação.

Devemos criar uma aplicação ASP.NET no Visual Studio. Inicialmente, criaremos uma pasta chamada “Administracao” onde ficaram contidas as páginas restritas. Dentro dela uma página “Default.aspx” e fora dela, devemos ter outro WebForm que será responsável para que usuário faça o Login. Esta página chamará “Login.aspx”. A estrutura de arquivos de nosso projeto ficará algo como:

Figura 1 – Estrutura de Arquivos da Aplicação.

Depois disso, devemos informar qual será a página de “Start”. Para isso informaremos que a página inicial será a “Default.aspx”, dentro da pasta “Administracao”. Explico: Quando a aplicação é iniciada, a página “Default.aspx” será chamada, ao chamá-la será verificado se o usuário corrente encontra-se ou não logado. Caso não esteja, ele será redirecionado para a página “Login.aspx”, onde ele será obrigado à se autenticar.

Figura 2 – Definindo a página inicial.

Bem, antes de mais nada, devemos construir a página “Login.aspx”, onde devemos ter o formulário para que o usuário possa digitar seu Login e Senha para se identificar. O formulário ficará da seguinte forma:

Figura 3 – Página de Login.

Em primeiro lugar, quando iniciamos a aplicação, reparem que ele requisitou a página “Default.aspx” dentro do diretório “Administracao”. Como foi verificado que o Usuário não está logado, fui direcionado para a página de Login (“Login.aspx”) e anexado na URL uma QueryString chamada “ReturnURL”, que será a página que o usuário será redirecionado após efetuar a Login (caso seja válido). Outro ponto que gostaria de chamar a atenção é para o CheckBox “Lembrar”. Ele serve para definirmos se o Cookie será ou não persistente, ou seja, se quando o usuário fechar o Browser e abri-lo novamente, já ficará ou não autenticado.

Antes de codificarmos o evento Click do botão de Login, vamos ver as configurações que devemos fazer no arquivo Web.Config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
<?xml version=“1.0” encoding=”utf-8″ ?>
<configuration>
    <system.web>
        <authentication mode=”Forms”>
            <forms name=”Administracao” loginUrl=“Login.aspx” />
        </authentication>
    </system.web>
    <location path=”Administracao”>
        <system.web>
            <authorization>
                <deny users=”?” />
            </authorization>
        </system.web>
    </location>
</configuration>
 
Código 1 – Configurações no arquivo Web.Config.

Informamos no arquivo Web.Config a página que será efetuado o Login e também informamos que a pasta “Administração” será restrita. A partir disso o usuário deverá efetuar o Login antes de visualizar o conteúdo da pasta “Administracao”.

Agora vamos criar uma tabela no SQL Server chamada “Administradores” onde armazenará o cadastro dos Administradores do Sistema. Depois de criada, no evento Click do Botão de Login, devemos ir até o SQL Server e verificar se esse usuário existe na Base de Dados. Caso positivo, ele será redirecionado para a página que encontra-se na QueryString “ReturnURL”, caso contrário, não podemos permitir o acesso.

A Tabela Administradores terá apenas o seguintes campos: Nome (Varchar(50)), Login(Varchar(10)) e Senha(Varchar(6)). E o evento Click ficará da seguinte forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
Imports System.Web.Security
Imports System.Data.SqlClient
 
Private Sub btnLogin_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles btnLogin.Click
 
    Dim conn As SqlConnection = New SqlConnection(“CONNECTION_STRING”)
    Dim strQuery As String
    Dim strRetorno As String
    strQuery = “SELECT Nome FROM Administradores “
    strQuery &= “WHERE Login = @Login AND Senha = @Senha”
    Dim cmd As SqlCommand = New SqlCommand(strQuery, conn)
 
    Dim Login As SqlParameter = New SqlParameter(“@Login”, SqlDbType.VarChar, 10)
    Login.Value = Me.txtLogin.Text.Trim
    cmd.Parameters.Add(Login)
 
    Dim Senha As SqlParameter = New SqlParameter(“@Senha”, SqlDbType.VarChar, 6)
    Senha.Value = Me.txtSenha.Text.Trim
    cmd.Parameters.Add(Senha)
 
    Try
        conn.Open()
        strRetorno = Convert.ToString(cmd.ExecuteScalar())
        conn.Close()
    Catch ex As Exception
        Response.Write(“Ocorreu uma falha.”)
    Finally
        If Not strRetorno = String.Empty Then
            FormsAuthentication.RedirectFromLoginPage(Me.txtLogin.Text.Trim, _
                Me.chkLembrar.Checked)
        Else
            Me.lblMensagem.Visible = True
        End If
    End Try
 
End Sub
 
Código 2 – Validando o Usuário.

Como podem ver, em primeiro lugar importamos os Namespaces necessários, no caso: System.Data.SqlClient e System.Web.Security. Depois, abrimos conexão com a Base de Dados e através de uma query, passamos como parâmetros o Login e Senha, verificamos se existe algum registro que atenda à essa condição. Caso exista, o usuário é redirecionado para a página “Default.aspx” dentro da pasta “Administracao”, caso contrário uma mensagem será exibida para ele, informando que o acesso foi negado. Lembrando que o CheckBox é passado como o segundo parâmetro para a Sub RedirectFromLoginPage (Linha 31), informando se é ou não para manter o Cookie persistente.

Importante: Não há necessidade de nos preocuparmos em tratarmos os apóstrofos “‘” e ponto-e-vírgula “;” para aqueles que tentarem a “SQL Injection”, pois quando trabalhamos com Parameters, eles ficam encarregados de tratarem isso automaticamente.

Depois de validado, somos direcionados para a página requisitada anteriormente. Veja abaixo:

Figura 4 – Página restrita sendo exibida.

Como passamos para a Sub RedirectFromLoginPage o Login do Usuário (Linha 30 do Código 2), é possível recuperarmos isso durante a sua sessão. Para isso pode fazer:

1
2
3
4
5
6
7
8
 
Imports System.Web.Security
 
Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
 
    Me.lblMensagem.Text = “Bem Vindo à Área Restrita ” & _
        HttpContext.Current.User.Identity.Name
End Sub
 
Código 3 – Podemos ver na Linha 5, como recuperar o Login do Usuário.

O que nos restou agora é fazermos o “Logout” do Usuário. Para isso, utilizaremos o LinkButton “Logout” e assim, ao clicar, a sessão do usuário será finalizada. Veja o exemplo abaixo:

9
10
11
12
13
14
 
Private Sub lnkLogout_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles lnkLogout.Click
 
    FormsAuthentication.SignOut()
    Response.Redirect(“Default.aspx”, True)
End Sub
 
Código 4 – Efetuando o Logout do Usuário.

Utilizamos a Sub SignOut da classe FormsAuthentication para finalizar a sessão do Usuário, e assim ele será novamente redirecionado para a página de Login (“Login.aspx”).

Conclusão: Com o ASP.NET a segurança em nossas aplicações Web ficaram bem mais fáceis de serem implementadas e bem mais seguras. Este artigo explica a parte superficial. Há muitas “features” que possam ser utilizadas para garantirmos o máximo de segurança possível, mas é claro que tudo dependerá de cada Aplicação.

ASP.NET Profile – Parte 1

Neste artigo apresentarei um overview do novo objeto do ASP.NET 2.0, chamado Profile qual tem a finalidade específica de armazenar informações de um determinado usuário em seu contexto.

O Profile é bem semelhante ao objeto Session atual, onde para cada usuário que temos em nossa aplicação Web é criado um Profile para ele. Até então funciona da mesma forma que o objeto Session. Mas o mais interessante está por vir, ou seja, quando fechamos o browser, a Session é perdida. Já utilizando o Profile ele se mantém persistente mesmo depois do usuário fechar o browser.

Isso se deve graças ao uso, por default, do Microsoft Access, onde são armazenadas estas informações, que fica dentro de uma pasta chamada Data, dentro da sua aplicação. Vale lembrar que é perfeitamente possível o uso de uma outra Base de Dados para termos uma melhor performance, como por exemplo SQL Server ou Oracle. Veremos este tópico um pouco mais adiante, em um futuro artigo.

Além da vantagem de se manter persistente mesmo depois de fechado o Browser, o Profile ainda tem uma vantagem, que particularmente considero fantástica, que é fortemente tipado (strongly typed), ao contrário da Session, que por sua vez aceitava um Object. Além disso, o IntelliSense já reconhece as propriedades, tornando assim o desenvolvimento mais rápido e menos propício a erros.

Definindo o Profile

Você deve utilizar o arquivo Web.Config para gerar a estrutura que o teu objeto Profile irá ter. Temos à nossa disposição o elemento profile, onde definimos as propriedades que vamos disponibilizar. O cenário é termos dentro do Profile, o Nome e Email do Usuário. Abaixo o código do arquivo Web.Config que define o Profile:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
<configuration>
    <system.web>
        <authentication mode=”Forms” />
        <anonymousIdentification enabled=”true” />
 
        <profile>
            <properties>
                <add name=”Nome” defaultValue=”” allowAnonymous=”true” />
                <add name=”Email” defaultValue=”” allowAnonymous=”true” />
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 1 – Definindo a estrutura do Profile no arquivo Web.Config.

Analisando o código acima, vemos o elemento anonymousIdentification que especificará que o Profile será criado para usuários anônimos ou autenticados. Se definí-lo com False e tentar atribuir algum valor as propriedades em runtime e o usuário não estiver autenticado, uma Exception será lançada. Já o atributo allowAnonymous é requerido quando as propriedades são usadas com usuários anônimos.

O código para atribuir um valor as propriedades, fica da seguinte forma:

1
2
 
Profile.Email = “israel@projetando.net”
Profile.Nome = “Israel Aéce”
 
Código 2 – Acessando as propriedades do Profile.

Como já podemos ver na Figura 1 logo abaixo, o Intellisense já passa a interpretar as propriedades que definimos no Web.Config:

Figura 1 – Intellisense já reconhecendo as propriedades definidas no Web.Config.

Repare que o tipo do dado não é definido. Isso porque o default, quando não informamos é System.String. Caso queria definir um tipo Inteiro ou qualquer outro tipo, tem o atributo type, onde define-se o tipo de dado que queira para aquela propriedade.

Profile Groups

Quando começamos a criar várias propriedades dentro do arquivo Web.Config para utilizarmos no Profile, começamos a ter a necessidade de separar algumas delas em grupos para que facilite a compreensão e organização. Para isso temos o elemento group, onde dentro dele colocamos as propriedades pertinentes ao mesmo. Se quisermos adicionar um grupo chamando Endereco, contendo Rua, Cidade, Estado, fazemos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
<configuration>
    <system.web>
        <authentication mode=”Forms” />
        <anonymousIdentification enabled=”true” />
 
        <profile>
            <properties>
                <add name=”Nome” defaultValue=”” allowAnonymous=”true” />
                <add name=”Email” defaultValue=”” allowAnonymous=”true” />
                <group name=”Endereco” />
                    <add name=”Rua” defaultValue=”” allowAnonymous=”true” />
                    <add name=”Cidade” defaultValue=”” allowAnonymous=”true” />
                </group>
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 3 – Definindo grupos do Profile no arquivo Web.Config.

E para acessá-los, o código fica:

1
2
3
4
 
Profile.Email = “israel@projetando.net”
Profile.Nome = “Israel Aéce”
Profile.Endereco.Rua = “Magnólias, das”
Profile.Endereco.Cidade = “Valinhos”
 
Código 4 – Acessando as propriedades com grupos do Profile.

Tipos Complexos

Muitas vezes, criamos objetos complexos que encapsulam alguma lógica e funcionalidades específicas. Um exemplo prático disso é quando criamos um objeto do tipo Carrinho de Compras para aplicações de comércio eletrônico. Neste caso, o ideal é colocarmos este tipo de objeto no Profile e assim fazer o uso destas funcionalidades. O Profile permite definir estes tipos complexos para ser gerenciado/armazenado, como por exemplo uma classe.

Para exemplificar como isso funciona realmente, criaremos uma classe (que é um tipo complexo) onde teremos um objeto que será o Carrinho de Compras do nosso Cliente. Esta classe armazenará os produtos que o cliente desejar incluir no Carrinho além de nos retornar o valor total do produtos e claro, podemos dar muito mais funcionalidades a ela se desejarmos. Primeiramente o código da classe que contém as propriedades para a estrutura do Produto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
<Serializable()> Public Class Produto
 
    Private _nome As String
    Private _valor As Double
    Private _qtde As Integer
 
    Public Property Nome() As String
        Get
            Return Me._nome
        End Get
        Set(ByVal value As String)
            Me._nome = value
        End Set
    End Property
 
    Public Property Valor() As Double
        Get
            Return Me._valor
        End Get
        Set(ByVal value As Double)
            Me._valor = value
        End Set
    End Property
 
    Public Property Qtde() As Integer
        Get
            Return Me._qtde
        End Get
        Set(ByVal value As Integer)
            Me._qtde = value
        End Set
    End Property
 
End Class
 
Código 5 – Classe Produto.

Depois de criada a classe que tem a estrutura do nosso Produto, criaremos a seguir a classe que conterá as funcionalidades do nosso Carrinho de Compras, incluindo dentro dela também a capacidade de armazenar os produtos que são adicionados ao carrinho pelo usuário:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
<Serializable()> Public Class CarrinhoCompras
 
    Private _items As New Generic.List(Of Produto)
 
    Public ReadOnly Property Total() As Double
        Get
            Dim tot As Double
            For Each prod As Produto In _items
                tot += prod.Valor * prod.Qtde
            Next
            Return tot
        End Get
    End Property
 
    Public ReadOnly Property Produtos() As Generic.List(Of Produto)
        Get
            Return Me._items
        End Get
    End Property
 
    Public Sub AdicionarProduto(ByVal p As Produto)
        Me._items.Add(p)
    End Sub
 
End Class
 
Código 6 – Carrinho de Compras.

Como podemos ver, é um código simples, onde temos um método chamado AdicionarProduto que é responsável por adicionar um novo produto a coleção de produtos. Uma propriedade que nos retorna o valor total dos Produtos e outra para resgatar a coleção de produtos que o usuário selecionou.

O que é importante reparar é que as duas classes necessitam do atributo Serializable, que se faz necessário para o armazenamento/persistência do objeto pelo Profile.

Da mesma forma que criamos, um pouco mais acima, as simples propriedades e seus grupos no arquivo Web.Config, devemos fazer o mesmo neste caso de tipos complexos. A forma com qual é declarada muda um pouco, tendo que definir alguns atributos como é mostrado abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
<configuration>
    <system.web>
        <authentication mode=”Forms” />
 
        <anonymousIdentification enabled=”true” />
 
        <profile>
            <properties>
                <add
                        name=”CarrinhoCompras”
                        type=”CarrinhoCompras”
                        serializeAs=”Binary”
                        allowAnonymous=”true” />
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 7 – Definindo tipos complexos no Web.Config.

O que notamos de novidade na declaração de tipos complexos no Web.Config é o elemento serializeAs que define qual o tipo de serialização do objeto. Feito isso, agora já poderemos utilizar o nosso Carrinho de Compras pelo site, apenas tendo o cuidado de, quando for utilizá-lo pela primeira vez, instanciá-lo para poder inicializá-lo. Veremos abaixo um simples exemplo de como adicionar novos produtos ao Carrinho e como exibir o seu conteúdo em um controle ASP.NET qualquer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    If (Profile.CarrinhoCompras Is Nothing) Then
        Profile.CarrinhoCompras = New CarrinhoCompras
    End If
 
    Dim p As New Produto
    p.Nome = “Produto 1”
    p.Qtde = 2
    p.Valor = 2.9
    Profile.CarrinhoCompras.AdicionarProduto(p)
 
    Dim p1 As New Produto
    p1.Nome = “Produto 44”
    p1.Qtde = 3
    p1.Valor = 14
    Profile.CarrinhoCompras.AdicionarProduto(p1)
 
    Me.Label1.Text = “Total: ” & Profile.CarrinhoCompras.Total.ToString(“C2”)
    Me.GridView1.DataSource = Profile.CarrinhoCompras.Produtos
    Me.GridView1.DataBind()
End Sub
 
Código 8 – Utilizando o Carrinho de Compras.

Como vemos, verificamos se existe alguma instância do nosso objeto CarrinhoCompras em nosso Profile. Em seguida, criamos dois produtos e adicionamos neste carrinho. Através da propriedade Produtos resgatamos todos os produtos que estão dentro do Carrinho e preenchemos nosso controle GridView e um Label com a propriedade Total, assim como é ilustrado na figura abaixo:

Figura 2 – Carrinho de Compras sendo exibido.

CONCLUSÃO: Vimos aqui a nova forma de armazenarmos objetos pertencentes à um determinado usuário que chamamos de Profile. Há ainda outras funcionalidades como por exemplo a configuração e utilização de outros Providers e também como gerenciar e exibir (relatórios) de Profiles. Funcionalidades quais veremos em um futuro artigo desta série para não ficar muito desgastante.

ASP.NET Profile – Parte 2

Como já foi dito anteriormente, os Profiles, por default, são armazenados em um banco do dados do tipo Microsoft Access dentro da pasta App_Data da aplicação. Haverá casos, onde temos a nosso dispor, uma base de dados SQL Server, que é infinitamente mais robusta que um simples arquivo MDB do Microsoft Access, e com isso podemos utilizá-la para que a aplicação grave neste servidor SQL Server as informações/dados referentes aos Profiles da mesma.

Para que isso seja possível, temos no ASP.NET o que chamamos de um Profile Provider, que permitirá você especificar onde as informações serão salvas, definindo isso em seu arquivo de configuração (Web.Config ou Machine.Config).

O ASP.NET nos fornece, já intrinsicamente, um provider/classe chamado SqlProfileProvider. Esta classe deriva de uma classe base chamada ProfileProvider, ficando assim flexível para quando você precisar criar seu próprio Profile Provider para um banco de dados específico, como por exemplo MySQL, Oracle, SyBase, etc.

Mas antes de utilizarmos o SqlProfileProvider temos que fazer algumas configurações fora da nossa aplicação, ou seja, rodarmos um aplicativo “*.exe” que vem juntamente com o .NET Framework 2.0, chamado aspnet_regsql, que você poderá encontrá-lo no seguinte path: WindowsMicrosoft.NETFramework[versao]. Este processo se faz necessário porque através dele, é que são geradas as Stored Procedures dentro do servidor SQL Server. Stored Procedures que são executadas pelo SqlProfileProvider para a manipulação dos dados dos Profiles.

Feito este processo, o que se tem a fazer agora é configurar o Provider, no nosso caso, o SqlProfileProvider no arquivo Web.Config da aplicação. Abaixo um trecho do código do arquivo Web.Config onde é configurado o SqlProfileProvider a nível da nossa aplicação (se desejar definir este Provider para todas as aplicações, poderá fazer esta configuração no arquivo Machine.Config):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
<configuration>
    <system.web>
        <connectionStrings>
            <add name=”CNProfiles”
                connectionString=”Server=localhost;Trusted_Connection=true;database=DBProfiles” />
        </connectionStrings>
 
        <authentication mode=”Forms” />
        <anonymousIdentification enabled=”true” />
        <profile defaultProvider=”SQLProvider” />
 
        <providers>
            <add name=”SQLProvider”
                type=”System.Web.Profile.SqlProfileProvider”
                connectionStringName=”CNProfiles” />
        </providers>
 
        <profile>
            <properties>
                <add name=”Nome” defaultValue=”” allowAnonymous=”true” />
                <add name=”Email” defaultValue=”” allowAnonymous=”true” />
            </properties>
        </profile>
    </system.web>
</configuration>
 
Código 1 – Configurando o Profile no arquivo Web.Config.

Como vemos, dentro do elemento providers temos o nome e o tipo (a classe) que no nosso caso é a SqlProfileProvider que fará o trabalho para gerir os Profiles dentro do SQL Server. Temos também um atributo chamado connectionStringName onde indicamos qual é a string de conexão, que informará o servidor e o banco de dados que será utilizado. Caso você tenha customizado uma classe para um banco de dados qualquer, o tipo (type) mudará, onde voce deverá informar o tipo da classe criada, inclusive o Namespace.

Agora basta utilizar os Profiles normalmente, como foi explicado na primeira parte deste artigo, e os dados já serão gravados dentro do SQL Server especificado na Connection String do arquivo Web.Config.

Gerenciando Profiles – Relatórios

Temos uma classe chamada ProfileManager que nos permite gerenciar os Profiles da nossa aplicação. É através desta classe, que nos é fornecido uma série de métodos e propriedades estáticos para as tarefas mais comuns, como por exemplo excluir, exibir, recuperar Profiles, etc.

Com isso, podemos criar uma aplicação Console ou mesmo um Windows Service que gerencie os Profiles da nossa aplicação, colocando-lá todas as manipulações que desejamos fazer com os Profiles. Abaixo os métodos e propriedades da classe ProfileManager:

Membro Descrição
 DeleteInactiveProfiles Exclui os Profiles inativos, baseando-se em uma data que lhe é informada.
 DeleteProfile Exclui um Profile específico baseando-se no username.
 DeleteProfiles Exclui vários Profiles baseando-se em um Array de usernames.
 FindInactiveProfilesByUserName Retorna uma coleção de objetos do tipo ProfileInfo que estão inativos desde uma data e username específico.
 FindProfilesByUserName Retorna uma coleção de objetos do tipo ProfileInfo de acordo com um username específico.
 GetAllInactiveProfiles Retorna uma coleção de objetos do tipo ProfileInfo com Profiles inativos baseando-se em uma data.
 GetAllProfiles Retorna uma coleção de objetos do tipo ProfileInfo com todos os Profiles.
 GetNumberOfInactiveProfiles Retorna um número inteiro que indica a quantidade de Profiles inativos baseando-se em uma data específica.
 GetNumberOfProfiles Retorna um número inteiro que indica a quantidade de Profiles.
 ApplicationName Nome da aplicação que armazena o Profile.
 AutomaticSaveEnabled Indica que o Profile será salvo automaticamente no final da execução da página.
 Enabled Resgata um valor booleano que indica se o Profile está ou não ativo na aplicação.
 Provider Resgata o provider padrão da aplicação.
 Providers Resgata uma coleção de providers de uma aplicação.

Ainda temos um objeto bastante importante chamado ProfileInfo, que contém informações de um Profile específico. Veremos na tabela abaixo as propriedades desta classe:

Membro Descrição
 IsAnonymous Indica se é ou não um Profile anônimo.
 LastActivityDate Indica última data que o Profile foi acessado.
 LastUpdatedDate Indica última data que o Profile foi atualizado.
 Size Resgata o tamanho do Profile.
 UserName Resgata o username do Profile.

Abaixo um exemplo de como utilizar a classe ProfileManager dentro de uma aplicação qualquer:

1
2
3
4
 
Sub Page_Load()
    Me.GridView1.DataSource = ProfileManager.GetAllProfiles(ProfileAuthenticationOption.All)
    Me.GridView1.DataBind()
End Sub
 
Código 2 – Utilizando a classe ProfileManager.

Como o método GetAllProfiles retorna uma coleção de objetos do tipo ProfileInfo, podemos tranquilamente definí-lo como DataSource de um container de dados para exibi-lo para o usuário assim como é mostrado no código 2, logo acima.

CONCLUSÃO: Nesta situação, os providers e os Profiles são bastante úteis, onde é encapsulado uma porção de códigos para nos ajudar a gerenciar essas informações que temos em aplicações Web/ASP.NET. Claro que se quiser utilizar uma outra base de dados, não SQL Server, terá mesmo que customizar um Provider para esta, herdando da classe base ProfileProvider. E com o uso da classe ProfileManager, temos acesso e gerenciamento dos Profiles da nossa aplicação, evitando trabalhar diretamente com código SQL.