Desenvolvimento - ASP. NET

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.

por Israel Aéce



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>
Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.