Explorando o WIF

by Israel Aece 19. February 2010 22:46

O novo modelo de autenticação e autorização possui três elementos, conhecidos como: Identity Providers, Relying Parties e Subjects. Já falamos sobre o papel que cada um deles exerce dentro deste novo sistema em um artigo anterior. Mas como utilizar estes elementos dentro das nossas aplicações, ou melhor, como fazer com que uma aplicação seja candidata à receber claims emitidas por algum identity provider?

É justamente neste momento que entra em cena um dos grandes pilares que compõem o novo modelo de autenticação e autorização, que é o Windows Identity Foundation (WIF). Através deste novo framework, a Microsoft disponibiliza uma série de tipos que podem ser utilizados não somente para construir aplicações que receberão as claims (relying parties), mas também para estender alguns recursos existentes, como por exemplo, tipos para a criação de um STS (Security Token Service) customizado, criar extensões para o ADFS 2.0, entre outras coisas.

O WIF expõe uma série de tipos para a criação das relying parties, e o uso de cada um destes tipos está condicionado ao tipo de ambiente que está sendo aplicado, que pode ser passivo ou ativo. A finalidade deste artigo é abordar os tipos comuns, e que são úteis para qualquer um destes ambientes. Tipos específicos, utilizados pelos ambientes passivos/ativos e também para estensibilidade, serão abordados em futuros artigos.

Antes de falar diretamente das classes do WIF, é importante dizer que desde a primeira versão do WCF, já havia um assembly chamado System.IdentityModel.dll, que fornece classes para construir serviços que suportem claims. O problema é que muitas outras classes que também são necessárias para executar algumas outras tarefas não eram públicas, tornando um desafio para aqueles que estão precisavam disso. E para complicar ainda mais, não havia um modelo de programação uniforme, que poderíamos facilmente acoplar à execução das aplicações, pois não mantém compatibilidade com o formato tradicional de segurança imposto pelo .NET Framework. O papel do WIF é unificar esse modelo de autenticação e autorização, tornando algo comum para qualquer tipo de aplicação que você desenvolva.

Desde as primeiras versões do .NET Framework, há duas interfaces que controlam a autenticação e autorização dos usuários: IIdentity e IPrincipal. A primeira é reponsável por representar o usuário que está autenticado, fornecendo entre outras informações, o seu nome. Já a interface IPrincipal, representa o contexto de segurança daquele usuário, e fornece uma propriedade chamada Identity do tipo IIdentity e um método chamado IsInRole, que dado uma string representando um papel, retorna um valor boleano indicando se o usuário está ou não contido nele.

E onde elas estão dentro da aplicação? Dentro do namespace System.Threading existe uma classe chamada Thread. Essa classe determina como controlar uma thread dentro da aplicação, e que entre vários membros, possui uma propriedade chamada CurrentPrincipal que recebe a instância de um objeto que implementa a interface IPrincipal. É através desta propriedade que devemos definir qual será a identity e a principal que irá representar o contexto de segurança para a thread atual. Para maiores detalhes, consulte o capítulo 9 deste livro.

Depois de uma pequena recapitulação, vamos começar a abordar o que temos à disposição dentro do WIF. As classes que veremos a seguir, estão dentro do assembly Microsoft.IdentityModel.dll e debaixo do namespace Microsoft.IdentityModel.Claims. Como uma das preocupações da Microsoft foi manter compatibilidade com o modelo de segurança do .NET Framework, ela criou versões específicas das interfaces IIdentity e IPrincipal, que são chamadas de IClaimsIdentity e IClaimsPrincipal. Abaixo temos a imagem do diagrama que representa a hierarquia entre as interfaces de identity:

A primeira propriedade que ela fornece, chamada de Actor, é do tipo IClaimsIdentity, que é útil em cenários de delegação, e em conjunto com ela, temos a propriedade BootstrapToken, mas veremos mais detalhes sobre elas em um artigo futuro, que abordará exatamente este cenário. A terceira propriedade, chamada Claims, é uma das mais importantes para este modelo de autenticação/autorização. Esta propriedade expõe uma coleção do tipo ClaimCollection, onde cada elemento é representado por uma instância da classe Claim, que detalheremos mais adiante. Label é uma propriedade de tipo string, que recebe uma informação qualquer, que personaliza aquela identidade.

Como sabemos, a propriedade Name exposta pela interface IIdentity, retorna o nome do usuário atual, enquanto o método IsInRole, faz verificações baseando-se nos papéis daquele mesmo usuário. Como esses membros são popularmente utilizados, como iremos preencher essas informações? É justamente aqui que as propriedades NameClaimType e RoleClaimType são úteis. Como todas as informações emitidas por um identity provider chegam para uma relying party através de claims, é necessário saber qual claim representa o nome do usuário (IIdentity.Name) e o papel (IPrincipal.IsInRole). Geralmente, cada claim nada mais é que uma string, representado por uma URI, mas não que isso seja obrigatório. Abordaremos mais detalhes sobre a sua representação quando falarmos à respeito da classe Claim, ainda neste artigo.

Observação: Como disse anteriormente, claims podem ser muito mais expressivas do que simplesmente ter o nome do usuário e seus papéis. A necessidade de termos as propriedades NameClaimType e RoleClaimType, é justamente para manter compatibilidade com todo o legado de segurança que já conhecemos e/ou temos em aplicações que fazem uso do .NET Framework.

Da mesma forma que vimos acima, a interface IClaimsPrincipal também herda diretamente da interface IPrincipal, incluindo apenas uma única propriedade, chamada de Identities, que é do tipo ClaimsIdentityCollection, ou seja, uma coleção onde cada elemento dentro dela é do tipo IClaimsIdentity. Na maioria das vezes, essa propriedade somente terá um único elemento, representando o usuário atual, entretanto, em cenários mais avançados, pode haver várias identidades de um mesmo usuário, emitidos por orgãos diferentes. A imagem abaixo ilustra a herança entre essas duas interfaces:

Como vimos acima, a interface IClaimsIdentity, expõe uma coleção de claims. Cada elemento é representado por uma instância da classe Claim, e como o próprio nome diz, representa uma claim entre as várias que um identity provider pode emitir para um determinado subject. Como já comentado em artigos anteriores, cada claim é uma espécie de afirmação que um identity provider emite para algum subject, e entre elas, podemos ter o nome do usuário e os papéis em que ele se encontra.

Como podemos notar na imagem acima, a classe Claim fornece várias propriedades que descreve cada uma delas. A propriedade ClaimType, retorna uma string, que na maioria das vezes é uma URI, representando o que aquela claim significa (se é o nome, e-mail, data de nascimento, etc.). A propriedade Issuer também retorna uma string com o nome daquele que emitiu a claim. OriginalIssuer tem sentindo quando trabalhamos em ambientes federados, onde pode haver mais do que um emissor. Properties é uma propriedade que expõe um dicionário de strings, que permite adicionarmos informações extras sobre uma determinada claim. A propriedade Subject é clara, retorna uma instância do tipo IClaimsIdentity que representa o subject ao qual aquela claim pertence. A propriedade Value retorna o valor daquela claim ("Israel Aece", "ia@israelaece.com", 05/09/1981, etc.). E, finalmente, temos a propriedade ClaimValue, que ajuda a definir o tipo daquela claim. Essa propriedade é útil durante o processo de deserialização do token, fornecendo informações a respeito do tipo que aquela claim é. Há uma classe estática chamada ClaimValueTypes, que possui várias constantes públicas que já são conhecidas, tais como: Integer, DateTime, Double, etc.

