DBAuthorization - Parte 6 - Caching

by Israel Aece 28. February 2009 13:45

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.

Tags:

ASP.NET | Security

DBAuthorization - Parte 5 - Provider

by Israel Aece 27. February 2009 13:43

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:

Tags:

ASP.NET | Security

DBAuthorization - Parte 4 - Estrutura dos Tipos

by Israel Aece 26. February 2009 13:41

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

Tags:

ASP.NET | Security

DBAuthorization - Parte 3 - Estrutura do Banco de Dados

by Israel Aece 26. February 2009 13:39

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.

Tags:

ASP.NET | Security

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

by Israel Aece 26. February 2009 13:38

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.

Tags:

ASP.NET | Security

DBAuthorization - Parte 1 - O problema

by Israel Aece 25. February 2009 13:37

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

Tags:

ASP.NET | Security

Expondo serviços WCF através do .NET Service Bus

by Israel Aece 23. February 2009 14:44

Depois da conta devidamente criada dentro do Windows Azure, podemos facilmente fazer uso das funcionalidades que ele nos fornece. Dentre essas funcionalidades temos o .NET Services e, dentro deste, um repositório de serviços chamado .NET Service Bus. Utilizando o WCF, podemos conectar duas aplicações que estão localizadas em diferentes locais, ultrapassando os limites de uma organização, utilizando a "ponte" que é o Service Bus.

Em termos de estrutura e criação de um serviço, nada muda em relação ao que já conhecemos. As pequenas mudanças são na configuração do mesmo, a começar pelo binding, já que devemos utilizar um dos relay bindings. Além disso, há um novo behavior chamado TransportClientEndpointBehavior, que nos permite configurar as credenciais de acesso, assim como é ilustrado na configuração do host logo abaixo:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Host.Servico">
        <endpoint
          address="sb://servicebus.windows.net/services/[solucao]/[servico]"
          behaviorConfiguration="bh"
          binding="netTcpRelayBinding"
          contract="Host.IContrato" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="bh">
          <transportClientEndpointBehavior
            credentialType="UserNamePassword">
            <clientCredentials>
              <userNamePassword
                 userName="[username]" password="[password]" />
            </clientCredentials>
          </transportClientEndpointBehavior>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Outro ponto importante que devemos notar é o endereço. Neste CTP, o .NET Service Bus apenas suporta dois schemes: "sb" e "http". O "http" para protocolo HTTP e o "sb" para todos os outros. De acordo com o modelo de nomenclatura, "servicebus.windows.net" é a raiz, e com isso os endereços devem seguir o seguinte formato: "[scheme]://servicebus.windows.net/services/[solucao]/[servico]/[nome]". Em futuras versões ele suportará diferentes formatos na URI.

As tipos que utilizamos para expor/consumir serviços WCF em cima do .NET Service Bus, estão contidos no Assembly Microsoft.ServiceBus.dll  que, por sua vez, é instalado com o SDK do Microsoft .NET Services (CTP). Ao rodar o host, o serviço está exposto e pode ser consumido pelos clientes. Ao iniciar o cliente que consome este serviço, as mensagens passam agora pelo .NET Service Bus até chegar efetivamente ao serviço.

Tags: ,

CSD | WCF

CSE - Corrupted State Exceptions

by Israel Aece 21. February 2009 14:46

A classe Exception é a base para todas as exceções do .NET Framework. Tanto exceções do próprio .NET quanto aquelas que criamos, herdam direta ou indiretamente dela, independentemente de sua severidade.

Tendo isso em mente, nunca foi tão perigoso utilizar a instrução catch(Exception). Mesmo em "aplicações finais", que são aquelas que devem ter um handler para tratar as possíveis exceções, utilizar esta técnica pode ser muito perigoso. Existem algumas exceções que são consideradas "exceções de sistema", como é o caso da AccessViolationException, e como também derivam da classe Exception, serão capturadas e "tratadas" pelo handler catch(Exception). Mas isso não quer dizer que voce pode/deve continuar utilizando a aplicação, já que essas exceções comprometem o estado do respectivo processo.

Para resolver este tipo de problema, a Microsoft está disponibilizando junto ao .NET 4.0 o conceito de CSE (Corrupted State Exceptions). Com este novo recurso, a CLR não entregará exceções que podem danificar o processo para o handler catch(Exception), a menos que voce faça isso explicitamente (apesar de não ser uma boa prática), decorando o método que pode causar a falha com o atributo HandleProcessCorruptedStateExceptionsAttribute. Para maiores informações sobre essa nova funcionalidade, consulte este artigo.

Tags:

.NET Framework

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host