DBAuthorization – Parte 9 – Interface de Administração

Nos posts anteriores analisamos detalhadamente como devemos proceder para criar a infraestrutura necessária, e para efetuar a autorização com as informações armazenadas em uma base de dados. Sem telas de administração destas informações, teríamos que recorrer diretamente as ferramentas do próprio banco de dados para inserir as informações.

Para facilitar, criei também duas telas: ViewAllRules.aspx e AddRule.aspx. A primeira delas exibe uma lista contendo todas as políticas cadastradas, exibindo o caminho (path), ação e tipo, dando também a opção de exclusão. A imagem abaixo exibe essa tela, podendo ser customizada para ter uma melhor aparência:

Basicamente, no evento Load da página invoco o método GetAllRules a partir do provider (classe DBAuthorization) e, com o retorno deste método (DBAuthorizationRuleCollection) carrega o controle DataList. O código para carregar os dados é bem simples:

private void BindRules()
{
    this.DataList1.DataSource = DBAuthorization.GetAllRules();
    this.DataList1.DataBind();
}

Já a segunda página, AddRule.aspx, disponibiliza um formulário com os devidos campos para a criação de uma nova política. Esse formulário já está configurado com alguns DropDownList’s para assegurar que o usuário que está cadastrando a política escolha apenas um dos tipos ou ações suportadas. Essa página é exibida abaixo:

O único código que temos aqui é a inserção da regra no evento Click do botão “Add Rule”, código qual é mostrado abaixo:

DBAuthorization.AddRule(
    new DBAuthorizationRule()
    {
        Action = Helper.ParseEnum<DBAuthorizationRuleAction>(this.Action.SelectedValue),
        Data = Helper.ExtractCollectionFromString(this.Data.Text.Trim()),
        Path = this.Path.Text.Trim(),
        Type = Helper.ParseEnum<DBAuthorizationRuleType>(this.Type.SelectedValue)
    });

Ao caminho (path) não necessariamente precisa ser uma página ASPX, mas um diretório. Isso pode ajudar quando você precisa proteger um diretório todo. Utilize a página quando precisar de um refinamento da autorização, concedendo acesso específico à um papel ou usuário. Como a busca pelo path está baseado na propriedade Path da classe HttpRequest, a propriedade Path da classe DBAuthorizationRule deve seguir o padrão de URL absoluta, como por exemplo: “/App/Diretorio/Pagina.aspx” ou apenas “/App/Diretorio/”.

Essas telas são simplesmente páginas ASPX que fazem uso do API DBAuthorization para exibir ou criar novas políticas de acesso. Como elas fazem parte de um projeto específico, essas telas não são reutilizáveis por várias aplicações. Fica sob responsabilidade do desenvolvedor criar ou não as telas nas aplicações que deseja fazer uso desta funcionalidade.

DBAuthorization – Parte 8 – Configuração do arquivo Web.config

No post anterior acoplamos o módulo DBAuthorizationModule e removemos o módulo UrlAuthorizationModule da execução da aplicação. Isso fará com que a avaliação das políticas seja feita utilizando o provider, que tem o papel de disponibilizar as informações para o módulo e para a aplicação.

Mas as configurações no arquivo Web.config não se resumem a isso. Além das configurações do Membership e Roles, ainda precisamos registrar o provider responsável pela autorização no Web.config. A criação da classe que representa essa seção já foi discutida no artigo em que demonstro como criar um provider customizado. Esse artigo pode ser acessado a partir deste endereço e vou assumir que você tenha o devido conhecimento nisso.

No código abaixo limitei as configurações relevantes do provider, como o registro da seção, a conexão com o banco de dados e a configuração do provider em si. Note que o escolhido foi o “SqlAuthorizationProvider”:

<?xml version=”1.0″?>
<configuration>
    <configSections>
        <section
            name=”dbAuthorization”
            type=”ProjetandoNET.Web.Configuration.DBAuthorizationSection, ProjetandoNET”
            requirePermission=”false” />
    </configSections>
    <connectionStrings>
        <clear/>
        <add
            name=”SqlConnectionString”
            connectionString=”Data Source=.;Initial Catalog=DB;Integrated Security=SSPI;”
            providerName=”System.Data.SqlClient” />
    </connectionStrings>
    <dbAuthorization defaultProvider=”SqlAuthorizationProvider”>
        <providers>
            <add
                name=”SqlAuthorizationProvider”
                type=”ProjetandoNET.Web.Security.SqlAuthorizationProvider, ProjetandoNET”
                applicationName=”WebTeste”
                connectionStringName=”SqlConnectionString”
                storeRulesInCache=”true”
                commandTimeout=”40″ />
        </providers>
    </dbAuthorization>