As interfaces não são "instanciáveis", e justamente por isso, precisamos de classes que implementem esses recursos expostos pelas interfaces apresentadas acima. E para isso, a Microsoft disponibilizou também as classes ClaimsIdentity e ClaimsPrincipal, que implementam as interfaces IClaimsIdentity e IClaimsPrincipal, respectivamente. Além dos membros que elas são obrigadas a implementarem, ainda há na classe ClaimsPrincipal, métodos estáticos, que auxiliam na criação de uma principal deste tipo, extraindo as informações de objetos que já são conhecidos, como HttpContext ou IIdentity. A imagem abaixo ilustra a estrutura destas duas classes:

Essas classes são, na maioria das vezes, instanciadas pelo próprio runtime do WIF, que as cria de acordo com o token que é recebido do identity provider. Depois de criadas, o próprio WIF armazena a instância da classe ClaimsPrincipal dentro da propriedade ThreadPrincipal, exposta pela classe Thread, ou se estivermos falando de uma aplicação ASP.NET, ela - também - estará acessível através da propriedade estática User da classe HttpContext.

Em ambos os casos, essas propriedades recebem e retornam classes que implementam a interface IPrincipal, e graças ao polimorfismo, podemos definir classes que implementam a interface IClaimsPrincipal nestas mesmas propriedades. O único detalhe importante, é que para extrair e ter acesso à todas as propriedades expostas pela interface IClaimsPrincipal, será necessário efetuar a conversão, assim como é mostrado no trecho de código abaixo:

IClaimsPrincipal principal = Thread.CurrentPrincipal as IClaimsPrincipal;
if (principal != null)
{
    IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;

    Console.WriteLine("Nome: {0}", identity.Name);
    Console.WriteLine("Label: {0}", identity.Label);

    foreach (Claim c in principal.Claims)
        Console.WriteLine("{0}: {1}", c.ClaimType, c.Value);
}

Obviamente que essas classes por si só não funcionam. Quem faz grande parte do trabalho para a criação de cada uma delas é o runtime do WIF, e a sua configuração depende diretamente de qual tipo de aplicação estaremos utilizando (web ou serviços). O que vimos no decorrer deste artigo, são as classes que representam um subject para uma relying party, e isso não importa o tipo de aplicação que estamos utilizando. Em futuros artigos, analisaremos como configurar cada uma delas, e vamos notar que essas propriedades já estarão devidamente preenchidas.

Conclusão: Este artigo apresentou os novos tipos que temos a nossa disposição para a criação de aplicações (relying parties), que consomem claims geradas por algum identity provider. Reparamos também que o modelo de programação segue a mesma estrutura imposta pelo .NET Framework, e que por isso, podemos reaproveitar o conhecimento adquirido anteriormente, para ter um impacto bem menor quando migramos para este novo modelo.

Tags: , ,

Security | WIF

Os Elementos do Sistema de Identidade

by Israel Aece 18. February 2010 08:04

No artigo anterior, falamos um pouco sobre os problemas conhecidos quando lidamos com a autenticação e autorização em uma aplicação, e no final dele, falei superficialmente sobre os produtos que a Microsoft tem desenvolvido para tornar a utilização do modelo baseado em claims mais simples. A partir de agora, vamos analisar os elementos que compõem o sistema de identidade, analisando o fluxo e alguns conceitos que circundam este modelo, e que é independente de tecnologia.

Tudo o que veremos a seguir é conhecido como Identity MetaSystem, que consiste em uma infraestrutura que abstrai todas as operações necessárias para promover tudo o que é preciso para suportar identidades em cima da internet, fornecendo uma arquitetura interoperável, que permite as mais variadas plataformas implementar e dar suporte à este modelo. Existem três grandes elementos que fazem parte dele, e que são responsáveis pela propagação das identidades, a saber:

  • Identity Providers: É o responsável pela validação e emissão de tokens, que são fornecidos para alguém, contendo um conjunto de claims.
  • Relying Parties: A aplicação que recebe e usa esses tokens que são emitidos por algum Identity Provider.
  • Subjects: É alguma coisa ou alguém que possui uma identidade digital (e suas claims), e que na maioria das vezes, representará um usuário.

Cada produto desenvolvido pela Microsoft tem como alvo um dos elementos acima, sendo o ADFS 2.0 uma espécie de identity provider; já o Cardspace permite o gerenciamento das identidades de um subject e, finalmente, o WIF ajuda na construção de aplicações que recebem esses tokens. Como dito anteriormente, todos esses elementos seguem padrões de mercado, e que são rigidamente gerenciados por órgãos independentes. Os protocolos que são utilizados para troca de informações foram desenhados para cruzar limites, que antes deles, eram invioláveis (plataforma e firewalls).

Outro elemento importante que faz parte deste modelo é o Security Token Service (STS). Como o próprio nome diz, trata-se de um serviço que está dentro de um identity provider, e que é responsável por emitir e empacotar as claims que são geradas para alguém, seguindo alguns padrões de mercado que analisaremos mais tarde, ainda neste artigo.

Há algumas técnicas que podem ser utilizadas para criar ambientes baseados em claims. Por exemplo, aplicações web e serviços SOAP (WCF) podem ser considerados relying parties, pois podem fazer uso de claims fornecidas por algum identity provider em nome de alguém. Já o navegador (browser) e aplicações Smart Client, são consideradas os subjects, já que são os responsáveis por gerenciar o fluxo do processo de autenticação, que direta ou indiretamente, irão direcionar o usuário para efetuar a sua autenticação no identity provider em que a aplicação confia.

Quando utilizamos este novo modelo, temos dois ambientes, onde cada um deles trabalha de forma ligeiramente diferente. Esses ambientes são conhecidos como ambiente passivo e ativo. A partir daqui, vamos analisar cada um desses ambientes, tentando abordar como esse modelo trata cada um deles, sem abordar as tecnologias que estão envolvidas. Para ilustrar melhor como cada um deles funciona, vamos analisar a imagem abaixo:

No ambiente passivo, o subject é o browser, enquanto a relying party é uma aplicação web. Quando um usuário requisita uma página que está protegida (1), a aplicação faz essa verificação e nota que o usuário não está autenticado. Com isso, a aplicação redireciona o usuário para o identity provider em que ela confia (2). Este, por sua vez, faz a autenticação do usuário. Aqui não importa o modelo de autenticação que é utilizado (Windows, UserNames, certificados, etc.). Depois de devidamente autenticado, o identity provider retorna o token correspondente aquele usuário (3), que a partir de agora, enviará esse token para todas as requisições subsequentes (4). Como a verificação da existência do token acontece em todas as requisições, uma vez que o usuário estiver autenticado, ele não será mais redirecionado para o identity provider.

Já no ambiente ativo, o fluxo muda um pouco. Neste cenário, o subject é uma aplicação Smart Client, enquanto a relying party é um serviço SOAP (WCF). O primeiro passo passa a ser a autenticação no respectivo identity provider (1), que uma vez autenticado, um token é emitido para aquele usuário (2). Com isso, todas as requisições irão embutir em seus respectivos headers, o token deste usuário (3). Como a relying party também conhece e confia naquele mesmo identity provider, então o acesso às operações do serviço será garantido.

É importante dizer que no ambiente ativo, o processo acaba sendo mais rápido quando comparado com o ambiente passivo, pois o cliente não precisa visitar o serviço para saber qual é o identity provider que o mesmo utiliza. Isso já aconteceu durante a referência do serviço na aplicação Smart Client, que traz, além da descrição do serviço em si, informações inerentes ao identity provider em que o serviço confia.

Identity Federation

O que vimos acima consiste nos cenários onde todos os participantes estão dentro de um mesmo domínio. Mas um dos principais cenários que este modelo atende, é justamente quando temos os participantes do sistema separados, em domínios diferentes. Da mesma forma que vimos anteriormente, a aplicação ou serviço também poderá aceitar claims que são emitidas por outros identity providers, que estão além do seu domínio, mas que indiretamente há uma relação de confiança estabelecida entre eles. A possibilidade de integração entre os dois domínios também é conhecida como Identity Federation.

Utilizar esta técnica, facilitará muito a possibilidade de SSO (Single Sign-On), que é a possibilidade de se autenticar uma única vez, e reutilizar aquela mesma credencial por todas as aplicações e serviços (relying parties) que aquele usuário utiliza, mesmo que essas aplicações estejam hospedadas nos servidores dos parceiros da nossa empresa. Outro grande ponto positivo desta opção, é que se o usuário não fizer mais parte da minha empresa, tudo o que eu preciso fazer, é remover/desabilitar a sua respectiva conta no meu domínio e, consequentemente, ele não terá mais acesso aos parceiros, já que os parceiros somente confiam em usuários que possuem os tokens emitidos por mim.

Da mesma forma que vimos acima, "cenários federados" também suportam os ambientes passivo e ativo, com mudanças simples no fluxo das informações. E para clarear como o fluxo ocorrerá, vamos utilizar imagens para ilustrar, começando com o ambiente passivo:

Note que o usuário, que está no domínio 1, tenta acessar uma aplicação web que está hospedada no domínio 2 (1). A aplicação detecta que o usuário ainda não está autenticado, e o redireciona para o identity provider do domínio onde a aplicação está hospedada, que é o orgão que ela confia. Como parte deste redirecionamento, o identity provider do domínio 2 conhece o identity provider do usuário, que está no domínio 1. Isso fará com que o usuário seja novamente redirecionado para o identity provider que o conhece, que é aquele que está debaixo do domínio 1 (2). Ao validar o usuário, o token que o representa dentro da empresa é emitido (3). Depois disso, o token gerado no domínio 1, é encaminhado para o identity provider do domínio 2 (4), que o validará e fará eventuais transformações, criando ou mapeando as claims para claims que a aplicação espera. Isso fará com que um novo token seja emitido (5), e que será utilizado pelo usuário para enviar para as requisições subsequentes (6).

Da mesma forma, o ambiente ativo segue o mesmo fluxo do ambiente passivo, apenas evitando o handshake necessário no ambiente passivo, que é necessário para descobrir qual é o identity provider responsável pela autenticação. Assim como comentado acima, todas as informações necessárias já foram fornecidas durante a referência do serviço, e as requisições (5) já enviarão o token embutido em seus headers.

Observação: Acima vimos os ambientes passivo e ativo sem mencionar os detalhes de implementação de cada um deles. Isso será alvo de futuros artigos, que irão detalhar cada um dos ambientes, mostrando como proceder para configurar cada um deles.

Os Padrões

Uma das principais características que este modelo autenticação e autorização deve ter, é a interoperabilidade entre as partes envolvidas. Como a ideia permitir uma espécie de layer na internet para gerenciar toda a segurança, inclusive entre domínios, uma das principais exigências é a garantia de que toda e qualquer plataforma pudesse tirar proveito disso.

Atualmente já há várias especificações que regem grande parte das comunicações distribuídas entre plataformas, e que são conhecidas como padrões WS-*. Os padrões que compõem as especificações WS-* já estão há bastante tempo no mercado, e são gerenciados por órgãos independentes. Há padrões para grande parte das necessidades que temos hoje em dia nos sistemas distribuídos, tais como: transações, mensagens confiáveis e segurança. Como esses padrões já estão quase todos finalizados, então porque não utilizá-los? É justamente isso que ocorreu, ou seja, grande parte de toda a comunicação que é efetuada entre as partes deste modelo de autenticação, são realizadas seguindo esses padrões, mas felizmente são transparentes para o usuário final, e abstraídas através dos produtos criados pela Microsoft. Abaixo temos esses padrões elencados, com uma breve descrição de cada um deles:

  • WSDL: Descreve quais funcionalidades (operações) são expostas pelo serviço.
  • WS-Policy: O WSDL não descreve nada sobre os requerimentos de segurança necessários para invocar o serviço. O WS-Policy aborda este caso, fornecendo uma forma genérica de descrever os requerimentos de segurança relacionados ao respectivo serviço.
  • WS-Security: Basicamente, este padrão especifica como aplicar a criptografia nas mensagens SOAP, fazendo com que as informações trafeguem de forma protegida, garantindo a confidencialidade e integridade. A forma de segurança aplicada aqui está condicionada ao formato de autenticação que você utilizará entre o subject e o identity provider. Como a interoperabilidade é um dos aspectos mais importantes, o WS-Security efetua a criptografia das mensagens através de token profiles, que descreve como mapear as tecnologias de autenticação que temos atualmente (KerberosUserNames, certificados, etc.), para um modelo genérico.
  • WS-SecurityPolicy: Fornece um padrão para representar as capacidades e requerimentos dos serviços através de políticas.
  • WS-Trust: Este padrão fornece extensões para o padrão WS-Security, definindo operações específicas de emissão, renovação e validação de tokens.
  • WS-Federation: Organiza em uma linguagem de mais alto nível os padrões WS-Trust e WS-Security, definindo mecanismos para permitir que a autenticação e autorização sejam feitas entre domínios.
  • WS-MetadataExchange: Permite uma forma de extrair, não somente os dados que descrevem as operações do serviço, mas também todas as funcionalidades (de infraestrutura) expostas por aquele serviço.

Além dos padrões acima, ainda temos o SAML (Security Assertion Markup Language). Enquanto o padrão WS-Security define como inserir as informações dentro do envelope SOAP, o SAML ajuda a definir o que essas informações são. O SAML é uma especificação, também baseada em XML, que permite alguém emitir afirmações a respeito de outro alguém ou para alguma coisa, independentemente da identidade que está sendo utilizada. Entre essas afirmações que fazemos sobre algo, elas também podem descrever os atributos deste alguém, que são também conhecidos como claims.

Como disse acima, o padrão WS-Trust fornece um contrato com quatro operações: Issue, Validate, Renew e Cancel. Cada uma dessas operações autoexplicativas, manipulam tokens que são gerados por um STS para seus subjects. Em todos os ambientes que vimos acima (passivo e ativo), em algum momento há um diálogo entre o subject e o STS do identity provider. Nestes casos, o diálogo é realizado para a emissão (Issue) de um token, que depois de emitido será utilizado para enviar para a aplicação (relying party) que está requerendo. Cada uma das operações expostas troca mensagens conhecidas como RST (Request Security Token) e RSTR (Request Security Token Response), especificadas pelo WS-Trust, que entre várias informações, incluem o tipo de token a ser emitido (que nada mais é que a versão do SAML que está sendo utilizada) e as claims que estão sendo solicitadas pela relying party.