</configuration>

Assim como na configuração do módulo, o registro da seção que criamos e também os tipos dos providers referentes à autorização também devem contemplar o nome completo (namespace + Assembly). Você pode utilizar cada elemento add para adicionar quantos providers forem necessários mas, em runtime, o ASP.NET utilizará o provider especificado no atributo defaultProvider.

DBAuthorization – Parte 7 – DBAuthorizationModule

Com o post anterior provemos uma boa performance, garantindo que a mesma informação seja compartilhada entre todos os usuários de uma aplicação específica. Além disso, em posts anteriores analisamos como extrair os dados da fonte de dados mas, tudo o que fizemos até então, é a manipulação de dados, mas a regra para validar se o usuário atual possui ou não direito de acesso não foi definida.

Todas as funcionalidades que falamos no primeiro post desta série estão acopladas ao pipeline do ASP.NET através de módulos (IHttpModule). Se analisarmos o arquivo Web.config do servidor (aquele que está dentro do diretório do .NET Framework), veremos os módulos vinculados à execução da aplicação:

<httpModules>
    <add
        name=”WindowsAuthentication”
        type=”System.Web.Security.WindowsAuthenticationModule”/>
    <add
        name=”FormsAuthentication”
        type=”System.Web.Security.FormsAuthenticationModule”/>
    <add
        name=”RoleManager”
        type=”System.Web.Security.RoleManagerModule”/>
    <add
        name=”UrlAuthorization”
        type=”System.Web.Security.UrlAuthorizationModule”/>
    <add
        name=”FileAuthorization”
        type=”System.Web.Security.FileAuthorizationModule”/>
</httpModules>

Os módulos de autenticação são executados de acordo com a opção escolhida no arquivo Web.config (Forms ou Windows). Para autorização, a classe HttpApplication fornece um evento chamado AuthorizeRequest, que é disparado quando chega o momento de determinar a autorização da página requisitada. Os módulos “assinam” esse evento, e são notificados quando ele ocorre e, com isso, executam as verificações necessárias para determinar se o usuário possui ou não os privilégios necessários para acessar a página solicitada.

A UrlAuthorization é a responsável por efetuar essa verificação levando em conta as políticas de acesso que colocamos no arquivo Web.config e, como sabemos, se alteradas forçam o processo do ASP.NET ser reciclado, o que queremos evitar. Como não vamos mais levar em conta as configurações de políticas de acesso que estão no arquivo de configuração, devemos remover este módulo da execução mas, antes disso, ainda é necessário criarmos o nosso.

DBAuthorizationModule é uma classe que implementa a Interface IHttpModule e também assina o evento AuthorizeRequest. No nosso caso, temos que avaliar se o usuário possui ou não privilégio de acesso consultando o provider anteriormente criado. O provider, por sua vez, irá retornar as políticas do cache ou efetuar a busca no banco de dados.

Depois que o provider devolver as políticas definidas, temos que aplicar a regra necessária para conceder ou negar o acesso para o usuário corrente. Mas antes de efetivamente executar essa tarefa, precisamos analisar a propriedade SkipAuthorization da classe HttpContext. Essa propriedade retorna um valor booleano indicando se a autorização deve ou não ser avaliada. Um exemplo disso é quando você solicita a página de Login. Essa página não deve sofrer nenhuma espécie de autorização, já que todos devem acessá-la. Caso essa propriedade retorne False, então devemos avaliar os privilégios do usuário. O código abaixo ilustra o módulo:

public class DBAuthorizationModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.AuthorizeRequest += new EventHandler(OnEnter);
    }

    private void OnEnter(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpContext context = application.Context;

        if (!context.SkipAuthorization)
        {
            if (!IsUserAllowed(context.User, context.Request.Path, context.Request.RequestType))
            {
                context.Response.StatusCode = 401;
                context.Response.StatusDescription = “Unauthorized”;
                application.CompleteRequest();
            }
        }
    }

    public static bool IsUserAllowed(IPrincipal principal, string requestedPath, string verb)
    {
        return DBAuthorization.GetAllRulesByPath(requestedPath).IsUserAllowed(principal, requestedPath, verb);
    }
}