Conclusão: No decorrer neste artigo, analisamos os ambientes possíveis que temos quando trabalhamos com este modelo de autenticação, incluindo o cenário federado. Além disso, analisamos como o fluxo das mensagens acontece nestes ambientes, detalhando em alto nível, os passos necessários para atingir o objetivo. Em futuros artigos, vamos explorar detalhadamente como implementar esse modelo em cada um cenários, analisando como configurar e manter um sistema que faz uso destes elementos.

Tags: , ,

CSD | Security | WIF

Uma nova forma de Autenticação/Autorização

by Israel Aece 17. February 2010 09:48

Grande parte das aplicações que desenvolvemos nos dias de hoje, exigem um alto nível de segurança, o que nos obriga a desenhar um sistema consistente e evolutivo, que permite de forma simples, manipulá-lo de acordo com as necessidades que surgem durante a vida do mesmo. Isso faz com que todos os desenvolvedores, além de se preocuparem com as regras de negócio, ainda precisam saber como lidar com dois aspectos importantes de toda aplicação: autenticação e autorização.

A autenticação consiste em saber quem o usuário é identificá-lo dentro do sistema; já a autorização, determina quais privilégios esse usuário tem dentro do sistema, e para isso, obrigatoriamente deverá ocorrer depois da autenticação, já que primeiro preciso saber quem ele é para depois conseguir encontrar os direitos que ele possui dentro do sistema.

Isso não seria uma tarefa difícil se não houvesse vários mecanismos diferentes, e que permitem configurar e gerenciar tanto a autenticação quanto a autorização. Cada um destes mecanismos tem como alvo um cenário distinto, e é complicado achar uma solução que atenda à todos os cenários em que a aplicação está exposta, tornando extremamente complicado gerenciar esses aspectos em um ambiente mais amplo, e que muitas vezes acarreta em duplicação de código e possíveis vulnerabilidades.

Além disso, é importante dizer que para cada forma de autenticação e autorização que utilizamos, temos que entender o mínimo sobre como ela funciona. Como disse antes, há varias opções que temos atualmente, como por exemplo: Kerberos, Forms Authentication, Url Authorization, certificados, etc. Cada uma delas tem suas peculiaridades, e exige de nós um entendimento mínimo para saber qual desses modelos se encaixa melhor com a nossa necessidade.

Em uma análise rápida, o Kerberos é a melhor opção se queremos autenticar um usuário que consome uma aplicação ASP.NET dentro da empresa, mas não traz nenhuma informação extra além de seu token. Esse modelo é fácil de implementar até que alguém, fora da empresa, também precise acessar essa mesma aplicação. Como esse modelo exige que o usuário esteja devidamente cadastrado no Active Directory da respectiva empresa, pessoas que estão além dela, não conseguirão acessá-la, e como sabemos, manter contas para usuários "voláteis", não é uma tarefa viável. Para entender a complexidade em que vivemos atualmente, veja a imagem abaixo:

Note que dentro do quadrado verde temos uma empresa qualquer, que por sua vez, possui vários funcionários. Cada funcionário, toda manhã, efetua o login no Windows, que vai até o controlador de domínio (Active Directory), valida o usuário, e caso seja válido, permite o acesso ao sistema operacional. Felizmente, na empresa que temos como exemplo acima, os desenvolvedores se preocuparam em reutilizar as credenciais do Windows para as aplicações que rodam dentro dela, onde uma delas é uma aplicação Windows (chamada de App) e que consome um serviço WCF, e a própria intranet, hospedada no IIS, e que também utiliza autenticação integrada ao Windows.

Agora repare que este mesmo usuário também acessa outras aplicações, que estão fora do domínio da empresa em que ele trabalha. O acesso a loja para comprar presentes, possui seu próprio sistema de autenticação e autorização, obrigando este usuário a criar uma conta ali. Já quando ele tenta acessar o site do banco, um certificado é exigido para que ele proceda com o login. E, como se não bastasse, a empresa em que ele trabalha mantém relações comerciais com outros parceiros, e cada um desses parceiros possui um sistema próprio de pedidos, e como já era de se esperar, cada um possui seu próprio esquema de autenticação e autorização.

Mas note que no caso do parceiro 1, a aplicação somente permite acesso para aqueles usuários que estão cadastrados dentro do AD da respectiva empresa, sendo assim, como acessá-la? Na maioria das vezes, o parceiro 1 cria uma aplicação simples, baseada em internet, e permite o acesso aos parceiros. Mas o que vai acontecer é que o parceiro 1 precisará, novamente, de uma nova forma para controlar os usuários que irão acessar tal aplicação, e muito provavalmente, irá recorrer à uma base de dados customizada para isso (se a aplicação for ASP.NET, podemos recorrer ao Membership). Mas essa solução dá início a outro problema, que ocorrerá no momento em que este usuário não fizer mais parte da minha empresa, pois terei que lembrar à quais parceiros ele tinha acesso, para assim removê-lo, já que ele não poderá continuar acessando, e isso ainda obrigará ao parceiro, criar uma interface (tela) para a administração de usuários.

Deixar para cada um implementar, de forma arbitrária, o modelo de autenticação e autorização, dá margem para que os desenvolvedores façam isso de qualquer modo, não se preocupando com pontos importantes, como por exemplo, o armazenamento das senhas de forma segura (hash), tráfego das credenciais através de um protocolo que não permita ninguém interceptar, evitar ataques de brutal-force, DoS, etc. Além destes problemas de infaestrutura, ainda temos as políticas de senhas, já que muitas aplicações não asseguram que a senha escolhida pelo usuário seja uma senha segura; a reutilização de senhas é um problema grave, pois para evitar o esquecimento, muitas pessoas utilizam a mesma senha para todas as aplicações que elas utilizam, e uma vez que isso cai em mãos erradas, o estrago pode ser irreparável. A reutilização é fácil até que você encontre aplicações com políticas de senha diferentes, pois o que pode ser uma senha válida para uma aplicação, não é para outra, obrigando a você escolher outra senha que se enquadre com as políticas desta aplicação, voltando assim, ao inferno das senhas diferentes.

Outro grande desejo que existe e que é difícil de implementar, é a capacidade de termos SSO (Single Sign-On) entre as aplicações, ou seja, a capacidade que damos ao usuário de se autenticar uma única vez, e assim poder acessar todas aquelas aplicações que confiam naquele autenticador. Algo mais ou menos como o que acontece com o Windows Live Id, onde você se autentica uma única vez, e tem acesso à todos os sites da Microsoft que são protegidos por ele.

Qual seria então o modelo ideal para resolver grande parte dos problemas que vimos acima? O modelo ideal seria a possibilidade de centralizar a autenticação em um único local, que determinará como ela deverá ser realizada e protegida. A centralização evitará com que toda a aplicação se preocupe com a lógica de autenticação, já que não competirá mais a ela esse papel. Os desenvolvedores podem se concentrar apenas no desenvolvimento da aplicação em si, com as regras de negócios, interfaces (telas), serviços, etc. Com a autenticação "externalizada", não precisamos mais misturar códigos de infraestrutura com códigos de regras de negócios, tornando a aplicação muito mais limpa, e, além disso, outro grande benefício com a refatoração é a facilidade que teremos em dividir o que compete ao desenvolvedor e o que compete ao administrador da rede.

O modelo que visa esse cenário é conhecido como Claims-Based Identity Model, ou seja, modelo de identidade baseado em claims, que traduzindo significa "afirmações". Nesse modelo, as aplicações que desenvolveremos não serão mais responsáveis por autenticar e muito menos por armazenar suas senhas. Para fazer uma analogia ao mundo real de como esse modelo trabalha, podemos recorrer ao exemplo da compra de algum produto através do cartão de crédito. Quando você vai pagar por esse produto, você fala que vai pagar com o cartão de crédito, mas a loja não confia em você, quando você diz: “Olha, eu tenho R$ 2.000,00 de crédito, pode me vender!”. Na verdade, o que a loja faz é "validar" aquele cartão que você apresentou, junto aquele que o emitiu, que é a operadora. É ela quem dirá se aquele cartão é ou não válido e dizer até o quanto você poderá gastar. A loja utilizará essas informações para proceder ou não com a venda.

Neste cenário descrito acima, note que a loja nada sabe sobre o cliente, mas confia em alguém que o avaliza. A partir daí, a loja geralmente faz o cadastro do cliente, colhendo o seu e-mail, endereço, etc., para mais tarde conseguir enviar um catálogo dos novos produtos. Da próxima vez que você vai comprar novamente a validação é realizada na operadora do cartão, mas seus dados cadastrais já estão catalogados, podendo ser apenas atualizados.

Outro ponto importante deste cenário, é que a autorização ainda deve ser realizada pela aplicação, ou seja, ela ainda é responsável por determinar o que usuário pode ou não acessar. Mas atualmente, os direitos de acessos que concedemos à determinadas funcionalidades dentro da aplicação, geralmente são determinadas por grupos, ou seja, se o usuário fizer parte do grupo Administradores, então ele pode ter acesso às contas corrente dos clientes. Mas no cenário descrito acima, ele vai além disso, já que a autorização é concedida baseando-se no limite do cartão de crédito.

Isso é mais um dos benefícios das aplicações baseadas em claims. As afirmações emitidas por alguém contra outro alguém, pode ser qualquer tipo de informação, e você refina o acesso através delas. Muitas vezes a autorização vai muito além dos grupos (simples strings) que o usuário pertence, pois há situações em que podemos negar ou conceder acesso de acordo com a data de nascimento, ou seja, se ele tiver mais do que 21 anos, então poderá acessar o conteúdo.

Para fazer com que tudo isso funcione, a Microsoft tem trabalhado em três produtos que, juntos ou separadamente, fornecerão grande parte do que precisamos para atingir o nosso objetivo, que é a centralização e o gerenciamento do processo de autenticação. Cada um desses produtos, implementam padrões de mercado, e por isso podem ser utilizados separadamente, ou melhor, podemos ter uma dessas ferramentas Microsoft falando com outras desenvolvidas pela IBM utilizando Java, pois todas falam o mesmo "idioma". A Microsoft criou a solução completa, composta de três produtos que estão listados abaixo, com suas respectivas descrições:

  • WIF - Windows Identity Foundation: Trata-se de um framework para a construção de aplicações baseadas em claims, e que abstrai toda a complexidade do processo de autenticação que é realizado seguindo uma série de padrões de mercado (WS-*). Além disso, a sua API traz uma série de tipos que habilitam a extensibilidade, podendo mais tarde, criar novas opções de autenticação.
  • ADFS 2.0 - Active Directory Federation Services 2.0: Uma ferramenta que ajuda os administradores de rede, a gerenciar o ambiente de autenticação que será fornecido por aquelas aplicações que queiram fazer uso do modelo baseado em claims. Essa ferramenta implementa os mesmos protocolos do WIF, permitindo assim que eles interajam.
  • Windows Cardspace 2.0: Uma ferramenta que é instalada nos clientes, como uma espécie de carteira virtual, que cataloga os cartões que podem ser enviados para as aplicações baseadas em claims. Trata-se de uma interface amigável que permite ao usuário final uma visualização simples do que ele tem e do que será efetivamente enviado à aplicação, podendo ele decidir se deve ou não proceder.

Conclusão: Com as informações acima, pudemos entender um pouco mais dos cenários de complexidade que possuímos e dos problemas que temos atualmente. Cada cenário exige um modelo diferente, mas que não atende a 100% dos casos. A finalidade deste artigo introdutório foi apresentar os desafios que temos, e quais soluções estão sendo oferecidas pela Microsoft, para poder tornar essa tarefa extremamente importante, em algo bem mais simples e reutilizável do que temos hoje em dia. A partir de uma série de artigos, vamos explorar cada uma das ferramentas e elementos que compõem este novo modelo de autenticação.

Tags: , , , ,

CSD | Security | WIF

Autenticação e Autorização no ASP.NET MVC

by Israel Aece 9. February 2010 08:58

Desde a primeira versão do ASP.NET, temos três formas de autenticação: Windows, Forms e Passport. A primeira opção consiste em permitir o acesso desde que o usuário faça parte do domínio/máquina. Essa opção torna o gerenciamento simples, facilita o Single Sign-On (SSO) interno, mas é útil em um ambiente controlado, como uma intranet. A opção Forms faz com que a aplicação se encarregue de gerenciar e autenticar seus usuários, baseando-se em um cookie que determinará quem é o usuário logado, e esta opção é a mais ideal para a internet. Finalmente, temos a Passport, que permitia autenticar o usuário utilizando o serviço de identidade da Microsoft, mas por haver um custo envolvido, acabou não evoluindo.

Com a vinda do ASP.NET MVC, grande parte dos serviços que são disponibilizados para o ASP.NET WebForms, já nasceram com o MVC ou estão aos poucos sendo adicionados à plataforma. Como já era de se esperar, a questão da autenticação e autorização já fazem parte desde a versão 1.0 do MVC, onde podemos utilizar a autenticação Windows ou Forms, mudando ligeiramente como devemos proceder para configurá-los e utilizá-los.

Para definir se vamos utilizar o modelo de autenticação Windows ou Forms, devemos recorrer ao elemento authentication no arquivo Web.config, como já fazíamos anteriormente. O atributo mode determina qual a opção de autenticação, e um sub-elemento chamado forms, nos permite customizar a autenticação baseada em Forms. O exemplo abaixo ilustra a configuração inicial:

<authentication mode="Forms">
    <forms loginUrl="Authentication/LogOn" />
</authentication>

Aqui há uma mudança radical aqui. O ASP.NET WebForms é baseado em arquivos, ou seja, todas as requisições, sejam elas da aplicação ou da infraestrutura (como é o caso aqui), o alvo sempre será um arquivo físico, com extensão *.aspx. Já no ASP.NET MVC, o alvo será sempre uma ação, que nada mais é que um método dentro de uma classe, conhecida como Controller. No exemplo acima, o controller é uma classe chamada AuthenticationController e dentro dele há um método chamado LogOn. É dentro deste método que você colocará todos os passos necessários para autenticar o usuário. Geralmente esse método deve receber o login e a senha, mas eventualmente poderá ter algo mais rebuscado.

Quando tentamos acessar um recurso protegido através do FormsAuthentication, ele automaticamente redireciona o usuário para a página de autenticação configurada acima, embutindo na URL uma query string chamada ReturnUrl, com o caminho (do arquivo ou da ação) que tentamos acessar. Esse parâmetro é útil para depois que validar o usuário, redirecioná-lo para a mesma seção que ele solicitou antes de efetuar a autenticação. Sendo assim, é necessário que o método que efetua a autenticação do usuário, também receba esse parâmetro.