Para avaliar as políticas, criei um método estático chamado IsUserAllowed que recebe como parâmetro o usuário corrente, a página (path) requisitada e o verbo (GET ou POST). Esse método recorre ao provider para recuperar a coleção de regras definidas e, em seguida, invoca o método IsUserAllowed da classe DBAuthorizationRuleCollection informando os mesmos parâmetros. É dentro deste método que toda a “mágica” ocorre, e que por questões de espaço, não será exibida aqui.

Caso os métodos IsUserAllowed retornem False, definimos o código de status de HTTP como 401 (Unauthorized) e invocamos o método CompleteRequest da classe HttpApplication, que obriga o ASP.NET a dar um “bypass” em todos os eventos da classe HttpApplication, direcionando a requisição para o evento EndRequest.

Com o módulo criado, então é necessário adicioná-lo à execução. Mas antes disso, como não faremos uso da Url Authorization, precisamos também removê-la. Dentro do arquivo Web.config da aplicação temos uma seção chamada httpModules. Essa seção permite alistarmos todos os módulos que serão utilizados pela aplicação e, a remoção do módulo “UrlAuthorization” se faz necessária porque em runtime ele mescla os módulos do Web.config do servidor com o da aplicação. Abaixo temos a configuração necessária:

<httpModules>
    <remove
        name=”UrlAuthorization”/>
    <add
        name=”DBAuthorizationModule”
        type=”ProjetandoNET.Web.Security.DBAuthorizationModule, ProjetandoNET”/>
</httpModules>

Note que o tipo (atributo type do elemento add) deve refletir o nome completo da classe, ou seja, incluindo os possíveis namespaces e também o nome do Assembly. As configurações do arquivo Web.config da aplicação não param por aí. Veremos mais detalhes no próximo post desta série.

DBAuthorization – Parte 6 – Caching

No post que vimos anteriormente falamos a respeito da infraestrutura necessária para criar uma solução flexível, visando ter uma independência de base de dados e que nos atendeu perfeitamente. O problema é que o acesso a qualquer fonte de dados (IO), seja banco de dados ou sistema de arquivos é muito custoso e, se fizermos isso durante toda e qualquer requisição para extrair as políticas de autorização, podemos ter problemas relacionados à performance da aplicação.

Quando habilitamos o uso das Roles do ASP.NET, então temos uma configuração que permite o armazenamento dos papéis do usuário em cookie e, com isso, apenas quando ele não for detectado é que o runtime do ASP.NET irá até a base para extrair os papéis do respectivo usuário. Essa solução é interessante, já que os papéis são exclusivos para cada um dos usuários. No caso das políticas de acesso às páginas da aplicação, isso refere-se à um recurso global e que não deve ser armazenado usuário por usuário.

Para evitar o round-trip excessivo ao provider (que no nosso caso é a base de dados), podemos criar uma variável global do tipo DBAuthorizationRuleCollection para armazenar todas as políticas de acesso da aplicação corrente. Com isso, apenas a primeira requisição fará o caminho completo, indo até o provider configurado, extraindo as informações e colocando-as no cache. A partir deste momento, as requisições subsequentes passam a utilizar este cache.

Uma vez que colocamos algum objeto no cache, passamos a ter um outro – pequeno – problema. Por quanto tempo isso vai “viver”? É importante lembrar que vamos criar uma área administrativa em que um usuário poderá customizar essas políticas e, qualquer mudança que ele fizer, devemos invalidar o cache existente. Talvez alguns querem alterar as políticas, mas não vê nenhum problema se essas mudanças não forem aplicadas imediatamente. Para atender a todos, criei um evento chamado DataChanged na classe DBAuthorizationProvider. Esse evento é disparado sempre quando uma nova política é criada ou excluída, e tem a finalidade de informar à aplicação que algo foi mudado, dando oportunidade para ela decidir se mantém ou não o cache.

Esse evento possui um argumento específico, chamado CachingRulesEventArgs. Essa classe tem uma propriedade booleana chamada ClearCache que, por padrão, é True e determina a limpeza do cache. Sendo assim, o momento ideal para assinar este evento é no evento Application_Start, acessível a partir do arquivo Global.asax. O exemplo abaixo ilustra como podemos determinar a limpeza ou não do cache, caso algum dado seja alterado durante a execução da aplicação:

DBAuthorization.Provider.DataChanged += (source, args) => args.ClearCache = true;

Como o padrão é True, somente faz sentido utilizar este código se quiser manter as políticas atuais em cache ou efetuar alguma tarefa customizada. A classe DBAuthorizationProvider possui um método virtual chamado OnDataChanged e, é dentro deste método que ela analisa se a aplicação se vinculou ou não ao evento, e caso verdadeiro o dispara. Finalmente, se a propriedade ClearCache retornar True, essa classe efetuará a limpeza do cache.

CacheManager é a classe estática responsável por gerenciar o cache e que é compartilhada por toda a aplicação. Além de manter a coleção das políticas da aplicação, também possui um objeto do tipo ReaderWriterLockSlim (namespace System.Threading) que gerencia o acesso concorrente ao cache. Esse tipo de objeto garante múltiplas threads para leitura ou o acesso exclusivo para escrita.

É importante dizer que, por mais custoso que seja, o desenvolvedor pode optar por querer sempre consultar a base de dados e, justamente por isso, que criei uma propriedade booleana chamada StoreRulesInCache, que indica ao runtime se os dados devem ou não serem armazenados no cache, sendo o padrão True.

DBAuthorization – Parte 5 – Provider

Os tipos que vimos no post anterior serão utilizados pelo módulo para determinar se a página requisitada possui ou não permissão de acesso. Mas antes disso ainda há um passo extremamente importante, que é justamente a manipulação das informações do repositório que, no nosso caso, será o banco de dados, mais precisamente o SQL Server 2005.

Para seguir as boas práticas do ASP.NET e tornar esse processo independente de fonte de dados, podemos recorrer a criação de um provider customizado (este assunto já foi esgotado neste artigo). Isso irá permitir criarmos uma infraestrutura flexível, permitindo que outros desenvolvedores possam criar classes para abstrair o acesso à outros repositórios, como Oracle ou até mesmo à um  arquivo Xml, como comentado anteriormente. Vale lembrar que as principais funcionalidades do ASP.NET, como o Membership, Roles, Profile, etc., estão baseadas nesta arquitetura.

Para entender exatamente como funciona esta arquitetura, consulte o artigo acima. Neste post vou abordar os tipos base que criei para a criação do provider e também a classe necessária para a manipulação dos dados de autorização dentro do SQL Server.

Em primeiro lugar, foi criado uma classe abstrata chamada DBAuthorizationProvider. Esta classe define a estrutura de métodos e propriedades que todo o provider concreto deverá ter para funcionar. Basicamente temos métodos que fazem a manipulação das informações da fonte de dados, sendo ela qual for, expondo ou recebendo os tipos que falamos anteriormente (DBAuthorizationRule e DBAuthorizationRuleCollection). Isso permitirá criar tipos concretos como SqlAuthorizationProvider (abordado nesta série), OracleAuthorizationProvider ou XmlAuthorizationProvider. Outra definição importante que essa classe expõe é a propriedade ApplicationName que, como todo provider, é necessária para possibilitar a utilização da mesma fonte de dados por múltiplas aplicações, e também deve ser considerada sempre que for efetuar alguma operação no banco de dados. Esta classe ainda fornece métodos para o gerenciamento de caching, que serão abordados futuramente.

O exemplo vem com uma implementação concreta deste provider, que chamei de SqlAuthorizationProvider. Essa classe é a implementação do provider para utilização com SQL Server. Dentro desta classe utilizamos os tipos expostos pelo namespace System.Data.SqlClient para manipular a fonte de dados, utilizando as Stored Procedures que foram mostradas na Parte 3 desta série.

Depois desta estrutura, ainda temos a classe estática DBAuthorization. Como já era de se esperar, essa classe terá todos os membros estáticos e servirá como uma espécie de “ponte” entre a aplicação e o provider especificado no arquivo de configuração. Ela fornece uma propriedade chamada Provider que, por sua vez, retorna a instância do provider (DBAuthorizationProvider) atualmente selecionado. É importante notar que esta classe fornece todos os métodos estipulados pela classe DBAuthorizationProvider mas, invoca os respectivos métodos a partir da propriedade Provider que, consequentemente, irá delegar a chamada para o objeto definido no momento.