Em tempo, a versão 2.0 do ASP.NET MVC fornece um atributo chamado HttpPostAttribute, que permite você decorar uma determinada ação, para que ela seja executada somente via POST, garantindo assim que ela seja executada somente através desta forma. Para ilustrar, o método LogOn fica da seguinte forma:

public class AuthenticationController : Controller
{
    [HttpPost]
    public ActionResult LogOn(string userName, string password, string returnUrl)
    {
        //...
    }
}

É importante dizer que se você estiver utilizando a autenticação Windows, nada disso é necessário, já que a autenticação acaba sendo feita automaticamente pelo IIS/ASP.NET.

Se precisar saber qual usuário está autenticado naquele momento, você pode recorrer à propriedade User, exposta pela classe Controller. Assim como já acontece com a classe Page do WebForms, essa propriedade retorna a instância de uma classe que implementa a interface IPrincipal, fornecendo acesso ao username do usuário atual, e um método chamado IsInRole, caso você precisa refinar ainda mais a autorização. Para maiores detalhes sobre essa interface, consulte este o capítulo 9 deste artigo.

Autorização

A autorização pode continuar sendo realizada da mesma forma que fazíamos antes, ou seja, recorrendo ao arquivo de configuração (Web.config) para especificar as regras de autorização. A diferença se dá também pelo fato de que o MVC é baseado em ações, e ao invés de determinar páginas e/ou diretórios nessas regras, utilizaremos as ações para refinar o acesso dos usuários. Se temos um controller chamado MonitorController e dentro dele uma ação (método) chamada VisualizarItens, e o acesso à ela somente pudesse ser feita por usuários autenticados, a regra ficaria da seguinte forma:

<location path="Monitor/VisualizarItens">
    <system.web>
        <authorization>
            <deny users="?" />
        </authorization>
    </system.web>
</location>

Caso você precise permitir o acesso à uma determinada ação para somente aqueles usuários que fazem parte de um determinado grupo (role), você pode configurar a regra da seguinte forma:

<location path="Monitor/VisualizarItens">
    <system.web>
        <authorization>
            <allow roles="Financeiro" />
        </authorization>
    </system.web>
</location>

Apesar desta técnica funcionar, é difícil de manter. Para cada novo controller ou ação que são criados, você precisa se preocupar em olhar para o arquivo de configuração e configurar quem poderá ou não ter acesso. O problema desta técnica é que se você esquecer, usuários que talvez não deveriam ter acesso, acabarão visualizando. No ASP.NET MVC há uma outra forma de se trabalhar, também declarativa, mas recorrendo à atributos dentro dos controllers/ações, facilitando a centralização e manutenção das regras de acesso.

Para isso, o ASP.NET MVC fornece um atributo chamado AuthorizeAttribute que podemos decorar em classes (controllers) ou em métodos (ações). Esse atributo fornece duas propriedades do tipo string: Users e Roles. Na primeira propriedade, especificamos os usuários (separados por vírgula) que podem ter acesso aquele membro (classe ou método) em que o atributo está sendo aplicado. Já a segunda propriedade, Roles, permite especificarmos os grupos (separados por vírgula), onde somente usuários que estão dentro de um deles é que podem ter acesso ao recurso. Utilizar a propriedade Users não é o mais ideal, porque usuários são muito "voláteis", e em pouco tempo, essa regra provavelmente não fará mais sentido.

Como dito acima, podemos aplicar esse atributo em nível de controller, e assim, as suas respectivas ações irão exigir que aquela regra seja atendida antes de permitir o acesso. No primeiro exemplo, aplicamos este atributo em nível de controller, e todas as ações exigirão que o usuário esteja autenticado:

[Authorize]
public class MonitorController : Controller
{
    public ActionResult VisualizarItens()
    {
        //...
    }

    public ActionResult RecuperarIndice()
    {
        //...
    }
}

Já o segundo exemplo, fará com que o controller seja acessado somente por usuários devidamente autenticados, mas isso não é o suficiente para o método VisualizarItens, que além disso, exige que o usuário esteja contido no grupo Financeiro:

[Authorize]
public class MonitorController : Controller
{
    [Authorize(Roles = "Financeiro")]
    public ActionResult VisualizarItens()
    {
        //...
    }

    public ActionResult RecuperarIndice()
    {
        //...
    }
}

Caso você tenha uma regra de validação muito mais complexa do que isso, nada impede você de criar um filtro para efetuar essa customização, fazendo que a autorização siga os passos necessários para atender essa regra predefinida. E para finalizar, lembre-se que o MVC trata os métodos como "opt-out", ou seja, por padrão, todos os métodos públicos estão acessíveis. Se mesmo com a autenticação e/ou autorização ele jamais deverá ser invocado, então você deve decorá-lo com o atributo NonActionAttribute.

Membership e Roles

A partir da versão 2.0 do ASP.NET, uma série de novos serviços foram adicionados, e entre eles temos o Memberhip Provider e Role Provider, algo que já falei extensivamente nestes outros artigos. Felizmente podemos continuar utilizando esses serviços, mas com um pouco mais de cautela.

Esses serviços seguem um padrão conhecido como Provider Model, e ao utilizar o Membership ou Roles, eu estou trabalhando de forma independente de repositório; posso trabalhar com SQL Server, Oracle ou Xml, apenas configurando os providers através do arquivo Web.config. Mas apesar desses serviços seguirem um padrão de extensibilidade bem definido, isso não quer dizer que é uma boa prática você pode utilizá-los diretamente dentro dos controllers.

É importante dizer que ao criar aplicações MVC, a testabilidade deve estar em foco, e utilizar as classes Membership ou Roles dentro dos controllers, o tornará dependente destas implementações, ou melhor, destes serviços, e que por sua vez, estão fortemente acoplados ao ASP.NET. Como disse anteriormente, a arquitetura do provider model é bem flexível, mas para criar uma versão "fake" é complexo demais, pois há uma infinidade de funcinalidades que as vezes serão desnecessárias. Além disso, utilizar essas classes torna meu controller dependente deste tipo de serviço mas, eventualmente, eu tenho o meu próprio repositório de usuários e suas respectivas permissões, e quero fazer uso deles ao invés daqueles que o ASP.NET fornece.

A classe AccountController

Quando criamos um novo projeto MVC, por padrão, uma série de recursos já são adicionados, e entre eles, temos a classe (controller) chamada AccountController. Essa classe foi desenhada para servir como um wrapper para os serviços de Membership e FormsAuthentication. Há vários problemas com essa classe, e o primeiro deles é aquele que descrevi acima, que é a forte dependência da API do Membership. Apesar da Microsoft ter refatorado o código e criado uma interface chamada IMembershipService, há ainda alguns tipos que estão sendo utilizados, como é o caso do enumerador MembershipCreateStatus. Além disso, essa classe ainda viola o princípio de SRP (Single Responsability Principle), que faz com que ela faça muito mais coisas do que realmente deveria.

Conclusão: O fato da Microsoft tem se preocupado em manter grande parte dos recursos do WebForms no MVC, é importante que você analise cuidadosamente como incorporá-lo ao MVC para não comprometer um dos maiores benefícios do MVC, que são os testes. É importante notar que grande parte do conhecimento adquirido no ASP.NET WebForms, acaba sendo reutilizado aqui, apenas com ligeiras mudanças devido à arquitetura do MVC.

Tags: , , ,

ASP.NET | Security

UserName e Password em serviços WCF para Silverlight

by Israel Aece 10. January 2010 13:19

Como sabemos, podemos utilizar usuário e senha para autenticar um usuário em um serviço WCF, mas há algumas regras que precisam ser seguidas, como eu já comentei neste post. Uma dessas regras é que não podemos trafegar essas informações sem alguma criptografia. Mesmo que o serviço seja exposto via HTTP, a utilização de um certificado (não do HTTPS), fará este papel.

Eu mostrei como customizar a autenticação neste artigo, e note que estou usando um certificado e o serviço não está disponível através do HTTPS. Além disso, o binding que está sendo utilizado é o WSHttpBinding, que sabe como efetuar a criptografia das informações. Agora imagine que este serviço pode ser consumido por uma aplicação Silverlight. Infelizmente o WCF do Silverlight não tem suporte ao WSHttpBinding, somente ao BasicHttpBinding, e mais recentemente, via NetTcpBinding.

Mas como podemos proceder neste caso? Para poder disponibilizar serviços WCF para uma aplicação Silverlight, precisaremos recorrer a segurança em nível de transporte, para garantir a proteção. Para isso, estamos obrigados à expor o serviço através de HTTPS, e as credenciais estarão protegidas dentro da mensagem. Mas antes da versão 3.0 do Silverlight, era díficil expor e/ou consumir serviços WCF que exigem usuário e senha, já que você precisava criar headers customizados para acomodar essas informações, assim como era (ou ainda é) feito pelos ASP.NET Web Services.

A partir da versão 3.0, o Microsoft introduziu mais uma opção no binding BasicHttpBinding (do Silverlight), que é a TransportWithMessageCredential. Com esta opção definida, o serviço deverá ser disponibilizado via HTTPS, e as credencias viajarão dentro da mensagem, sem precisarmos escrever qualquer código adicional. Felizmente, quando efetuamos a referência em uma aplicação Silverlight, o proxy gerado já disponibiliza a propriedade ClientCredentials, onde podemos atribuir o usuário e senha, que eventualmente vem de controles TextBoxes da tela.

A configuração do serviço é relativamente simples. O primeiro passo é criar o validador customizado de usuários, que nada mais é do que uma classe que herda de UserNamePasswordValidator, e dentro do método Validate, você escreve a regra para buscar e validar o usuário. Para maiores detalhes de como isso funciona, consulte este artigo. Depois note também que definimos a segurança como sendo TransportWithMessageCredential, conforme comentamos acima.

<system.serviceModel>
  <services>
    <service name="ServicoDeClientes"
             behaviorConfiguration="bhr">
      <endpoint name="srv"
                binding="basicHttpBinding"
                contract="IContrato"
                bindingConfiguration="bc"/>
    </service>
  </services>
  <bindings>
    <basicHttpBinding>
      <binding name="bc">
        <security mode="TransportWithMessageCredential"/>
      </binding>
    </basicHttpBinding>
  </bindings>
  <behaviors>
    <serviceBehaviors>
      <behavior name="bhr">
        <serviceMetadata httpsGetEnabled="true"/>
        <serviceCredentials>
          <userNameAuthentication
            customUserNamePasswordValidatorType="ValidadorDeUsuarios"
            userNamePasswordValidationMode="Custom"/>
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

Depois da configuração do serviço, o publicamos e, consequentemente, referenciamos na aplicação cliente (Silverlight). Ao referenciá-lo, já podemos notar que o arquivo de configuração já está devidamente especificado com a mesma opção de segurança: TransportWithMessageCredential.

<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="https://localhost/SLTest/ServicoDeClientes.svc"
                binding="basicHttpBinding"
                bindingConfiguration="srv"
                contract="ServicoDeClientes.IContrato"
                name="srv" />
    </client>
    <bindings>
      <basicHttpBinding>
        <binding name="srv">
          <security mode="TransportWithMessageCredential" />
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Com isso, tudo o que nos resta é instanciar o proxy e definir a propriedade ClientCredentials, que receberá os credenciais do usuário. Depois de configurado, você já está apto a chamar qualquer operação exposta pelo serviço, que antes dela ser efetivamente executada, o runtime do WCF avaliará as credenciais informadas, se certfificando de que são válidas, e somente a partir daí, irá invocar o método.

ServicoDeClientes.ContratoClient p = new ServicoDeClientes.ContratoClient();
p.ClientCredentials.UserName.UserName = this.Login.Text;
p.ClientCredentials.UserName.Password = this.Password.Text;

p.PingCompleted += (s, args) => this.textBlock1.Text = args.Result;
p.PingAsync();

A chamada da operação de forma assíncrona, nada tem nada a ver com a questão de segurança. Isso é apenas uma característica do WCF do Silverlight, e mesmo que não houvesse qualquer nível de segurança envolvido, a forma para invocar uma operação seria a mesma.

Tags: , ,

Security | WCF

UserNames com autenticação Basic

by Israel Aece 3. December 2009 07:51

Há algum tempo em comentei aqui sobre as opções de segurança no WCF, e mais tarde, como customizá-la para receber e validar usernames e passwords em um repositório qualquer. Antes da versão 3.5 do WCF, essa customização somente trabalha com segurança em nível de mensagem. Com o 3.5, a Microsoft adicionou esse suporte para também trabalhar com segurança em nível de transporte.

Isso é útil em cenários onde queremos utilizar a autenticação Basic do HTTP como meio de fornecimento das credenciais, mas ao invés da conta informada existir e de ser validada dentro do sistema operacional, vamos customizar essa regra, utilizando um repositório customizado, como uma base de dados, evitando que os clientes tenham, obrigatoriamente, contas cadastradas dentro do Windows.

Para que isso funcione, a configuração de segurança do binding deverá ser definida como Transport, e a forma de fornecimento de credenciais como Basic. Além disso, você também precisará implementar um validador customizado, herdando da classe abstrata UserNamePasswordValidator, como eu já mostrei neste artigo. Abaixo temos a configuração do lado do serviço:

<bindings>
  <basicHttpBinding>
    <binding name="bindingConfig">
      <security mode="Transport">
        <transport clientCredentialType="Basic"/>
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

Apenas lembre-se de que o Basic faz com que as credenciais sejam trafegadas codificadas e não criptografadas, e justamente por isso, estamos recorrendo à segurança em nível transporte (que neste caso é o HTTPS), para garantir a integridade e confidencialidade da mensagem e das credenciais. Para informar as credenciais do lado do cliente, você faz da mesma forma que antes:

using (Servico p = new Servico)
{
    p.ClientCredentials.UserName.UserName = "IA";
    p.ClientCredentials.UserName.Password = "123";

    Console.WriteLine(p.Ping("teste"));
}

O único problema desta técnica, é que você não poderá hospedar o serviço no IIS. Isso se deve ao fato de que quando o atributo clientCredentialType estiver definido como Basic, o IIS fará a verificação durante o carregamento do serviço, analisando se o modo de autenticação Basic está habilitado para aquele diretório virtual, e caso contrário, uma exceção do tipo NotSupportedException será disparada, informando que o modo Basic não está habilitado.