Para extrair o provider selecionado no arquivo de configuração, essa classe invoca dentro do construtor estático o método Initialize. Aqui não há necessidade de efetuar algum tipo de “lock” pois, por padrão, o construtor estático já está protegido contra o acesso concorrente neste trecho do código. Para finalizar, a imagem abaixo ilustra a relação entre as classes que foram abordadas neste post:

DBAuthorization – Parte 4 – Estrutura dos Tipos

No post anterior exibi a estrutura necessária para armazenamento das políticas no banco de dados. Da mesma forma, a aplicação também precisa definir alguns tipos (enumeradores, classes, etc.) para garantir que a programação/consumo desta API seja realizada de forma consistente.

Neste post não será discutido na íntegra cada uma das classes, pois isso será feito no momento específico e, além disso, há classes que foram criadas que são utilizadas como “helpers” e que não serão abordadas aqui. Vamos focar apenas nos tipos que representam a estrutura necessária para fazer o recurso funcionar.

Há dois enumeradores chamados DBAuthorizationRuleAction e DBAuthorizationRuleType. O primeiro fornece duas opções autoexplicativas: Deny e Allow. Já o segundo determina os tipos disponíveis, onde efetivamente a política será aplicada: Users, Roles ou HttpVerbs. A combinação destes dois valores ainda exigirá um valor adicional, que são os usuários, papéis ou verbos que poderão ou não acessar o recurso em questão.

Uma outra classe que tem o papel importante nesta funcionalidade é a DBAuthorizationRule. Esta classe representa uma política e, para isso, ela possui as seguintes propriedades (entre parênteses temos o tipo delas): Action (DBAuthorizationRuleAction), Type (DBAuthorizationRuleType), PathId (Guid), RuleId (Guid), Path (String) e Data (StringCollection). Além destas propriedades, ainda temos as propriedades Everyone (Boolean) e Anonymous (Boolean), que são somente leitura. A propriedade Everyone retorna um valor booleano indicando se o caractere “*” está definido na propriedade Data, enquanto a propriedade Anonymous também retorna um valor booleano indicando a existência do caractere “?”. Essas duas propriedades estão fortemente relacionadas ao tipo, ou seja, o caractere “?” somente faz sentido quando é usado em conjunto com o tipo DBAuthorizationRuleType.Users.

Para finalizar, ainda temos a classe DBAuthorizationRuleCollection que, como o próprio nome diz, é uma coleção de elementos do tipo DBAuthorizationRule. Para efeitos de boas práticas, esta classe herda diretamente da classe Collection<T>, apesar de não haver a necessidade de interceptar qualquer mudança na coleção. A imagem abaixo ilustra graficamente os tipos discutidos neste post

DBAuthorization – Parte 3 – Estrutura do Banco de Dados

Como falado no post anterior, vamos utilizar uma base de dados como repositório para as políticas de acesso aos arquivos ASP.NET. Vale lembrar que você pode utilizar qualquer outro tipo de repositório, como por exemplo, um arquivo Xml independente.

É importante dizer que a partir da versão 2.0 do ASP.NET, a Microsoft criou diversas APIs para gerenciamento de usuários (Membership), de papéis (Roles) e de perfis de usuários (Profile). Essas funcionalidades foram extensamente discutidas neste artigo e já assumirei que você já tenha o respectivo conhecimento.

As tabelas auxiliares necessárias serão colocadas no mesmo banco de dados que já possua a infraestrutura das funcionalidades do ASP.NET que vimos no artigo mencionado acima. Basicamente teremos duas tabelas adicionais, sendo: aspnet_Authorization_Paths e aspnet_Authorization_Rules. Como podemos ter diversas políticas para uma página específica, então a primeira tabela será responsável por armazenar paths (páginas ou diretórios) que você deseja aplicar as políticas. Além do path (que deve incluir o diretório virtual), essa tabela terá uma coluna chamada ApplicationId, que é a chave estrangeira da tabela aspnet_Applications (built-in), já que podemos ter um banco de dados do ASP.NET compartilhado para várias aplicações. A segunda tabela, aspnet_Authorization_Rules, armazenará quais são as políticas definidas para um determinado path e, com isso, além de ter uma coluna para relacionamento com a tabela de paths, temos também: Action, Type e Data. A coluna Action determina qual a ação a ser executada (Deny ou Allow); já a coluna Type determina em quem vamos aplicar a ação (Users, Roles ou Verbs) e, finalmente, a coluna Data armazenará o nome dos usuários, papéis ou verbos que farão parte da política. A imagem abaixo ilustra o relacionamento entre elas:

Verbs: Esses são os conhecidos verbos de HTTP, e que neste caso faremos o uso GET ou POST. A idéia é também permitir o acesso somente quando a requisição for realizada através de um deles.

Além das tabelas ainda temos as Stored Procedures. Essas Stored Procedures apenas definem as queries necessárias para efetuar a inserção, exclusão ou a leitura das informações referentes as políticas de acesso. Para seguir a mesma convenção estipulada pela Microsoft (não que você deva seguí-la), as Stored Procedures estão prefixadas com a palavra “aspnet_”. Para atender as tarefas que vamos desempenhar, criei quatro delas, a saber:

 

  • aspnet_Authorization_AddRule: Adiciona uma nova regra.
  • aspnet_Authorization_DeletePathById: Exclui o path e todas as regras relacionadas.
  • aspnet_Authorization_DeleteRuleById: Exclui apenas uma única regra.
  • aspnet_Authorization_ReadRules: Extrai todas as regras definidas para uma aplicação.

Para poupar espaço, não vou colocar o código referente a cada Stored Procedure aqui. No final da série publicarei o projeto todo, incluindo o backup do banco de dados que possui a estrutura necessária para fazer tudo isso funcionar.

DBAuthorization – Parte 2 – A possível solução

Para solucionar o problema descrito no post anterior, podemos utilizar o banco de dados como repositório das políticas de acesso às páginas ASP.NET. Com isso, qualquer mudança que seja necessária, você pode utilizar os comandos (queries) e classes tradicionais de acesso à dados para fazer tal manipulação.

Mover para o banco de dados nos permitirá criar uma interface amigável para que o responsável pelo sistema gerencie as políticas de acesso. Como um ponto negativo desta técnica, temos o excesso de round-trips que haverá durante a execução da aplicação. Como todas as páginas ASP.NET podem ou não estar protegidas, será necessário consultar a base de dados para certificar que há alguma política definida para esta página e, se houver, aplicá-la.

Como as políticas de acesso são comuns para qualquer usuário, ou melhor, são configurações globais da aplicação e serão aplicadas/avaliadas em todas as requisições, podemos utilizar alguma estratégia de caching para evitar o constante acesso à base de dados. Mas esta técnica exige um certo sincronismo entre a leitura e escrita e que será discutido no momento correto.

DBAuthorization – Parte 1 – O problema

O ASP.NET introduziu novas formas de configurar e gerenciar a autenticação e autorização em aplicações Web. Temos o Windows Authentication e Forms Authentication para a autenticação e Url Authorization ou File Authorization para autorização.

Com Windows Authentication, o usuário apresentará o token do Windows que será validado em algum local (sendo na própria máquina ou em algum domínio). Já com o Forms Authentication, a aplicação irá gerenciar a identidade do usuário gravando-a em cookie durante o login e reutilizando-o nas chamadas subsequentes. Em um ambiente de internet, esse modelo é o mais utilizado.

Para autorização, temos duas possibilidades, sendo a primeira delas a File Authorization. Neste modo, o usuário somente terá acesso a um arquivo ASPX se o mesmo tiver permissão (ACL) de acesso. Este tipo de autorização é comumente utilizada em conjunto com a autenticação Windows (geralmente utilizado em intranets), onde você customizará as permissões fisicamente nos arquivos. No modo Url Authorization, a configuração de acesso é feito baseando-se na URL, e em um ambiente de internet, é também o que mais se utiliza.

O foco dos posts será a configuração da Url Authorization. Para customizar a restrição de cada arquivo, devemos recorrer ao arquivo Web.config, configurando a página ou diretório e quem pode ou não pode acessá-los. Aqui você poderá refinar a autorização baseando-se em usuários (users) ou em papéis (roles). A forma mais simples de conceder e negar acesso é fazendo uso dos papéis, pois nome de usuários mudam com muita frequência, e em pouco tempo deixam de existir. Como exemplo, o trecho de código abaixo ilustra uma possível configuração:

<?xml version=”1.0″?>
<configuration>
    <location path=”AreaRestrita”>
        <system.web>
            <authorization>
                <deny users=”?”/>
            </authorization>
        </system.web>
    </location>
</configuration>

Neste exemplo podemos notar que o diretório “AreaRestrita” é protegido contra usuários anônimos (representado pelo caractere “?”), ou seja, enquanto o usuário não estiver autenticado na aplicação, ele não terá acesso a qualquer conteúdo que está dentro deste diretório. Já no exemplo a seguir, apenas permitimos acesso à página “CriarUsuario.aspx” se o usuário fizer parte do papel (role) “Admin”:

<?xml version=”1.0″?>
<configuration>
    <location path=”CriarUsuario.aspx”>
        <system.web>
            <authorization>
                <allow roles=”Admin”/>
            </authorization>
        </system.web>
    </location>
</configuration>

Neste modelo de definição de autorização, temos um grande problema que é a atualização das informações, ou melhor, a alteração das políticas ali definidas. Podemos facilmente recorrer a API System.Xml que temos dentro do .NET Framework ou, de forma mais simples, utilizar as APIs de configuração que me permite o acesso tipado às seções do arquivo Web.config. Mas o problema maior não é isso, mas sim o transtorno que a alteração causa. Qualquer mudança no arquivo Web.config causará a reciclagem do processo do ASP.NET e, consequentemente, todas as informações que estão em memória, como o caching, variáveis estáticas, de sessão e aplicação são completamente perdidas, podendo ocasionar erros inesperados em tempo de execução e um comportamento indesejado para os usuários.

Particularmente eu acredito que essas políticas não mudam com muita frequência, mas recebi diversos e-mails e questões em fóruns perguntando sobre isso. De qualquer forma, resolvi criar uma forma customizada para definir as políticas de acesso às páginas ASP.NET. A partir da série de posts abaixo vou mostrar uma possível solução para este caso:

DBAuthorization – Parte 1 – O Problema
DBAuthorization – Parte 2 – A possível solução
DBAuthorization – Parte 3 – Estrutura do Banco de Dados
DBAuthorization – Parte 4 – Estrutura dos Tipos
DBAuthorization – Parte 5 – Provider
DBAuthorization – Parte 6 – Caching
DBAuthorization – Parte 7 – DBAuthorizationModule
DBAuthorization – Parte 8 – Configuração do arquivo Web.config
DBAuthorization – Parte 9 – Interface de Administração
DBAuthorization – Parte 10 – Conclusão

Criando certificados para teste

Algumas funcionalidades de segurança do WCF exigem que se utilize certificados para proteger a mensagem. Eu tenho escrito sobre algumas dessas funcionalidades aqui e, no download do código fonte, o certificado não está junto com o projeto. Com isso, ao rodar a aplicação, voce receberá uma exceção dizendo que não foi possível localizar este certificado. Isso aconteceu recentemente com o artigo sobre customização de autenticação e autorização e que o leitor Marco Tulio apontou aqui.

Neste artigo específico, para que voce consiga rodá-lo, o primeiro passo é criar um certificado de testes a partir do utilitário makecert.exe que, por sua vez, vem juntamente com o SDK do .NET Framework. Para utilizá-lo, basta definir algumas configurações necessárias para a geração do certificado, algo como é mostrado abaixo:

C:>makecert -sr LocalMachine -ss TrustedPeople -sky exchange -pe -a sha1 -n “CN=localhost” C:localhost.cer

Observação: Para entender cada uma dessas opções que o utilitário fornece, consulte este artigo.

Depois do certificado criado, então nos resta configurá-lo para que o serviço possa fazer uso dele. Ao abrir o arquivo de configuração do host, efetue a mudança do certificado, apontando para este que foi recém criado. O código ficará semelhante ao qual é mostrado abaixo:

<serviceCredentials>
  <serviceCertificate
    findValue=”localhost”
    storeLocation=”LocalMachine”
    storeName=”TrustedPeople”
    x509FindType=”FindBySubjectName” />
    <!– Outras Configurações –>
</serviceCredentials>

Depois disso, execute o host separadamente e atualize a referencia no projeto cliente para que a chave pública seja alterada.

Para finalizar, ainda falando do artigo que explora a customização da autenticação e autorização no WCF, é necessário alterar o path dos arquivos XML que estão dentro da classe/arquivo XmlSecurityHelper.