Só que se você habilitá-lo, o problema volta a acontecer, pois a ideia por trás desta técnica, é utilizar o Basic como forma de envio das credenciais, mas não de validá-las, e com isso, se você colocar um usuário e senha que não existe dentro do Windows, ele não deixará você acessar o serviço, e mesmo que você digite um usuário/senha válidos, a implementação da classe UserNamePasswordValidator nunca será executada. Para que isso seja possível, você precisa modificar o pipeline do ASP.NET, acoplando módulos customizados que farão esse trabalho.

Tags: , ,

Security | WCF

Proteção da Página de Logout

by Israel Aece 12. August 2009 09:13

Quando nossa aplicação ASP.NET exige o controle da autenticação e autorização para conceder ou negar acessos, é muito comum criarmos uma página chamada Logout.aspx, que no seu evento Load efetuamos a "limpeza". Essa "limpeza" consiste em remover o cookie de autenticação (método SignOut), apagar eventuais variáveis de sessão e alguma outra finalização específica daquela aplicação.

O problema reside quando você coloca essa página dentro de um diretório que também está protegido. Imagine que tenho um diretório dentro da aplicação chamado AreaRestrita. Dentro dele temos as várias páginas da aplicação, incluindo a Logout.aspx que, provavelmente, em todas as páginas haverá um link apontando para ela. Na configuração, especificamos que somente usuários autenticados poderão ter acesso ao conteúdo - páginas - deste diretório.

Neste caso, se o usuário efetuar o login, e deixar a aplicação aberta, sem navegar por ela, o cookie irá expirar. Como ele não tem mais nada para efetuar na aplicação, ele clica no link Logout. Como qualquer página do diretório está protegida contra usuários não autenticados, ao tentar acessar a página Logout.aspx, o usuário será redirecionado para a página de Login, para que ele informe as credenciais de acesso, para depois acessar a página de Logout. Aparentemente o usuário saiu da aplicação, mas a página Logout.aspx não foi executada e, consequentemente, toda a "limpeza" que você faria, não irá ocorrer.

Mesmo que utilize outra técnica, como um LinkButton e a "limpeza" sendo realizada em seu evento Click, você irá esbarrar no mesmo problema. A solução aqui é manter a página Logout.aspx acessível a todos os usuários, definindo isso no arquivo de configuração da aplicação (Web.config), assim como é mostrado abaixo:

<?xml version="1.0"?>
<configuration>
  <location path="Logout.aspx">
    <system.web>
      <authorization>
        <allow users="*"/>
      </authorization>
    </system.web>
  </location>
</configuration>

Particularmente não gosto muito da necessidade de ter que criar uma página para isso. O Logout não deveria ser uma página, mas sim uma ação a ser executada. Ponto positivo para o MVC, que nos permite criar um método (Action) chamado Logout, e invocá-lo a partir de um controle da View, sem a necessidade de ter toda a complexidade de uma página ASPX por trás de uma tarefa, que na maioria das vezes, é muito simples.

Tags:

ASP.NET | Security

Página de Acesso Negado

by Israel Aece 4. August 2009 12:55

Quando utilizamos FormsAuthentication para proteger uma determina página ou diretório, há um comportamento que talvez não agrade a maioria das pessoas. Suponhamos que você faça o login digitando o seu nome de usuário e senha. Ao validar esse usuário em algum repositório, o ASP.NET automaticamente redireciona o usuário para a página/diretório que ele tentou acessar inicialmente.

A partir daí, se você utilizar roles para restringir o acesso à alguma página/diretório dentro da aplicação e o usuário não esteja dentro dela, ele será redirecionado - novamente - para a página de login. Talvez isso não seja o ideal, já que o mais coerente seria redirecionar o usuário para uma página que contenha uma mensagem mais amigável e próxima ao que realmente aconteceu, algo como: "Você não tem permissão para acessar o recurso solicitado. Contate o Administrator.".

Quando utilizamos a UrlAuthorization (que já é o padrão), a cada requisição ela verifica se o usuário que acessa uma determinada página, possui a permissão para isso. Caso a configuração no arquivo Web.config diz que, para acessar aquela página, é necessário que o usuário esteja na role de "Administradores", e ele por sua vez não estiver, o UrlAuthorization define a propriedade StatusCode da resposta do HTTP para 401 (que significa "Acesso Negado"). O FormsAuthentication (mais precisamente, o FormsAuthenticationModule) se vincula ao evento EndRequest, e dentro dele analisa se a propriedade StatusCode foi definida como 401. Caso tenha sido, o FormsAuthenticationModule redireciona o usuário para a página de login, trocando o StatusCode para 302 ("Redirecionar").

Para mudar este comportamento, podemos criar um módulo e nos vincularmos ao evento EndRequest. Dentro deste evento, podemos analisar se a propriedade StatusCode foi definida como 401, e se estiver, redirecionar para uma página que informa exibe a mensagem correta ao invés da página de login, melhorando assim a navegabilidade da aplicação. O módulo é extremamente simples, assim como podemos notar abaixo:

public class AccessDeniedModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.EndRequest += new EventHandler(context_EndRequest);
    }

    private void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        if (app.Response.StatusCode == 401 && app.User.Identity.IsAuthenticated)
        {
            app.Response.Redirect("~/AreaRestrita/AcessoNegado.aspx");
        }
    }

    public void Dispose() { }
}

Como já sabemos, para que um módulo funcione, precisamos acoplá-lo na execução do ASP.NET, e fazemos isso através do elemento <httpModules />. Mas este módulo deve ter um cuidado especial para que funcione adequadamente. Já existem diversos módulos em execução pelo ASP.NET, e um deles é justamente o FormsAuthenticationModule. Se simplesmente adicionarmos o módulo que criamos na coleção de módulos da aplicação, o StatusCode vai chegar sempre como 302, pois a requisição primeiramente passará pelo módulo FormsAuthenticationModule, que como vimos acima, faz essa mudança. Com isso, a nossa condicional sempre irá falhar, fazendo com que o usuário seja redirecionado para a página de login. A dica aqui é ir até o arquivo Web.config que está em nível de servidor (%windir%\Microsoft .NET\Framework\VERSÃO\CONFIG\), copiar a coleção de módulos e colar no arquivo Web.config da aplicação, como podemos ver abaixo:

<httpModules>
  <clear />
  <add name="ScriptModule"
       type="System.Web.Handlers.ScriptModule, System.Web.Extensions"/>
  <add name="OutputCache"
       type="System.Web.Caching.OutputCacheModule"/>
  <add name="Session"
       type="System.Web.SessionState.SessionStateModule"/>
  <add name="AccessDeniedModule"
       type="AccessDeniedModule"/>

  <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="AnonymousIdentification"
       type="System.Web.Security.AnonymousIdentificationModule"/>
  <add name="Profile"
       type="System.Web.Profile.ProfileModule"/>
</httpModules>

Depois de colocar os módulos no arquivo Web.config da aplicação, precisamos colocar o elemento <clear /> para que não duplique a execução dos módulos. E para finalizar, o módulo que criamos deverá ser colocado antes do módulo do FormsAuthentication para que funcione como o esperado. Aproveitando a oportunidade, é importante que você deixe somente os módulos que a sua aplicação realmente utiliza, ganhando alguma performance, já que evitará passar por passos desnecessários. Para maiores detalhes sobre isso, consulte este artigo.

Tags: ,

ASP.NET | Security

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