TechEd Brasil 2010 - Palestras

by Israel Aece 3. August 2010 23:12

Nos dias 13, 14 e 15 de Setembro, haverá em São Paulo o evento TechEd 2010. Este evento reune uma grande quantidade de palestras voltadas para as tecnologias mais atuais, e além disso, vários profissionais que trabalham com tecnologias Microsoft. Neste ano, estarei efetuando duas palestras, quais estão listadas abaixo, seguida de sua respectiva descrição:

Título: Visão Geral do Windows Identity Foundation (WIF)
Código: SIA303
Palestrante: Israel Aece
Nível: 300
Descritivo: Autenticação e Autorização são duas preocupações que existem em toda e qualquer aplicação. Dependendo do tipo de aplicação que está sendo construída, há uma forma diferente de você implementar essas funcionalidades, que exige o conhecimento específico da respectiva API que cada uma fornece, e como se isso não bastasse, ainda precisamos nos atentar a alguns detalhes que são comuns para qualquer situação, o que pode tornar certos aspectos redundantes. A finalidade desta palestra é abordar o novo framework da Microsoft chamado Windows Identity Foundation (WIF), qual podemos acoplar as nossas aplicações, para tornar essas tarefas árduas em algo bem mais simples.

Título: Implementando Serviços RESTful usando o Microsoft .NET Framework
Código: DEV305
Palestrante: Israel Aece
Nível: 300
Descritivo: Nesta sessão, aprenda como desenvolvedores .NET podem reutilizar seu conhecimento do Windows Communication Foundation (WCF) para aproveitar as ferramentas integradas e extensibilidade de um framework de WCF único, incluindo WCF WebHttp Services para RESTful Services, WCF Data Services e OData e WCF RIA Services para o desenvolvimento do início ao fim de aplicações Microsoft Silverlight.

Tags: , , ,

WCF | WIF

Consumindo um STS diretamente

by Israel Aece 28. July 2010 16:46

No artigo anterior eu mostrei como construir um serviço WCF que faz uso do WIF para terceirizar o processo de autenticação. Lá criamos um serviço de STS utilizando os recursos fornecidos pela própria API do WIF. Como havia dito anteriormente, este serviço especial fornecido pelo WIF, nada mais é que um serviço WCF, mas que expõe um conjunto de operações específicas para lidar com a autenticação de usuários.

Quando referenciamos o serviço em uma aplicação cliente, o arquivo de configuração já é montado com todas as entradas necessárias para que o WCF em conjunto com o WIF, façam todo o processo de autenticação, e depois disso, já podemos efetuar as chamadas para as operações que o serviço (relying party) fornece. Apesar de toda a mágica ser realizada internamente, podemos explicitamente consultar o serviço de STS, para que possamos solicitar a emissão, validação ou cancelamento de um token para um determinado usuário.

Como o serviço de STS é baseado em WCF, podemos utilizar a estrutura cliente do WCF para acessá-lo. Mas para facilitar ainda mais, a Microsoft criou uma versão específica dessa estrutura, expondo versões das mesmas classes, mas voltadas para o consumo de um serviço específico de STS. Internamente essas classes substituem o comportamento padrão, acoplando os recursos fornecidos pelo WIF. A finalidade deste artigo é mostrar o conjunto de classes que temos para efetuar essa comunicação.

A primeira classe que temos é a WSTrustChannelFactory. Essa classe é responsável por criar os famosos proxies que são utilizados pelos clientes para enviar as mensagens. Mas pelo prefixo ao nome desta classe, jé podemos perceber que ela gera toda a infraestrutura para a comunicação em cima do protocolo WS-Trust, qual já discutimos em artigos anteriores. Assim como em um serviço normal, em seu construtor precisamos informar o binding e o endereço para o serviço de STS.

Aqui entra em cena alguns novos bindings, que são utilizados exclusivamente para a comunicação com serviços de STS, e que fornecem configurações pertinentes à forma de autenticação que o usuário fará. Todos os bindings herdam da classe abstrata WSTrustBindingBase, e estão debaixo do namespace Microsoft.IdentityModel.Protocols.WSTrust.Bindings. Abaixo temos a lista com cada binding suportado e sua respectiva finalidade:

  • CertificateWSTrustBinding: Permite ao cliente apresentar um certificado para a autenticação.
  • IssuedTokenWSTrustBinding: Permite ao cliente apresentar um IssuedToken para a autenticação.
  • KerberosWSTrustBinding: Permite ao cliente apresentar um token Kerberos para a autenticação.
  • UserNameWSTrustBinding: Permite ao cliente apresentar um login e senha para a autenticação.
  • WindowsWSTrustBinding: Permite ao cliente utilizar as credenciais do Windows para a autenticação.

Depois do binding, temos que definir o certificado (chave pública) que utilizaremos. Essa chave será utilizada para proteger a mensagem de envio ao serviço de STS, garantindo que somente a chave privada correspondente consigará acessar a informação, e esta, por sua vez, estará de posse do STS. Em seguida temos a definição do nome do usuário e senha, que serão encaminhados para o serviço de STS, que serão utilizadas por ele para validar o usuário (UserNameSecurityTokenHandler).

Com a factory configurada, agora podemos recorrer ao método CreateChannel, que retornará o canal de comunicação configurado com o STS. Esse método retornará o proxy, que implementará o contrato IWSTrustChannelContract. Como podemos perceber, esse contrato disponibilizará os métodos para emissão, validação e cancelamento de tokens. O método que veremos aqui é o Issue, que recebe como parâmetro a instância da classe RequestSecurityToken (RST), que parametriza alguma ação exposta pelo contrato. A configuração desta classe exige informarmos o endereço da relying party que queremos acessar (AppliesTo), que determina se o STS pode ou não emitir o token para ela. Abaixo podemos visualizar o código que comentamos acima:

private const string STS_ADDRESS = "http://IsraelAeceNB2:9010/sts";
private const string SRV_ADDRESS = "http://IsraelAeceNB2:9000/ServicoDeCredito";

private static SecurityToken EmitirToken()
{
    using (WSTrustChannelFactory af =
        new WSTrustChannelFactory(
            new UserNameWSTrustBinding(SecurityMode.Message),
            new EndpointAddress(
                new Uri(STS_ADDRESS),
                EndpointIdentity.CreateDnsIdentity("Autenticador"))))
    {
        af.Credentials.ServiceCertificate.SetDefaultCertificate(
            "CN=Autenticador", StoreLocation.LocalMachine, StoreName.My);

        af.Credentials.UserName.UserName = "Israel";
        af.Credentials.UserName.Password = "P@ssw0rd";

        IWSTrustChannelContract channel = af.CreateChannel();
        RequestSecurityTokenResponse response = null;

        var token = channel.Issue(new RequestSecurityToken()
        {
            RequestType = RequestTypes.Issue,
            AppliesTo = new EndpointAddress(SRV_ADDRESS)
        }, out response);

        return token;
    }
}

Por último, e não menos importante, temos o retorno do método Issue. Ele retorna a instância de uma classe que herda direta ou indiretamente da classe SecurityToken, correspondendo ao token gerado pelo serviço de STS, caso a autentição tenha resultado com sucesso. É este token que informaremos para o serviço antes de efetuar a chamada para as operações. Opcionalmente você pode capturar a mensagem de resposta do processo de autenticação, e para isso, você pode utilizar uma sobrecarga que há no método Issue, que permite especificar através de um parâmetro de saída uma classe do tipo RequestSecurityTokenResponse (RSTR), e com isso ter acesso a informações como tempo de vida, tipo de autenticação, tipo do token gerado, etc.

Com o token gerado, agora nos resta passá-lo para o serviço, para que o mesmo o utilize  para permitir a execução da operação que desejarmos invocar. Para a criação do serviço, vamos também recorrer à uma factory, definindo em seu tipo genérico o contrato do serviço. Em seu construtor, passamos a instância do binding WS2007FederationHttpBinding, que permite dialogarmos com um serviço que suporta este tipo de autenticação (via STS). Além dele, ainda é necessário informarmos o endereço até o serviço, assim como já fazemos quando desejamos invocar qualquer serviço WCF. Depois deste objeto criado, precisamos configurar alguns parâmetros que são necessários para estabelecer a ligação entre o cliente e o serviço.

Essas configurações estão todas concentradas na propriedade Credentials, que é exposta pela classe ChannelFactory<TChannel>. Só que antes de analisar as propriedades que ela fornece, precisamos entender o comportamento deste binding. Como ele exige um token para enviar ao serviço, é necessário informarmos ele antes de efetuar a requisição para qualquer operação. A propriedade SupportInteractive tem a finalidade de exibir ou não a tela do Windows Cardspace, solicitando que o usuário informe este token. No cenário deste exemplo, o token já foi emitido, devido a uma solicitação explícita ao serviço de STS, e justamente por isso, devemos evitar que essa tela seja exibida, definindo-a como False.

Em seguida, especificamos a chave pública que foi fornecida pelo serviço, garantindo que a mensagem que chegará para ele venha devidamente protegida. A propriedade CertificateValidationMode se faz necessária porque precisamos desabilitar a validação do certificado, já que ele foi criado apenas para testes. Para finalizar a configuração, invocamos o método de estensão chamado ConfigureChannelFactory, que é fornecido através da classe ChannelFactoryOperations, que por sua vez, está debaixo do namespace Microsoft.IdentityModel.Protocols.WSTrust.

Essa classe ainda fornece mais um método (de estensão) importante para o nosso exemplo, que é o CreateChannelWithIssuedToken. Este método recebe como parâmetro a instância de um token, que é representado pela classe SecurityToken, que criamos no passo acima, retornando o proxy de comunicação com o serviço. É neste momento que informarmos ao serviço o token que será utilizado para efetuar a chamada para as operações dele. O código abaixo ilustra toda essa configuração, incluindo as chamadas para as operações expostas pelo serviço.

private static void InvocarServico(SecurityToken token)
{
    using (ChannelFactory<IConsultasFinanceiras> sf =
        new ChannelFactory<IConsultasFinanceiras>(
            new WS2007FederationHttpBinding(), 
            new EndpointAddress(
                new Uri(SRV_ADDRESS),
                EndpointIdentity.CreateDnsIdentity("Servico"))))
    {
        sf.Credentials.SupportInteractive = false;
        sf.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
            X509CertificateValidationMode.PeerTrust;
        sf.Credentials.ServiceCertificate.SetDefaultCertificate(
            "CN=Servico", StoreLocation.LocalMachine, StoreName.My);
        sf.ConfigureChannelFactory();

        var channel = sf.CreateChannelWithIssuedToken(token);
        channel.DefinirNovoLimiteDeCredito(10, 1000);
        Console.WriteLine(channel.RecuperarLimiteDeCredito(1000));
    }
}

Como podemos notar no exemplo acima, a chamada para o serviço está totalmente independente do modelo de  autenticação. Tudo o que precisa fazer é informar o token gerado pelo STS, sem ter ideia de como a autenticação foi realizada. Amanhã, o modelo de autenticação exigido pelo STS muda e o consumo do serviço não sofrerá qualquer alteração.

Conclusão: Neste artigo pudemos desvendar todo o procedimento que é reliazado internamente pelo WIF quando tentamos consumir um serviço WCF exige que a autenticação seja realizada por um STS. Agora coordenamos isso manualmente, o que nos dá certa flexibilidade, como por exemplo, em um ambiente passivo, evitar o redirecionamento para o STS para autenticar o usuário, onde poderíamos fornecer uma página local de login, e através desta alternativa, consumir o STS requisitando a emissão do token e, consequentemente, reutilizar o token durante toda a sesão do usuário.

Tags: , ,

Security | WCF | WIF

Autenticação via Claims no WCF

by Israel Aece 13. July 2010 22:33

Nos artigos anteriores, falei sobre a proposta da Microsoft para tentar unificar o sistema de autenticação das aplicações. Nos dois primeiros artigos, comentei um pouco sobre os problemas existentes e a teoria que circunda qualquer Identity MetaSystem. Na sequência, introduzi o WIF e alguns tipos que são comuns para qualquer tipo de aplicação que fará uso deste novo framework. A finalidade deste artigo é mostrar como podemos aplicar o WIF em serviços WCF, analisando tudo o que é necessário para colocar isso em funcionamento, incluindo a criação de um STS customizado para testes.

Em um artigo anterior, vimos como podemos implementar o WIF à aplicações ASP.NET, que recorrem ao navegador para serem apresentadas. Com isso, o WIF ou qualquer outro framework de autenticação, faz uso das funcionalidades que o navegador disponibiliza, para assim, coordenar de forma passiva, tudo o que é necessário para efetuar a autenticação em uma segunda aplicação.

Quando falamos em autenticação de serviços, o processo muda um pouco, pois geralmente eles são acessados através de uma aplicação cliente, que muitas vezes deve se identificar antes de consumir as funcionalidades que ele expõe. Neste cenário podemos perceber que não há o navegador, e sim uma aplicação (na maioria das vezes, desktop (Windows Forms ou WPF)) que consomem o serviço.

No artigo anterior discutimos o ambiente passivo, e aqui entra o ambiente ativo. O nome se deve ao fato de que a aplicação cliente coordena ativamente o processo de autenticação do usuário quando ele, por sua vez, precisar acessar um serviço que exija que o usuário esteja devidamente autenticado para poder acessá-lo. Como já acontecia anteriomente, os serviços que compõem a sua estrutura, pode seguramente confiar em um orgão que efetua a autenticação, evitando assim configurarmos serviço por serviço todo o processo necessário para a autenticação do usuário.

Como o WCF já traz vários pontos de estensibilidade, a Microsoft apenas introduziu nesses locais recursos para interceptar o processo de autenticação, e com isso, acoplando o WIF para efetuar todo o trabalho. A ideia aqui é ao configurar um serviço WCF, dizer que ele somente permitirá usuários autenticados por uma outra aplicação, que também será um serviço, mas construído com os recursos fornecidos pelo WIF.

Ao expor um serviço WCF neste caso, a configuração do mesmo deverá conter entre vários itens, o autenticador. Com isso, o WIF será capaz de modificar o documento WSDL que descreve o serviço, também incluindo o local deste autenticador que o teu serviço confia. Com isso, ao referenciar o serviço em uma aplicação cliente, ela já saberá qual o endereço correto do autenticador e, consequentemente, antes de efetuar a requisição efetiva para uma das operações do serviço, ele será inteligente o bastante para autenticar o usuário no autenticador e, finalmente, invocar a operação solicitada. Através da imagem abaixo, podemos visualizar as etapas deste processo:



É 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 autenticador que o mesmo utiliza. Isso já aconteceu durante a referência do serviço na aplicação cliente, que traz, além da descrição do serviço em si, informações inerentes ao autenticador em qual o serviço confia, assim como já foi comentado acima.

Depois de entendermos o processo em um nível macro, precisamos nos atentar em como podemos proceder para preparar tudo isso. Em primeiro lugar, vamos avaliar as opções de template de projeto voltadas para o WCF, que o WIF nos fornece quando instalamos o seu SDK:

  • WCF Security Token Service: Template que traz a configuração padrão para criar um serviço WCF que servirá como um STS no ambiente ativo.
  • Claims-aware WCF Service: Template que traz um serviço WCF pré-configurado para utilizar algum STS, que será responsável pela autenticação do usuário.

Apesar delas existirem, não vamos utilizá-las no decorrer deste artigo. A ideia aqui é apresentar os detalhes de criação do começo ao fim, analisando cada uma das seções necessárias para fazer com que todo esse processo funcione da forma correta. Com isso, teremos três elementos envolvidos no nosso exemplo: o serviço (relying party) que contém todas as funcionalidades que desejamos expor aos clientes, um autenticador (STS), que será o responsável por autenticar os usuários e, finalmente, a aplicação cliente (subjects), que utilizará o serviço WCF para extrair as informações e exibí-las na tela, mas antes disso, deverá autenticar o usuário que a acessa no STS correspondente.

A criação do STS

Para iniciar, vamos começar com a construção do STS, que será o nosso autenticador. Para isso, vamos recorrer à uma aplicação do tipo Console, que irá expor um serviço que aceitará as requisições de login, validará o usuário em algum repositório e, finalmente, devolverá para a aplicação solicitante o token gerado por ele. Como já comentei nas entrelinhas, o STS nada mais é que um serviço, que se utiliza da mesma estrutura de um serviço WCF, mas expondo um tipo de serviço diferenciado, que tem a finalidade de validar e autenticar um usuário. Para termos acesso as classes que nos permite criar este tipo de serviço, devemos referenciar na aplicação o assembly Microsoft.IdentityModel.dll, que é fornecido pelo WIF.

Se o STS nada mais é que um serviço, temos que ter uma classe que o representa, e para isso o WIF já fornece uma classe abstrata chamada SecurityTokenService, que é o nosso STS em si, fornecendo toda a estrutura necessária para a criação de um STS customizado, mas para isso, devemos sobrescrever dois métodos nas classes derivadas, a saber: GetScope e GetOutputClaimsIdentity.

O primeiro método, GetScope, nos dá a possibilidade de verificar quais são as chaves de criptografia que serão utilizadas, baseando-se no endereço da relying party. Esse método retorna uma instância da classe Scope, que contém toda a configuração necessária para gerar o retorno para a respectiva aplicação (relying party). Já o segundo método, GetOutputClaimsIdentity, é o local onde efetivamente definimos quais claims serão criadas para aquele usuário recém autenticado, e que na maioria das vezes, iremos recorrer a uma busca no repositório para extrair esses dados. Esse método retorna a instância de uma classe ClaimsIdentity, que implementa a interface IClaimsIdentity, e como sabemos, fornece uma propriedade chamada Claims, que nada mais é que uma coleção, onde podemos adicionar quantas claims desejarmos.

Como mencionei, o método GetScope é responsável por capturar os recursos necessários para estabelecer a comunicação segura. Aqui teremos dois certificados, sendo um deles utilizado pelo autenticador (STS) e outro pelo serviço (relying party). Cada um deles vai garantir que as mensagens trocadas não sejam manipuladas por ninguém. Para isso, a classe Scope fornece duas propriedades: EncryptingCredentials e SigningCredentials. Na primeira propriedade, definimos o certificado fornecido pelo serviço, que com a sua chave pública, faremos a criptografia da mensagem quando formos retornar a resposta do processo de autenticação (RSTR). Já a propriedade SigningCredentials, apontamos para o certificado do autenticador, que utilizará para garantir que a mensagem não será alterada durante a sua viagem. Abaixo temos um trecho da classe que representa o serviço de STS, omitindo algumas seções por questões de espaço:

public class ServicoDeAutenticacao : SecurityTokenService
{
    public ServicoDeAutenticacao(ConfiguradorDoServicoDeAutenticacao configurador)
        : base(configurador) { }

    protected override IClaimsIdentity GetOutputClaimsIdentity(
        IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
    {
        principal.Identities[0].Claims.AddRange(
            RepositorioDeUsuarios.RecuperarClaimsDoUsuario(principal.Identity.Name));

        return principal.Identity as IClaimsIdentity;
    }

    protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
    {
        Scope scope = new Scope(request.AppliesTo.Uri.AbsoluteUri);
        scope.EncryptingCredentials = this.GetCredentialsForAppliesTo(request.AppliesTo);
        scope.SigningCredentials = new X509SigningCredentials(
            CertificateUtil.GetCertificate(
                StoreName.My,
                StoreLocation.LocalMachine,
                "CN=Autenticador"));

        return scope;
    }

    //Outros Membros
}

Depois da classe que representa o serviço de STS criada, o próximo passo é a criação de uma classe que herda da classe SecurityTokenServiceConfiguration. Essa classe permite configurar um serviço de STS, definindo qual será o tipo da classe que representará o STS, que foi aquela que criamos no passo anterior. Essa classe possui várias propriedades, que permite configurar de forma imperativa o STS, mas neste caso, tudo o que precisamos fazer é herdar, e dentro do seu construtor, definir duas principais propriedades: SecurityTokenService e SigningCredentials. A primeira correspondente ao Type da classe que herda de SecurityTokenService, enquanto a segunda, define os dados do certificado usado para a conversação. O código abaixo mostra a sua estrutura:

public class ConfiguradorDoServicoDeAutenticacao : SecurityTokenServiceConfiguration
{
    public ConfiguradorDoServicoDeAutenticacao()
        : base("ServicoDeAutenticacao")
    {
        this.SecurityTokenService = typeof(ServicoDeAutenticacao);
        this.SigningCredentials = new X509SigningCredentials(
            CertificateUtil.GetCertificate(
                StoreName.My,
                StoreLocation.LocalMachine,
                "CN=Autenticador"));
    }
}

Para encerrar as classes customizadas, é necessário a criação de um security token handler. Neste contexto, o handler é responsável por extrair o token enviado pelo cliente, e validá-lo em algum repositório. Como queremos optar para que o usuário informe o seu login e senha, utilizaremos aqui um security token handler exclusivo para este caso, que é o UserNameSecurityTokenHandler.

Ao herdar desta classe, devemos sobrescrever o método ValidateToken, que fornece como parâmetro a instância do token enviado pelo usuário, representado pela classe UserNameSecurityToken, que por sua vez, fornece duas propriedades pertinentes ao modelo de autenticação, que são elas: UserName e Password. O exemplo abaixo ilustra como proceder para customizar essa classe:

public class ValidadorDeUsuario : UserNameSecurityTokenHandler
{
    public override ClaimsIdentityCollection ValidateToken(SecurityToken token)
    {
        UserNameSecurityToken usernameToken = token as UserNameSecurityToken;

        if (!RepositorioDeUsuarios.Validar(usernameToken.UserName, usernameToken.Password))
            throw new SecurityException("Usuário inválido.");

        ClaimsIdentity identidadeAtual = new ClaimsIdentity("ValidadorDeUsuario");
        identidadeAtual.Claims.Add(new Claim(ClaimTypes.Name, usernameToken.UserName));

        return new ClaimsIdentityCollection() { identidadeAtual };
    }

    //Outros Membros
}

Como podemos notar no código acima, é dentro do método ValidateToken que devemos recorrer ao repositório de usuários para validar as credenciais informadas pelo cliente. Caso o usuário não seja encontrado, você pode disparar uma exceção do tipo SecurityException, informando a inexistência daquele usuário.

E para colocar tudo isso em funcionamento, entra em cena a classe WSTrustServiceHost. Essa classe herda diretamente da classe ServiceHost (exposta pela API do WCF), que permite carregar e gerenciar um serviço STS utilizando toda a estrutura do WCF, e com isso, ficará disponível para receber as requisições das aplicações clientes que desejam autenticar seus usuários, antes de efetivamente acessar uma das operações expostas pelo serviço. Em princípio, temos que recorrer a esta classe quando não estamos utilizamos as templates de projetos que vimos acima, pois quando as usamos, isso tudo o que vemos aqui ficará transparente.

Há um overload do construtor da classe WSTrustServiceHost, que aceita como parâmetro uma classe do tipo SecurityTokenServiceConfiguration, contendo toda a configuração necessária de um serviço de STS. O código abaixo ilustra a sua utilização:

using (WSTrustServiceHost host = 
    new WSTrustServiceHost(new ConfiguradorDoServicoDeAutenticacao()))
{
    host.Open();
    Console.WriteLine("[ Serviço de Autenticação em funcionamento ]");
    Console.ReadLine();
}

Assim como qualquer serviço WCF, somente essa configuração não é suficiente para subir o serviço de STS. Ainda há a necessidade de apontar, via arquivo de configuração, outras informações necessárias para o mesmo, tais como endpoints, contratos, baseAddresses, etc., que como sabemos, é necessário para todo e qualquer tipo de serviço WCF. Mas como estamos criando uma espécie de serviço voltado exclusivamente para o STS, temos já contratos predefinidos, que expõem funcionalidades de emissão, cancelamento e renovação de tokens, mas tudo isso tudo será utilizado, em um primeiro momento, internamente pelo runtime do WIF.

Sendo assim, a classe que representará o serviço é a WSTrustServiceContract, que implementa a interface IWSTrust13SyncContract, que define o contrato do serviço, fornecendo as operações comentadas no parágrafo anterior. Agora o nosso serviço deverá expor dois endpoints, sendo um deles utilizando o contrato IWSTrust13SyncContract em conjunto com o binding WS2007HttpBinding, e além disso, um binding tradicional de metadados (IMetadataExchange), que irá expor todas as funcionalidades do serviço de STS. A única configuração relevante do binding, é que devemos definir o atributo clientCredentialType do elemento message como sendo UserName, já que a ideia é permitir ao usuário informar os dados de acesso através da aplicação cliente. Abaixo podemos visualizar as configurações deste serviço:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract"
               behaviorConfiguration="serviceConfig">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:9010/sts"/>
          </baseAddresses>
        </host>
        <endpoint address=""
                  binding="ws2007HttpBinding"
                  bindingConfiguration="bindingConfig"
                  contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract" />
        <endpoint address="mex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <bindings>
      <ws2007HttpBinding>
        <binding name="bindingConfig">
          <security mode="Message">
            <message clientCredentialType="UserName"
                     negotiateServiceCredential="false"
                     establishSecurityContext="false"/>
          </security>
        </binding>
      </ws2007HttpBinding>
    </bindings>
</configuration>

Como se não bastasse, ainda precisamos das configurações referentes ao WIF neste mesmo arquivo de configuração da aplicação que servirá como host do serviço de STS. Como já vimos em artigos anteriores, a seção para essa configuração é a microsoft.identityModel, que deve ser registrada primeiramente antes de ser utilizada.

Depois da seção registrada, o próximo passo é remover o security token handler que é adicionado por padrão (WindowsUserNameSecurityTokenHandler), e em seguida, adicionarmos aquele que criamos acima. Para isso, recorremos à uma coleção chamada securityTokenHandlers, e especificamos ali o tipo completo, incluindo o nome e o assembly onde ele foi criado. Além disso, ainda há a seção serviceCertificate, que apontamos o certificado que o autenticador irá utilizar para se comunicar com o serviço, protegendo todas mensagens que são trocadas entre eles. O código abaixo resume todas as configurações do WIF que são necessárias na aplicação que hospedará o serviço STS, que compõem o arquivo de configuração que vimos acima com as configurações pertinentes ao WCF:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="microsoft.identityModel"
             type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, ..."/>
  </configSections>
  <microsoft.identityModel>
    <service>
      <securityTokenHandlers>
        <remove type="Microsoft.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, ..." />
        <add type="Autenticador.ValidadorDeUsuario, Autenticador ..." />
      </securityTokenHandlers>
      <serviceCertificate>
        <certificateReference storeLocation="LocalMachine"
                              storeName="My"
                              x509FindType="FindBySubjectDistinguishedName"
                              findValue="CN=Autenticador" />
      </serviceCertificate>
    </service>
  </microsoft.identityModel>
</configuration>

A criação do Serviço

Depois do serviço de autenticação criado, vamos partir para o serviço que utilizará e confiará naquele STS para autenticar os usuários. O serviço que vamos utilizar como exemplo é bem simples, com apenas duas operações que recebem e retornam tipos simples. A ideia aqui será restringir o acesso às operações deste serviço, permitindo o acesso somente depois do usuário ser devidamente autenticado pelo serviço de STS que criamos acima.

Sendo assim, a configuração do serviço WCF com as nossas regras de negócio é simples, contendo os endpoints, os behaviors para expor os metadados e a criação de um hosting, que utilizaremos neste exemplo, uma aplicação do tipo Console. A única diferença aqui na configuração do serviço é a utilização do binding WS2007FederationHttpBinding, que é utilizado quando você está terceirizando o processo de autenticação.

No código abaixo podemos visualizar a configuração do binding do endpoint do nosso serviço. Definimos a segurança como sendo Message, que garantirá que a mensagem seja trafegada de forma segura, mesmo que o transporte não forneça esse recurso. Nas configurações internas, temos um elemento chamado issuerMetadata, que através do atributo address, devemos especificar o endereço do endpoint do serviço de STS, responsável pela autenticação. Em segundo lugar e não menos importante, temos o elemento claimTypeRequirements, que é uma coleção onde podemos especificar quais são as claims que o serviço está exigindo que o STS emita.

<bindings>
  <ws2007FederationHttpBinding>
    <binding name="bindingConfig">
      <security mode="Message">
        <message>
          <issuerMetadata address="http://localhost:9010/sts/mex" />
          <claimTypeRequirements>
            <add claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/name"
                 isOptional="false" />
            <add claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
                 isOptional="false" />
          </claimTypeRequirements>
        </message>
      </security>
    </binding>
  </ws2007FederationHttpBinding>
</bindings>

No serviço também precisamos de algumas configurações pertinentes ao WIF, e como já era de se esperar, é necessário também registrar a seção do mesmo, através do elemento configSections. Neste caso, precisamos definir a audienceUri, que nos permite elencar uma coleção de URIs, que definem os possíveis endereços para qual o token poderá ser entregue, evitando que clientes maliciosos façam uso do mesmo. Em seguida temos a seção serviceCertificate, que apontamos o certificado que o serviço vai utilizar para se comunicar com o autenticador, protegendo as mensagens que são trocadas entre eles. Abaixo o código que ilustra essas configurações:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="microsoft.identityModel"
             type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection ..."/>
  </configSections>
  <microsoft.identityModel>
    <service>
      <issuerNameRegistry type="Servico.ValidadorDoAutenticador, Servico ..." />
      <audienceUris>
        <add value="http://localhost:9000/ServicoDeCredito" />
      </audienceUris>
      <serviceCertificate>
        <certificateReference storeLocation="LocalMachine"
                              storeName="My"
                              x509FindType="FindBySubjectDistinguishedName"
                              findValue="CN=Servico" />
      </serviceCertificate>
    </service>
  </microsoft.identityModel>
</configuration>

Finalmente, ainda analisando o código acima, temos um elemento chamado issuerNameRegistry, que como o próprio nome diz, permite especificarmos ali os STSs em que a aplicação confia. Para termos o controle desta validação, podemos criar uma classe que herde da classe abstrata IssuerNameRegistry, sobrescrevendo o método GetIssuerName, analisando o certificado fornecido pelo autenticador, e assim tomarmos alguma decisão para saber se confiamos ou não nele. Se iremos confiar, então devemos retornar uma string representando o autenticador, caso contrário, devemos disparar uma exceção para reportar essa "falha". O código abaixo ilustra essa classe:

public class ValidadorDoAutenticador : IssuerNameRegistry
{
    public override string GetIssuerName(SecurityToken securityToken)
    {
        X509SecurityToken certificado = securityToken as X509SecurityToken;

        if (certificado != null)
            if (certificado.Certificate.SubjectName.Name == "CN=Autenticador")
                return certificado.Certificate.SubjectName.Name;

        throw new SecurityTokenException("Autenticador Inválido.");
    }
}

O último detalhe é a criação do host, que quando utilizamos um self-hosted, recorrermos à classe ServiceHost, e aqui não será diferente. Só há uma pequena configuração a ser realizada aqui, que é a integração do runtime do WIF ao pipeline do WCF. Essa configuração pode ser feita de forma imperativa ou declarativa. Utilizando a primeira opção, antes de abrir o host, devemos enviar a instância da classe ServiceHost para o método estático ConfigureServiceHost, exposto pela classe FederatedServiceCredentials, assim como podemos notar no trecho de código abaixo:

using (ServiceHost host = new ServiceHost(typeof(ServicoDeCredito), new Uri[] { }))
{
    FederatedServiceCredentials.ConfigureServiceHost(host);

    host.Open();
    Console.WriteLine("[ Serviço de Crédito em funcionamento ]");
    Console.ReadLine();
}

Ao submeter o host para o método ConfigureServiceHost, uma série de elementos são adicionados ao processamento das requisições que chegam para o serviço, efetuando toda a comunicação necessária com o serviço de STS em qual o serviço confia, e além disso, é capaz de capturar o resultado do processo de autenticação realizado remotamente, e com isso prosseguir com o processo de autorização, que determinará o que o usuário poderá ou não acessar.

A autorização continua sendo de responsabilidade do serviço local, que na maioria das vezes recorre às roles para refinar o acesso. Quando estamos trabalhando com o WIF, todas as informações que ele emite para um determinado usuário autenticado, é representado por uma claim. No nosso exemplo, o STS está gerando três claims, sendo uma delas do tipo name, e as outras duas do tipo role. O WCF em conjunto com o WIF são capazes de analisar as claims geradas pelo STS, e depois disso, configurar a propriedade CurrentPrincipal da classe Thread com uma instância da classe ClaimsPrincipal, representando a identidade do usuário e suas respectivas claims.

Desta forma, podemos continuar trabalhando com o modelo de programação da mesma forma que já utilizávamos no passado, ou seja, através do método IsInRole (imperativo) ou com o uso do atributo PrincipalPermissionAttribute (declarativo), assim como é exemplificado abaixo:

public class ServicoDeCredito : IConsultasFinanceiras
{
    [PrincipalPermission(SecurityAction.Demand, Role = "Funcionario")]
    public decimal RecuperarLimiteDeCredito(int codigoDoCliente)
    {
        //Implementação
    }

    [PrincipalPermission(SecurityAction.Demand, Role = "Gerente")]
    public void DefinirNovoLimiteDeCredito(int codigoDoCliente, decimal novoValor)
    {
        //Implementação
    }
}

A criação do Cliente

Finalmente, depois dos dois serviços devidamente implementados, chega o momento de consumí-los em uma aplicação cliente. Tudo o que precisamos fazer nela, é adicionar a referência para o serviço WCF, assim como já fazemos. Não há necessidade de fazer referência ao serviço de STS na aplicação cliente, pois além do documento WSDL descrever as operações do serviço, ele também traz todas as informações necessárias a respeito do serviço responsável pela autenticação, como o endereço onde ele está localizado, quais são as claims que o serviço exige que o autenticador emita, etc.

Ao referenciar o serviço através da opção Add Service Reference da IDE do Visual Studio ou através do utilitário svcutil.exe, o proxy gerado conterá as informações tradicionais do endpoint do serviço com o qual a aplicação quer se comunicar, mas também incluirá algumas configurações extras, que determinam como a comunicação com o STS será realizada. Justamente por conhecer onde o STS está e como devemos ser comunicar com ele, que o ambiente ativo é bem mais rápido que o passivo, como já discutido anteriormente.

Outro detalhe importante no arquivo de configuração, são as chaves públicas dos certificados envolvidos no processo. Uma delas foi fornecida pelo serviço e a outra pelo autenticador, que o runtime do WCF e do WIF irão utilizá-las durante o processo de comunicação para garantir com que as mensagens que serão trocadas entre as partes sejam protegidas.

Por fim, a aplicação cliente só precisa consumir o serviço, apenas se atentando a informar o login e senha antes de invocar alguma das operações. O código abaixo ilustra a utilização do serviço, através do proxy que foi criado durante a referência do serviço. O arquivo de configuração do cliente será omitido aqui por questões de espaço.

using (ConsultasFinanceirasClient proxy = new ConsultasFinanceirasClient())
{
    proxy.ClientCredentials.UserName.UserName = "Israel";
    proxy.ClientCredentials.UserName.Password = "P@ssw0rd";

    proxy.DefinirNovoLimiteDeCredito(560, 10000);

    Console.WriteLine("Limite para o cliente 200: {0:C2}",
        proxy.RecuperarLimiteDeCredito(200));
}

Conclusão: Depois de tudo o que fizemos aqui, o nosso serviço ficou completamente independente de como se faz a autenticação do usuário. Se tivermos um barramento de serviços dentro de algum local, como uma empresa, todos os serviços podem confiar em um único autenticador, e a reutilização será muito grande, já que temos todo o processo centralizado, facilitando a manutenção, já que os serviços consumidores nada sabem sobre a forma como estamos autenticando os usuários.

TesteComAmbienteAtivo.zip (42.08 kb)

Tags: , , ,

Security | WCF | WIF

Autenticação com WCF e jQuery

by Israel Aece 28. May 2010 21:48

Recentemente escrevi sobre o consumo de serviços WCF a partir do jQuery. Naquele artigo foi comentado como construir serviços WCF para ser acessados através do ambiente REST, e além disso, vimos também como proceder para o consumo deste serviço utilizando o jQuery como cliente.

Mas um detalhe que não foi abordado no artigo foi a questão da segurança, ou melhor, do processo de autenticação do cliente que consome o serviço. Por exemplo, imagine que temos um serviço que exige que o usuário se identifique, para que assim possamos determinar se ele terá ou não acesso ao serviço. Apesar do WCF fornecer várias alternativas para isso, quando estamos em um ambiente REST, alguns cuidados especiais são necessários, quais serão abordados no decorrer deste artigo.

O primeiro detalhe para nos atentarmos é como vamos configurar o serviço para que o mesmo possa exigir as credenciais. Para expor um serviço para ser consumido através do ambiente REST, utilizamos um binding exclusivo chamado de WebHttpBinding. Em sua configuração padrão, não há nenhum tipo de autenticação configurada, mas como qualquer binding, podemos recorrer à opção security para isso.

Temos apenas três opções relacionadas à segurança para este binding: None, Transport e TransportCredentialOnly. A primeira opção desabilita qualquer tipo de proteção e autenticação no respectivo serviço. Já a segunda opção, Transport, determina que será o protocolo que deverá garantir a segurança da mensagem (HTTPS) e, finalmente, o TransportCredentialOnly, que não fornecerá integridade e confidencialidade na mensagem que está sendo trafegada, e utilizará apenas o protocolo HTTP exclusivamente para autenticação do usuário.

Depois desta configuração que determina como a comunicação deverá ser protegida, temos uma espécie de "sub-configuração" dela que precisamos nos atentar. Esta sub-configuração determina como as credenciais serão passadas para o serviço. Entre as várias formas, temos o HTTP Basic, que faz com que as credenciais viagem do cliente para o serviço sem qualquer espécie de criptografia. Esse modelo, em sua configuração padrão, tem uma forte afinidade com o Windows/Active Directory, ou seja, para utilizar esse recurso, as credenciais informadas do lado do cliente deverão refletir uma conta válida no Windows/Active Directory, algo que não vamos nos preocupar neste momento. A configuração do serviço WCF deverá ficar da seguinte forma:

<system.serviceModel>
  <services>
    <service
      name="RESTComBasicAuthentication.Services.ServicoDeUsuarios"
      behaviorConfiguration="config">
      <endpoint
        address=""
        binding="webHttpBinding"
        contract="RESTComBasicAuthentication.Services.IUsuarios"
        behaviorConfiguration="edpConfig"
        bindingConfiguration="bc" />
    </service>
  </services>
  <bindings>
    <webHttpBinding>
      <binding name="bc">
        <security mode="TransportCredentialOnly">
          <transport clientCredentialType="Basic" />
        </security>
      </binding>
    </webHttpBinding>
  </bindings>
  <!-- Outras Configurações -->
</system.serviceModel>

Quando utilizamos o modelo Basic, ao tentar acessar um recurso protegido, o próprio browser é capaz de identificar e abrir uma janela com os campos para informarmos o username e password, só que não podemos nos esquecer de que esse serviço será consumido por algum cliente, e que o foco aqui é o uso do jQuery, e com isso, de alguma forma precisamos enviar o username e password do cliente para o serviço. Como já sabemos, o jQuery oferece uma função chamada $.ajax, que nos permite configurar e efetuar a chamada para um determinado serviço ou algum outro método. Internamente, este método recorre a um objeto popularmente conhecido, que é o XMLHttpRequest. Este objeto é o responsável por gerenciar toda a comunicação entre o cliente e o serviço.

De acordo com a especificação deste objeto, há um método chamado Open, que entre os parâmetros convencionais, como endereço até o serviço, o tipo da requisição (Json ou Xml), há também como informar mais duas informações relacionadas ao processo de autenticação: username e password. Através dos parâmetros username e password, definimos duas strings que representam cada uma dessas informações. Como o jQuery abstrai o acesso a este objeto, a função $.ajax também nos permite informar o usuário e senha, como já mencionado acima. Sabendo da existência destes parâmetros, podemos configurar a chamada para o serviço através do jQuery da seguinte forma:

function RecuperarUsuario() {
    $.ajax(
    {
        type: "POST",
        username: "UsuarioQueExisteNoWindows",
        password: "123456",
        url: "http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario",
        contentType: "application/json",
        data: '{ "nome": "Israel Aece", "email": "ia@israelaece.com" }',
        processData: true,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
    });
}

Se você monitorar a requisição para este serviço com alguma ferramenta, como é o caso do Fiddler, você verá que o username e password não serão enviados até que realmente seja necessário. O modelo de autenticação Basic é baseado no processo conhecido como "Desafio HTTP 401", que funciona da seguinte forma:

  • O cliente solicita um recurso (página, serviço, etc.) que está protegido.
  • Ao detectar que o cliente não está autenticado, o servidor exige que ele se autentique e informe as credenciais a partir do modelo Basic. Isso é informado a partir de um header chamado WWW-Authenticate: Basic.
  • Neste momento, o servidor retorna uma resposta com o código 401 (Access Denied), que instrui o cliente (browser) a solicitar as credenciais de acesso.
  • Uma vez informado, o browser recria a mesma requisição, mas agora envia nos headers da mesma o usernamepassword codificados em Base64, sem qualquer espécie de criptografia. Na resposta, o header enviado é o Authorization: Basic [Username+Password Codificado].
  • Quando este header acima estiver presente, o servidor (IIS) é capaz de validá-lo no Windows/Active Directory, e se for um usuário válido, permitirá o acesso, caso contrário retornará a mesma resposta com código 401, até que ele digite uma credencial válida.

Apesar das credenciais estarem em hard-code no exemplo acima, nada impede de criarmos uma tela onde o cliente pode digitar um usuário e senha, mas lembrando que este usuário deverá exisitir no Windows/Active Directory.

Customização

O principal ponto negativo da solução acima é a necessidade de termos os usuários cadastrados no Windows, algo que pode ser inviável em um ambiente que não é controlado, como é o caso da internet. Algo muito comum em aplicações deste tipo, é a validação do usuário em uma base de dados, ou até mesmo, utilizando a estrutura do Membership do ASP.NET. Para atingirmos esse objetivo, precisamos entender um pouco mais sobre o processo de autenticação e também alguns pontos de estensibilidade.

Quando configuramos o modelo Basic, o IIS é o responsável por coordenar todo o processo de acesso ao recurso, e se o usuário não estiver autenticado, o próprio IIS devolve a mensagem com o código 401 e o header específico para o mesmo (1), para que o browser proceda com a solicitação das credenciais (2), e depois de informadas, refaz a requisição embutindo o header com as credencias codificadas (3). A imagem abaixo ilustra esse processo:



No exemplo que vimos acima, o único recurso protegido pelo modelo Basic é apenas o arquivo ServicoDeUsuarios.svc. Agora, se desejamos customizar, temos que trazer todo o controle do processo de autenticação para a aplicação, ou seja, não iremos mais querer que o IIS coordene o processo, que como sabemos, ele recorre ao Windows para validar os usuários. Sendo assim, precisamos permitir o acesso anônimo ao arquivo *.svc.

Há algum tempo eu comentei sobre a possibilidade de efetuar a customização da autenticação do WCF através de usuário e senha, utilizando a classe UserNamePasswordValidator para essa validação. O problema é que o binding WebHttpBinding não suporta a customização deste validador, o que nos obrigaria à descer o nível até o pipeline do ASP.NET para implementar algo específico para o WCF.

Ao invés disso, podemos recorrer aos interceptadores (Interceptors). Os interceptadores não estão nativamente dentro do WCF, mas estão disponíveis ao instalar o WCF-REST Starter Kit. Este kit nada mais é que um conjunto de funcionalidades que podemos incorporar aos nossos projetos WCF baseados em REST, tornando algumas tarefas comuns em algo mais simples de se resolver/implementar, como vai ser o caso aqui.

Ao instalar este kit, teremos à nossa disposição alguns novos assemblies, e entre eles um chamado Microsoft.ServiceModel.Web.dll. Uma das classes fornecidas por ele é a classe abstrata RequestInterceptor. Essa classe fornece um método abstrato chamado ProcessRequest, que permite interceptar a requisição, antes mesmo dela começar a ser executada, fornecendo um parâmetro do tipo RequestContext, que representa o contexto da requisição atual, qual será utilizado para a interação com o cliente que está tentando acessar o recurso.

Com esta possibilidade, podemos criar um interceptador que extrai o header da requisição HTTP que representa a credencial informada pelo usuário (WWW-Authenticate), e a valida em algum repositório de sua escolha, como uma base de dados. A partir daqui é necessário conhecermos como funciona o processo do modelo HTTP Basic, para conseguirmos dialogar com o cliente, para que assim ele consiga coordenar o processo de autenticação do usuário.

Como havia dito acima, o username e password não são enviados até que sejam efetivamente exigidos. Nesta customização, ao identificarmos que o header não está presente na requisição, precisamos configurar a resposta para o cliente com o código 401, que representa acesso não autorizado, e informar na resposta o mesmo header, para continuar obrigando o usuário a informar o username e password.

Para saber se o cliente informou as credenciais, precisamos detectar a presença do header chamado Authorization. Se existir, então precisamos decodificá-lo, utilizando o método FromBase64String da classe Convert, que dado uma string, retorna um array de bytes representando as credenciais separadas por um ":". Depois disso, tudo o que precisamos fazer é separá-los, para que assim podermos efetuar a validação em algum repositório. O código abaixo ilustra esse processo:

string credentials = mp.Headers["Authorization"];

if (!string.IsNullOrWhiteSpace(credentials))
{
    string decodedCredentials =
        Encoding.Default.GetString(Convert.FromBase64String(credentials.Substring(6)));

    int separator = decodedCredentials.IndexOf(':');
    username = decodedCredentials.Substring(0, separator);
    password = decodedCredentials.Substring(separator + 1);
}

Caso o header não exista ou o usuário não existir na base de dados, então podemos criar o header para que o cliente seja - novamente - obrigado a informar as credenciais. Para isso, utilizamos as propriedades da mensagem, e configuramos a mensagem de retorno. A mensagem é representada no WCF pela classe Message. O código abaixo ilustra a criação, configuração e resposta ao cliente com esta mensagem recém criada:

private void GenerateAccessDeniedMessage(ref RequestContext requestContext)
{
    Message reply = Message.CreateMessage(MessageVersion.None, null, null
        new DataContractJsonSerializer(typeof(object)));

    HttpResponseMessageProperty hrp = new HttpResponseMessageProperty();
    hrp.StatusCode = HttpStatusCode.Unauthorized;
    hrp.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", this.Realm));
    reply.Properties[HttpResponseMessageProperty.Name] = hrp;

    requestContext.Reply(reply);
    requestContext = null;
}

Se o usuário for válido, então é necessário inicializar o contexto de segurança do WCF, que é representado pela classe ServiceSecurityContext, que utiliza uma instância da classe GenericPrincipal para identificar o usuário logado.

Finalmente, tudo isso não funciona por si só. Ainda precisamos acoplar a instância desta classe à execução. Para isso, devemos recorrer a customização da classe que cria o host, que é responsável por gerenciar a execução da classe que representa o serviço. A classe ServiceHostFactory fornece um método chamado CreateServiceHost, que como o próprio nome diz, permite retornar qualquer classe que herde direta ou indiretamente da classe ServiceHost. Dentro do assembly do WCF-Rest Starter Kit, temos uma versão específica de host, chamada de WebServiceHost2. A grande diferença deste host, é que ele expõe uma propriedade chamada Interceptors, que permite adicionarmos qualquer interceptador, desde que ele herde da classe RequestInterceptor, como já vimos acima. Depois que a factory customizada foi criada, devemos alterar o arquivo *.svc para que ela seja utilizada:

<%@ ServiceHost
    Language="C#"
    Debug="true"
    Factory="RESTComBasicAuthentication.CustomRestHostFactory"
    Service="RESTComBasicAuthentication.Services.ServicoDeUsuarios"
    CodeBehind="ServicoDeUsuarios.svc.cs" %>

Uma vez que acoplamos este interceptador, ele é quem gerenciará o processo de autenticação, ou seja, o cliente irá dialogar com este interceptador, mas para que isso funcione corretamente, é necessário que a autenticação Basic esteja desabilitada no IIS. Através da imagem abaixo, podemos reparar este novo comportamento:

Conclusão: Apesar de não temos nativamente no WCF uma forma de customizar a autenticação em serviços REST, podemos recorrer ao WCF-REST Starter Kit para conseguir efetuar essa customização, utilizando a infraestrutura oferecida pelo modelo HTTP Basic para enviar as credenciais. Apesar de tudo funcionar como deveria, é importante dizer que os dados continuam sendo trafegados sem qualquer proteção, e para evitar que alguém visualize o conteúdo, é importante que toda a comunicação seja protegida pelo HTTPS.

RESTComBasicAuthentication.zip (328.00 kb)

Tags: , , ,

CSD | WCF

QCon - Como unificamos nossa camada de autenticação?

by Israel Aece 25. May 2010 22:04

O principal evento de arquitetos e desenvolvedores chega a América Latina. O QCon SP traz, dias 11 e 12 de Setembro, ícones internacionais e nacionais de diversas áreas, com apresentações de alto nível técnico. Com sistemas cada vez mais complexos, o QCon aborda não apenas uma única tecnologia ou aspecto: passa de Java, .NET e Rails até Arquitetura, Design, Cloud, Escalabilidade, Replicação, Cache e casos de sucesso.

Neste evento, eu fui convidado para compor juntamente com alguns ícones do mundo .NET (tais como Thiago Cruz Soares e Fabio Galuppo), o grupo de palestrantes focados em tecnologias Microsoft/.NET. A minha palestra tem como finalidade abordar o WIF - Windows Identity Foundation, que já falei bastante por aqui. Abaixo temos a descrição completa da palestra:

Título: Como unificamos nossa camada de autenticação?
Palestrante: Israel Aece
Descritivo: Autenticação e autorização são dois conceitos importantes que existem no desenvolvimento de software. A autenticação é a necessidade de saber quem o usuário é, enquanto a autorização é saber que direitos ele tem no software. Há uma grande complexidade em torno destes temas, pois cada aplicação precisa se preocupar em vários detalhes para garantir a segurança, tais como: um local seguro para armazenar as credenciais de acesso, definição de políticas para a criação e reutilização de senhas, etc. E como se isso não fosse o bastante, corremos um grande risco de termos a mesma infraestrutura repetida entre várias aplicações, dificultando a manutenção, segurança e a experiência de navegação dos usuários que as acessam. Essa palestra mostrará cenários típicos do nosso dia-à-dia, como SSO (Single Sign-On) e federação dentro da plataforma .NET.

Tags: ,

General | Security | WIF

Autenticação via Claims no ASP.NET MVC

by Israel Aece 11. April 2010 20:54

Anteriormente eu comentei como podemos utilizar o WIF em uma aplicação ASP.NET WebForms, analisando detalhadamente os passos necessários para efetuar a configuração dos módulos utilizados pelo WIF. Mas o WIF também pode ser utilizado em conjunto com aplicações ASP.NET MVC. A finalidade deste artigo é mostrar algumas dicas importantes para fazer com que o WIF seja acoplado ao pipeline de uma relying party construída com o ASP.NET MVC, pois ao contrário do que vimos no artigo anterior, não há templates de projetos com o WIF pré-configurado para o ASP.NET MVC.

O primeiro passo é importante para ser dito, é que para utilizar o WIF com o MVC, também devemos recorrer aos módulos WSFederationAuthenticationModule e SessionAuthenticationModule, que são acoplados ao pipeline do ASP.NET e são os responsáveis pelo processamento e gerenciamento do processo de autenticação do usuário. Sendo assim, é necessário adicioná-los manualmente na seção <httpModules /> do arquivo Web.config, ou se estiver utilizando o assistente Federation Utility, que está disponível a partir da IDE do Visual Studio, ele já fará essa configuração automaticamente.

Como sabemos, a infraestrutura de segurança do o ASP.NET MVC é igual ao do ASP.NET WebForms, e com isso devemos respeitar as mesmas imposições feitas quando utilizamos o WIF no WebForms, ou seja, configurar o modelo de autenticação como indefinido (None), pois essa tarefa não compete mais à esta aplicação. Além disso, temos também que criar e configurar adequadamente a seção <microsoft.identityModel />, que como já sabemos, isso acaba sendo realizado automaticamente quando utilizamos o Federation Utility.

O primeiro passo é você se preocupar em proteger as seções da aplicação MVC que não permitem o acesso anônimo. Ao contrário do WebForms, no MVC definimos quais são as actions (métodos) que são restritas. Para definir isso, podemos utilizar o arquivo Web.config ou através do atributo AuthorizeAttribute. O código abaixo negará o acesso anônimo aos métodos (actions) VisualizarItens e RecuperarIndice da classe (controller) Monitor. Para maiores detalhes, você pode consultar este artigo.

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

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

Pelo fato de termos os módulos do WIF acoplados ao MVC, ao detectar que o usuário não tem permissão de acesso a um destes métodos, automaticamente o módulo WSFederationAuthenticationModule entrará em ação e redirecionará o usuário para o STS que ele confia, incluindo na requisição a action e o controller que ele tentou acessar, para que quando o STS validá-lo, o WIF consiga redirecionar o usuário para o mesmo local.

Outro detalhe importante quando configuramos o WIF no ASP.NET MVC, é o uso do atributo passiveRedirectEnabled do elemento wsFederation. Quando ele é definido como False, o desenvolvedor da relying party deverá explicitamente efetuar o redirecionamento para o STS quando necessário. No ASP.NET WebForms, podemos fazer uso do controle FederatedPassiveSignIn, que tem a finalidade de encapsular toda a complexidade deste processo. Mas como sabemos, no MVC não utilizamos esses tipos de controles, o que nos obriga a fazer o redirecionamento manual, lembrando que isso somente é necessário quando o atributo passiveRedirectEnabled for definido como False.

Quando necessitamos desligar o auto-redirecionamento, precisamos saber como proceder para conseguir interagir com o STS. Para exemplificar isso, temos que criar um controller que irá gerenciar o processo de login, logout e o processamento do token gerado. Esse controller utilizará alguns dos membros que são expostos pela API do WIF, que vimos em um artigo anterior. O controller irá definir actions que determina cada uma das tarefas que ele vimos acima, podendo criar nas views, links que direcionam para cada uma delas.

public class SecurityController : Controller
{
    public ActionResult EfetuarLogin()
    {
        if (!User.Identity.IsAuthenticated)
        {
            var fam = FederatedAuthentication.WSFederationAuthenticationModule;
            var requestMessage = new SignInRequestMessage(new Uri(fam.Issuer), fam.Realm)
            {
                Context = "EndereçoParaOndeUsuarioSeraDirecionadoDepoisDoLogin"
            };

            return new RedirectResult(requestMessage.WriteQueryString());
        }

        return this.View();
    }

    [ValidateInput(false)]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult ProcessarToken()
    {
        var fam = FederatedAuthentication.WSFederationAuthenticationModule;

        if (fam.CanReadSignInResponse(HttpContext.Current.Request, true))
        {
            string url = HttpContext.Current.Request.Form["wctx"];
            return new RedirectResult(url);
        }

        return this.View();
    }

    public ActionResult EfetuarLogout()
    {
        if (User.Identity.IsAuthenticated)
        {
            var fam = FederatedAuthentication.WSFederationAuthenticationModule;
            var signOutMessage = new SignOutRequestMessage(new Uri(fam.Issuer));

            fam.SignOut(false);
            return new RedirectResult(signOutMessage.WriteQueryString());
        }

        return this.View();
    }
}

O primeiro método, chamado de EfetuarLogin, extrai alguns parâmetros do módulo WSFederationAuthenticationModule e cria uma mensagem de login para o STS. A mensagem de login é representada pela instância da classe SignInRequestMessage, que fornece uma propriedade chamada Context, que recebe uma URL para qual o usuário será direcionado depois do login ter sido realizado. Finalmente, utilizando o método WriteQueryString, que retorna uma string representando o endereço completo até o STS, incluindo os parâmetros necessários para que o STS faça toda a validação necessária, e passamos essa string através de um objeto RedirectResult, que faz com que o MVC redirecione o usuário ao STS.

Na sequência temos o método ProcessarToken, que é responsável por verificar se a requisição trata-se de um token gerado pelo STS, e isso é realizado através do método CanReadSignInResponse da classe WSFederationAuthenticationModule. Se este método retornar True, então extraímos o parâmetro wctx da requisição e redirecionamos o usuário para este endereço. O wctx representa o endereço que o usuário tentou acessar antes de efetuar o login no STS, qual utilizaremos o RedirectResult para direcionar o usuário novamente para lá. Podemos notar que este método está decorado com dois atributos, onde o primeiro deles desligamos a validação para permitir que informações com caracteres < e > sejam postadas para ele. Isso é necessário porque o token gerado é baseado em XML. Em seguida, utilizamos o atributo AcceptVerbsAttribute para garantir que este método será acessado somente através do verbo POST.

Finalmente, temos o método EfetuarLogout, que coordena o processo de logout do usuário. Ele utiliza o método SignOut da classe WSFederationAuthenticationModule, e na sequência utilizamos uma instância da classe SignOutRequestMessage, que recebe em seu construtor o endereço do STS. Depois da instância criada, utilizamos novamente um objeto RedirectResult para redirecionar o usuário para o endereço gerado pelo método WriteQueryString da classe SignOutRequestMessage, que efetuará o logout no STS.

Depois deste controller criado, tudo o que precisamos fazer na aplicação, é mencionar as actions login e logout onde desejamos visualizar estes links. Para exemplificar, o código abaixo ilustra como criar estes links:

<%= Html.ActionLink("Login", "EfetuarLogin", "Security") %>
<%= Html.ActionLink("Logout", "EfetuarLogout", "Security") %>

Conclusão: Como percebemos neste artigo, apesar de não termos toda a facilidade de configuração e controles que existem no ASP.NET WebForms, é fácil acoplar o WIF à execução de uma aplicação ASP.NET MVC. A única exigência aqui é você tentar identificar se precisa ou não do auto-redirecionamento; caso não precisar, o controller que foi criado acima não será necessário.

Tags: , , ,

ASP.NET | WIF

Explorando os módulos do WIF para o ASP.NET

by Israel Aece 26. March 2010 15:21

Como vimos no artigo anterior, o WIF fornece vários módulos para acoplarmos ao pipeline do ASP.NET, e que farão tudo o que for necessário para efetuar a comunicação com o respectivo STS. Esses módulos são aqueles tradicionais módulos do ASP.NET, que implementam a interface IHttpModule, e se vinculam à eventos que "cortam" a requisição na vertical.

Esses módulos são: WSFederationAuthenticationModule e SessionAuthenticationModule, e estão debaixo do namespace Microsoft.IdentityModel.Web. O primeiro é responsável pelo processamento do token que é gerado pelo STS, enquanto o segundo persiste essa informação em um cookie, e nas requisições subsequentes, ele irá analisá-lo, e caso não exista ou esteja inválido, redirecionará o usuário novamente para o STS.

Ambas as classes (módulos), fornecem uma série de membros que permite aos desenvolvedores ASP.NET a interceptarem alguns estágios do processamento através de eventos, adicionando um código customizado seja ele para manipulação ou auditoria. Além disso, esses módulos também oferecem alguns métodos e propriedades para permitir a reutilização de algumas funcionalidades/informações. A finalidade deste artigo é explorar os principais membros públicos e o que e como podemos fazer para efetuar tal customização.

Antes de efetivamente falarmos dos membros que são disponibilizados, precisamos saber como temos acesso à cada um deles. Como é responsabilidade do próprio ASP.NET criar os módulos que disponibilizam esses eventos, de algum forma precisamos acessar essas instâncias criadas. Para isso, existe uma classe estática chamada FederatedAuthentication, que também está debaixo do namespace Microsoft.IdentityModel.Web. Essa classe fornece apenas cinco propriedades, também estáticas, a saber:

  • ClaimsAuthorizationModule: Retorna a instância do módulo ClaimsAuthorizationModule. Este módulo é acoplado ao runtime do ASP.NET no evento OnAuthorizeRequest, invocando o método CheckAccess da classe ClaimsAuthorizationManager, que você pode utilizar para customizar a autorização.
  • ClaimsPrincipalHttpModule: Retorna a instância do módulo ClaimsPrincipalHttpModule, que faz uma espécie de tradução, tranformando o principal e identity corrente, em objetos que implementam as interfaces IClaimsIdentity e IClaimsPrincipal.
  • ServiceConfiguration: Essa propriedade retorna uma instância da classe ServiceConfiguration, que representa a seção do arquivo de configuração que corresponde ao WIF.
  • SessionAuthenticationModule: Retorna a instância do módulo SessionAuthenticationModule. Sua funcionalidade já foi discutida acima e neste outro artigo.
  • WSFederationAuthenticationModule: Retorna a instância do módulo WSFederationAuthenticationModule. Sua funcionalidade também já foi discutida acima e neste outro artigo.

A classe FederatedAuthentication ainda fornece um evento ServiceConfigurationCreated, que é disparado depois que a seção do WIF foi totalmente carregada. Agora, depois de conhecermos como podemos chegar até os eventos, podemos recorrer ao modo tradicional do .NET para nos vincularmos à eles, que é através do operador +=. Mas temos também tem uma segunda alternativa, onde podemos definir o tratador do evento utilizando o nome do módulo (classe) e o nome do evento, diretamente dentro do arquivo Global.asax, assim como podemos ver no exemplo abaixo:

<%@ Import Namespace="Microsoft.IdentityModel.Configuration" %>
<%@ Import Namespace="Microsoft.IdentityModel.Web" %>

void WSFederationAuthenticationModule_SignedIn(object sender, EventArgs e)
{
    //...
}

WSFederationAuthenticationModule - Propriedades

Este módulo fornece várias propriedades que ajudam na configuração do redirecionamento para o STS, na geração da mensagem de requisição e no processamento do token emitido pelo STS. Grande parte dessas propriedades acabam sendo configuradas através da seção do WIF (<microsoft.identityModel />) que encontra-se dentro do arquivo Web.config da aplicação que corresponde à relying party.

Entre essas propriedades, temos a PassiveRedirectEnabled. Essa propriedade recebe um valor boleano, que indica ao WIF que ao detectar que o usuário não está autenticado, irá redirecioná-lo automaticamente para o STS configurado na aplicação. É importante dizer que se utilizarmos o controle FederatedPassiveSignIn, essa propriedade deverá estar definida como False, assim como foi comentado neste outro artigo.

Issuer também é uma propriedade importante, que recebe o endereço do STS, para qual o usuário será redirecionado para efetuar a autenticação. A propriedade Reply contém o endereço da relying party, para que o STS possa identificar se ele pode ou não emitir o token para ela.

WSFederationAuthenticationModule - Métodos

Essa classe ainda fornece vários métodos, mas que na maioria das vezes acabam sendo utilizados pelo próprio runtime. O primeiro deles é o método CanReadSignInResponse, que recebe como parâmetro uma requisição (HttpRequest) e retorna um valor boleano indicando se ela se refere à uma mensagem de autenticação, gerada pelo STS. CreateSignInRequest é um método que dado alguns parâmetros, retorna uma instância da classe SignInRequestMessage, que corresponde à mensagem de solicitação de login ao STS.

Ainda temos um método estático, chamado de FederatedSignOut. Esse método tem a finalidade de efetuar a requisição ao STS para efetivar o logout. Já o método SignOut apenas notifica a relying party de que o usuário efetuou o logout. Quando você invoca o primeiro deles, FederatedSignOut, depois de efetuado o logout no STS, o usuário é redirecionado novamente para a relying party, e quando o WIF dectecta que ele fez o logout no STS, invoca o método SignOut, disparando assim os eventos correspondentes para que a relying party seja notificada de que isso realmente aconteceu.

Já o método IsSignInResponse, dado a requisição (HttpRequest), retorna um valor boleano indicando se a requisição trata-se da resposta dada pelo STS. Você pode utilizá-la para burlar ou evitar algum processamento que não deveria acontecer nesta situação. Temos também o método RedirectToIdentityProvider, que como o próprio nome diz, me permite, de forma explícita, redirecionar o usuário para o STS. Isso te dá uma flexibilidade quando você não quer o redirecionamento automático que o WIF faz ao identificar que o usuário não está autenticado.

WSFederationAuthenticationModule - Eventos

O primeiro evento que temos dentro deste principal módulo do WIF é o RedirectingToIdentityProvider. Este evento é disparado antes do usuário ser redirecionado para o STS, dando a oportunidade de customizar o redirecionamento, a mensagem de requisição que é enviada (RST) e a oportunidade de cancelá-la. Tudo isso é fornecido através do argumento RedirectingToIdentityProviderEventArgs, que possui apenas duas propriedades: Cancel e SignInRequestMessage.

Depois deste evento temos os eventos que são disparados durante o recebimento e processamento do token gerado pelo STS, que são: SecurityTokenReceived, SecurityTokenValidated e SessionSecurityTokenCreated. O primeiro é disparado quando a resposta com o token foi enviada pelo STS. Já o segundo, é disparado depois que o token foi validado pelo WIF e o objeto IClaimsPrincipal já foi criado com as claims devolvidas pelo STS, e que podemos ter acesso através do argumento SecurityTokenValidatedEventArgs que é passado para este evento. Finalmente, o evento SessionSecurityTokenCreated, que tem a finalidade de notificar que o token foi recebido, validado e criado, e já está pronto para emitir o cookie para armazená-lo, mas isso depende do valor que é colocado na propriedade WriteSessionCookie, fornecido pelo argumento SessionSecurityTokenCreatedEventArgs. Depois desse estágio, quem faz as manipulações no cookie é o módulo SessionAuthenticationModule, que veremos mais adiante.

Há também eventos que são disparados durante o processo de login, a saber: SignedIn e SignInError. O primeiro evento é disparado depois que o objeto IClaimsPrincipal já foi definido para a thread/requisição corrente, e com isso, seguramente você poderá recorrer as propriedades HttpContext.Current.User e Thread.CurrentPrincipal. O segundo evento, SignInError, é disparado quando algum erro ocorre dentro deste módulo durante a recepção ou validação do token, e você pode utilizar esse evento para verificar a exceção que foi disparada, ter oportunidade de conseguir tratá-la e, eventualmente, catalogar em algum lugar.

Esse módulo ainda fornece mais três eventos que correspondem ao processo de logout: SigningOut, SignedOut e SignOutError. O primeiro é disparado antes do processo de logout iniciar, e segundo ocorre quando o processo de logout foi concluído e, finalmente, o evento SignOutError é disparado se algum problema acontecer durante o logout, e justamente por isso, ele inclui uma propriedade chamada Exception, que retorna a instância da exceção disparada.

E, finalmente, o evento AuthorizationFailed é disparado sempre quando você tenta acessar algum recurso protegido e ainda não está autenticado. Esse evento fornece como argumento um objeto do tipo AuthorizationFailedEventArgs, que por sua vez, disponibiliza uma propriedade chamada RedirectToIdentityProvider, que é do tipo boleana, indicando se o WIF deve ou não redirecionar o usuário para o STS.

SessionAuthenticationModule - Propriedades

A principal propriedade exposta por essa classe é a CookieHandler. Essa propriedade, que expõe um objeto com o mesmo nome, é responsável por armazenar o token gerado pelo STS em um cookie do lado do cliente. Além de gerar o cookie, o módulo SessionAuthenticationModule verifica a existência do mesmo, e caso exista, evita o redirecionamento para o STS em futuras requisições. O cookie handler também é estensível, e veremos mais detalhes sobre isso em um artigo específico.

SessionAuthenticationModule - Métodos

Como essa classe gerencia o cookie que corresponde ao token gerado pelo STS, ela também fornece métodos auxiliares que permitem ter acesso ao cookie em questão. O método ContainsSessionTokenCookie recebe uma coleção de cookies (que na maioria das vezes está dentro do objeto HttpRequest), e retorna um valor boleano indicando se o cookie existe dentro dela. Já o método TryReadSessionTokenFromCookie recebe um parâmetro de saída do tipo SessionSecurityToken, e retorna um valor boleano indicando se foi posssível ler o cookie. Caso retorne True, você pode seguramente acessar o parâmetro de saída, que corresponderá ao token gerado pelo STS.

O método DeleteSessionTokenCookie apenas tem a finalidade de excluir o cookie que corresponde ao token daquele usuário atual. Apesar de público, ele geralmente é invocado durante o processo de logout.

SessionAuthenticationModule - Eventos

Para evitar que o usuário seja redirecionado para o STS a toda requisição que fazemos à alguma página aplicação, esse módulo é adicionado ao runtime do ASP.NET com a finalidade de evitar essa transição a todo momento. Ele verifica se a requisição possui o cookie que corresponde ao token do usuário, que este módulo gerou quando ele (o usuário) se autenticou no STS. Ao detectar a presença deste cookie, ele extrai a informação e permite, através de dois eventos, interceptar o trabalho que ele faz. Esses eventos são: SessionSecurityTokenReceived e SessionSecurityTokenCreated.

Como disse acima, durante a execução este módulo verifica se o cookie está presente, e se estiver, recria o token a partir dele, e após isso, o evento SessionSecurityTokenReceived é disparado. Esse evento fornece como argumento um objeto do tipo SessionSecurityTokenReceivedEventArgs, qual você pode utilizar para re-emitir o cookie, permitindo você a manipular várias características dele, como por exemplo o tempo de expiração do token.

Já o segundo evento, SessionSecurityTokenCreated, é disparado durante a re-emissão do cookie, que muitas vezes acontece quando ele expira. Como argumento, este evento fornece um objeto do tipo SessionSecurityTokenCreatedEventArgs, que fornece duas propriedades: SessionToken e WriteSessionToken. A primeira corresponde ao token do usuário que você pode customizar aqui; já a segunda, recebe um valor boleano indicando se o cookie que irá ser regerado deve ou não ser persistido.

Esse módulo ainda fornece mais três eventos que correspondem ao processo de logout: SigningOut, SignedOut e SignOutError. O primeiro é disparado antes do processo de logout iniciar, o segundo ocorre quando o processo de logout for concluído e, finalmente, o evento SignOutError que é disparado se algum problema acontecer durante o logout, e justamente por isso, ele inclui uma propriedade chamada Exception, que retorna a instância da exceção disparada.

Conclusão: Este artigo mostrou os principais membros dos módulos do WIF, e que você pode fazer uso deles quando o WIF estiver habilitado na aplicação. Como notamos na descrição de cada um dos eventos disponibilizados pelos módulos, eles servem para interceptar vários momentos durante o redirecionamento, validação e persistência do token, e você pode ou não optar por utilizá-los, e isso dependerá da sua necessidade.

Tags: , , , ,

ASP.NET | WIF

Autenticação via Claims no ASP.NET WebForms

by Israel Aece 12. March 2010 23:09

Nos artigos anteriores, falei sobre a proposta da Microsoft para tentar unificar o sistema de autenticação das aplicações. Nos dois primeiros artigos, comentei um pouco sobre os problemas existentes e a teoria que circunda qualquer Identity MetaSystem. Na sequência, introduzi o WIF e alguns tipos que são comuns para qualquer tipo de aplicação que fará uso deste novo framework. A finalidade deste artigo é mostrar como podemos aplicar o WIF em uma aplicação ASP.NET WebForms, analisando tudo o que é necessário para colocar isso em funcionamento, incluindo a criação de um STS para testes.

Quando efetuamos a instalação do SDK do WIF, várias configurações são realizadas dentro da IDE do Visual Studio .NET para suportá-lo. Entre essas configurações, temos a adição de novas templates de projetos, que nos permite criar um projeto com o WIF já pré-configurado, evitando assim lidarmos diretamente com as configurações de baixo nível que ele exige e que veremos no decorrer deste artigo. As quatro templates de projetos disponíveis, estão listadas abaixo com suas respectivas descrições:

  • WCF Security Token Service: Template que traz a configuração padrão para criar um serviço WCF que servirá como um STS no ambiente ativo.
  • ASP.NET Security Token Service Web Site: Template que traz a configuração padrão para criar uma aplicação ASP.NET que servirá como um STS no ambiente passivo.
  • Claims-aware WCF Service: Template que traz um serviço WCF pré-configurado para utilizar algum STS, que será responsável pela autenticação do usuário.
  • Claims-aware ASP.NET Web Site: Template que traz uma aplicação ASP.NET pré-configurada para utilizar algum STS, que será responsável pela autenticação do usuário.

Como disso no parágrafo anterior, o artigo será focado em algumas partes dos bastidores do WIF dentro de uma aplicação ASP.NET (ambiente passivo), e para começar, sabemos que podemos utilizar o atributo mode do elemento authentication no arquivo Web.config, para determinar qual será o modelo de autenticação; esse atributo permite escolhermos um entre as quatro opções disponíveis: None, Forms, Windows e Passport. Abaixo temos um exemplo desta configuração:

<configuration>
  <system.web>
    <authentication mode="Windows" />
  </system.web>
</configuration>

Ao configurar desta forma, automaticamente a aplicação ASP.NET reutilizará a credencial do usuário que está atualmente na máquina, e utilizará essa credencial para refinar o acesso (autorização) e para saber quem o usuário realmente é. Com isso, as propriedades Thread.CurrentPrincipal e HttpContext.User retornam uma instância da classe WindowsPrincipal, que foi criada pelo runtime do ASP.NET, e que corresponde ao usuário atual. Para nos certificarmos disso, podemos recorrer ao seguinte código:

WindowsPrincipal wp = HttpContext.Current.User as WindowsPrincipal;
WindowsIdentity wi = wp.Identity as WindowsIdentity;
Response.Write(wi.Name);

Da mesma forma, se utilizarmos a opção Forms, o runtime do ASP.NET criará uma instância da classe GenericPrincipal e FormsIdentity, que corresponderão ao usuário autenticado através do Forms Authentication, e que na maioria da vezes, esta opção recorre à uma base de dados que serve como repositório para os usuários, onde os serviços de MembershipProvider e de RoleProvider, fornecidos a partir da versão 2.0 do ASP.NET, são uma das várias alternativas disponíveis.

Mas e se queremos preparar nossa aplicação para suportar claims? No artigo Explorando o WIF, eu comentei sobre as interfaces IClaimsIdentity e IClaimsPrincipal, e são elas que devem ser utilizadas pelas aplicações baseadas em claims. Da mesma forma que vimos anteriormente, a criação de classes que implementam essas interfaces ainda continua sendo de responsabilidade do runtime do ASP.NET, que ao receber o token do STS, fará tudo o que for necessário para ler o seu conteúdo e criar os objetos necessários, para que assim determine se o usuário foi devidamente autenticado. Falaremos sobre estes passos mais adiante, ainda neste artigo.

Mesmo que no primeiro momento não queremos envolver um STS para efetuar a validação do usuário, podemos já fazer com que nossa aplicação transforme tudo o que temos atualmente em claims. Por exemplo, se minha aplicação usa autenticação baseada no Windows, então eu menciono WindowsPrincipal na aplicação; já se minha aplicação possui autenticação baseada em Forms, então poderei mencionar GenericPrincipal. Ao invés de lidar diretamente com esses tipos específicos, podemos recorrer as interfaces IClaimsIdentity e IClaimsPrincipal, quais foram desenhadas para atender a essa necessidade, ou seja, independem de tecnologia de autenticação que está sendo utilizada.

Para fazer com que o ASP.NET passe a criar as classes de identity e principal que fazem uso de claims, precisamos acoplar no pipeline do mesmo, um módulo fornecido pelo WIF chamado de ClaimsPrincipalHttpModule, que está debaixo do namespace System.IdentityModel.Web. É importante dizer que os tipos que veremos a partir daqui, estão dentro do assembly Microsoft.IdentityModel.dll, que deverá ser referenciado nas aplicações. Este módulo se vincula ao evento PostAuthenticateRequest (que ocorre assim que o usuário foi identificado), e faz uma espécie de tradução, tranformando o principal e identity corrente, em objetos que implementam as interfaces IClaimsIdentity e IClaimsPrincipal, respectivamente. Durante essa transformação, todos os atributos que eram fornecidos pela tecnologia de autenticação, passam a virar claims, e estão disponívieis para utilização. Para exemplificar o que vimos acima, o primeiro passo é adicionar o módulo no pipeline do ASP.NET, utilizando o elemento httpModules:

<configuration>
  <system.web>
    <authentication mode="Windows" />
    <httpModules>
      <add name="ClaimsPrincipalHttpModule"
           type="Microsoft.IdentityModel.Web.ClaimsPrincipalHttpModule, ..." />
    </httpModules>
  </system.web>
</configuration>

Essa configuração fará com que o ASP.NET faça a transformação do que foi gerado pela tecnologia de autenticação (Windows ou Forms), em tipos que correspondem ao modelo de claims. Sendo assim, o código que utilizamos acima para extrair o nome do usuário a partir da propriedade User da classe HttpContext, pode ser substituído pelo código abaixo. O interessante é que independentemente de que modelo de autenticação estamos utilizando, esse código não irá variar.

IClaimsPrincipal principal = HttpContext.Current.user as IClaimsPrincipal;
IClaimsIdentity identity = principal.Identity as IClaimsIdentity;

foreach (var item in identity.Claims)
    Response.Write(string.format("{0}: {1}<br />", item.ClaimType, item.Value));

Envolvendo um STS

No exemplo que vimos acima, ele somente se preocupa em fazer com que todos os modelos de autenticação suportados pelo ASP.NET, sejam automaticamente traduzidos para claims, nada mais. Até então, a autenticação continua sob responsabilidade desta mesma aplicação. Como podemos proceder para "externalizar" a autenticação? Será justamente este assunto que está seção irá abordar, entendendo como configurar a aplicação para conseguir receber os tokens que serão gerados por um terceiro, o STS.

Em uma aplicação ASP.NET tradicional, e que faz uso do modelo Forms para autenticação, quando tentamos acessar uma página protegida e não estamos autenticado, o ASP.NET automaticamente irá nos redirecionar para uma página de login que é definida no arquivo Web.config, colocando na Url uma query string chamada ReturnUrl, que contém a página que tentamos acessar. Esse parâmetro é interessante, pois se formos devidamente autenticado, seremos redirecionado para ela.

Quando envolvemos um STS, o processo é bem semelhante, mas ao invés dele redirecionar para uma página local que efetuará o login, devemos ser redirecionados para um outro site, que por sua vez, fará a validação e também a emissão do token, redirecionando o usuário para aplicação - protegida - que ele tentou acessar. Como vimos nos artigos anteriores, esse é o ambiente passivo, onde o navegador irá atuar apenar para coordenar os redirecionamentos entre as aplicações (STS e relying party). A imagem abaixo ilustra como o processo deverá acontecer:

Analisando a imagem acima, repare que o usuário solicita uma aplicação ASP.NET que está protegida (1). Essa aplicação verifica que o usuário não está autenticado. Como ela não é mais responsável por autenticá-lo, ela redireciona o usuário para o STS em que ela confia (2). O usuário se autentica no STS utilizando a tecnologia que foi definida lá, que pode ser Kerberos (Windows), UserName (Forms Authentication), certificados, etc. (3). Se o usuário for válido, um token é emitido para aquele usuário (4). Finalmente, o navegador redireciona o usuário para a página solicitada, mas agora incluindo o token (5).

Toda a mágica que acontece neste processo, acaba sendo realizada através de funcionalidades que o próprio protocolo HTTP fornece, ou seja, faz uso de requisições GET e POST, anexando na URL alguns parâmetros que são utilizados para garantir que tudo funcione conforme o esperado. Antes de analisarmos o conteúdo que é trafegado entre as partes, vamos nos concentrar em como devemos configurá-las para que essa conversação seja possível.

Antes de criar a aplicação que servirá como relying party, vamos optar por iniciar pelo STS. No início do artigo, vimos as templates de projeto que são criadas na instalação do SDK do WIF, e uma delas é justamente uma aplicação ASP.NET que serve como um STS (ASP.NET Security Token Service Web Site). A ideia desta template, é permitir você criar uma aplicação que valida usuários e emita tokens para ele. Um cenário em que ele poderia ser utilizado, é se você tiver uma estrutura de Membership, Roles e Profile, e quer que este STS valide os usuários e faça a emissão de tokens baseando-se nessa estrutura. Como sabemos, a vantagem disso é que todas as aplicações podem confiar neste STS, centralizando assim toda a lógica necessária para isso.

A aplicação STS gerada pela template, possui três arquivos na raiz do projeto: Default.aspx, Login.aspx e Web.config. No arquivo Web.config, não há nada especial, nada que não conhecemos. A configuração inicial faz com que o atributo mode do elemento authentication seja definido como Forms, pois será esse o modo de autenticação que os usuários usarão lá. Neste mesmo arquivo de configuração, a página Login.aspx é definida como a página onde os usuários deverão informar suas credenciais, e a página Default.aspx é proibida de ser acessada por usuários anônimos.

<configuration>
  <system.web>
    <authentication mode="Forms">
      <forms loginUrl="~/Login.aspx" />
    </authentication>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

Além do que vimos acima, dentro do projeto criado para servir como STS, ele ainda fornece o diretório App_Code, com algumas classes dentro. As classes que constam ali, principalmente a CustomSecurityTokenService, é o nosso STS em si. Repare que ele herda diretamente da classe SecurityTokenService, que fornece toda a estrutura necessária para a criação de um STS customizado. Ele fornece dois métodos chaves que devem ser sobrescritos nas classes derivadas, a saber: GetScope e GetOutputClaimsIdentity.

O primeiro método, GetScope, nos dá a possibilidade de verificar quais são as chaves de criptografia que será utilizada, baseando-se no endereço da relying party. Esse método retorna uma instância da classe Scope, que contém toda a configuração necessária para gerar o retorno para a respectiva aplicação (relying party). Já o segundo método, GetOutputClaimsIdentity, é o local onde efetivamente definimos quais claims serão criadas para aquele usuário recém autenticado, e que na maioria das vezes, iremos recorrer a uma busca no banco de dados para extrair esses dados. Esse método retorna a instância de uma classe ClaimsIdentity, que implementa a interface IClaimsIdentity, e como sabemos, fornece uma propriedade chamada Claims, que nada mais é que uma coleção, onde podemos adicionar quantas claims desejarmos.

Ambos métodos recebem como parâmetro um principal, do tipo IClaimsPrincipal, que corresponde ao principal criado para o chamador do STS, e além dele, um outro parâmetro chamado request, do tipo RequestSecurityToken, que representa a requisição enviada ao STS (RST), e que pode ser utilizada para extrair informações adicionais.

Note que a validação do usuário acaba sendo realizada através da tecnologia que utilizamos para isso, que neste caso, é a Forms Authentication. No code-behind do arquivo Login.aspx, temos a validação acontecendo, que deve recorrer à alguma base de dados, como o MembershipProvider, para validar o usuário. Somente a partir daí é que a classe do STS que vimos acima entrará em cena.

Criação da Relying Party

Na seção anterior, expliquei como estruturar a aplicação que servirá como STS. Uma vez que ela está criada, precisamos agora desenvolver as aplicações que servirão como relying party, ou melhor, aplicações que utilizarão aquele STS para autenticar o usuário. Da mesma forma que fizemos para criar um STS, vamos recorrer a a template de projeto chamada Claims-aware ASP.NET Web Site para criar a relying party. Este projeto já traz a estrutura necessária para que a aplicação já consuma um STS, mas como fizemos acima, vamos entender a mágica que está por trás disso tudo.

Do lado da aplicação ASP.NET que será uma relying party, pequenas mudanças são necessárias no arquivo de configuração (Web.config), mas que impactam diretamente em como o runtime do ASP.NET irá tratar os redirecionamentos necessários. Da mesma forma que já fazemos no ASP.NET tradicional, precisamos negar o acesso aos usuários anônimos, aquelas páginas que necessitam que eles estejam devidamente autenticados. Para isso, utilizamos o elemento authorization, assim como é mostrado abaixo:

<configuration>
  <system.web>
    <authentication mode="None" />
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

Note também que a autenticação é desligada nesta aplicação, pois não compente mais a ela validar o usuário. Mas isso por si só não é suficiente para que esta aplicação contate o STS. Ainda é necessário especificar algumas outras informações que conduzirá a relying party à como efetuar o redirecionamento para o STS, e para isso, há uma seção exclusiva do WIF, que nos permite configuar cada um dos parâmetros exigidos. O código abaixo ilustra a configuração de uma relying party que confia no STS que criamos acima:

<microsoft.identityModel>
  <service>
    <audienceUris>
       <add value="https://aplicacoes.minhaempresa.com.br/App01/" />
    </audienceUris>
    <applicationService>
       <claimTypeRequired>
          <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
                     optional="true" />
          <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
                     optional="true" />
       </claimTypeRequired>
    </applicationService>
    <federatedAuthentication>
      <wsFederation passiveRedirectEnabled="true"
                    issuer="https://administrativo.minhaempresa.com.br:444/STS01/"
                    realm="https://aplicacoes.minhaempresa.com.br/App01/"
                    requireHttps="true" />
      <cookieHandler requireSsl="true" />
    </federatedAuthentication>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, ...">
      <trustedIssuers>
        <add thumbprint="0844D588E25237D81AC61BF7C38EDAB7075BD024"
             name="https://administrativo.minhaempresa.com.br:444/STS01/" />
      </trustedIssuers>
    </issuerNameRegistry>
  </service>
</microsoft.identityModel>

Primeiramente precisamos registrar a seção microsoft.identityModel no arquivo de configuração, através do elemento configSections, pois ela não consta no schema de configuração do .NET Framework. Uma vez registrada, podemos fazer uso dela na mesma aplicação. Imediatamente dentro desta seção, temos uma sub-seção chamada de service que envolverá todas as configurações de autenticação daquela aplicação.

A partir de agora, temos outras sub-seções que efetivamente configuram o comportamento do WIF, e como podemos ver, temos uma seção chamada audienceUris. Dentro desta seção, podemos elencar uma coleção de URIs, que definem os possíveis endereços em que o token poderá ser entregue, evitando que clientes maliciosos façam uso do mesmo. Já o próximo elemento, applicationService/claimTypeRequired, tem um papel importante, que diz ao STS quais claims a aplicação necessita, podendo você determinar, através do atributo optional, se ela é ou não opcional.

Em seguida, vemos um elemento chamado federationAuthentication. Este elemento nos permite configurar as informações referente ao STS em que a aplicação confia. O sub-elemento wsFederation fornece alguns atributos que nos auxiliam nisso. O primeiro deles é o issuer, onde devemos especificar o endereço do STS; já no atributo realm informamos o endereço da aplicação solicitante, e que coincide com o endereço especificado no elemento audienceUris. E ainda, temos o atributo passiveRedirectEnabled, que recebe um valor boleano indicando se o WIF deve efetuar o redirecionamento automático quando detectar que o usuário não está autenticado. Veremos um cenário mais claro para este atributo quando falarmos dos controles fornecidos pelo WIF. Ainda debaixo do elemento federationAuthentication, temos o sub-elemento chamado de cookieHandler, que fornece alguns atributos para configurar o cookie que corresponderá a sessão de segurança de um determinado usuário.

Para finalizar este grande bloco de configuração, temos ainda uma última seção, chamada de issuerNameRegistry. Como o próprio nome diz, permite especificarmos ali os STSs em que a aplicação confia. É importante dizer que cada STS (issuer) é conhecido através do thumbprint de um certificado X.509. Note também que esta seção especifica o tipo da classe que é responsável por efetuar a validação do STS. A classe é definida através do atributo type, e no exemplo acima, a classe que utilizamos já está embutida no WIF, e é chamada de ConfigurationBasedIssuerNameRegistry, que tem a finalidade de extrair do arquivo de configuração as informações necessárias para efetuar tal validação. Caso queira customizar isso, basta você herdar da classe abstrata IssuerNameRegistry, sobrescrevendo o método GetIssuerName.

Depois de todas as configurações que vimos acima, isso ainda não funciona sozinho, pois ainda não há nada que mude a execução padrão do ASP.NET. É justamente aqui que introduzimos duas novas classes, chamadas de WSFederationAuthenticationModule e SessionAuthenticationModule (namespace Microsoft.IdentityModel.Web). Ambas classes são módulos (implementam a interface IHttpModule), e devem ser acoplados na execução do ASP.NET, para que o WIF entre em ação. Esses módulos irão consumir grande parte das configurações que vimos acima, para que consiga efetuar todas as tarefas, tais como: redirecionamento, criptografia e deserialização do token gerado pelo STS.

Uma vez que o módulo WSFederationAuthenticationModule é adicionado ao pipeline do ASP.NET, ele se vincula ao evento AuthenticateRequest. Ao tentar acessar uma página protegida e não estiver autenticado, esse módulo irá detectar que o acesso foi negado (status 401 do HTTP), e redirecionará o usuário para o respectivo STS. Depois que o usuário for validado e o STS emitir o token para ele, novamente ele será redirecionado novamente para a aplicação (RP), onde este mesmo módulo interceptará e irá utilizar este token para criar a instância da classe IClaimsPrincipal, atribuindo-a a propriedade User da classe HttpContext. Esse módulo encapsula todas as complexidades dos padrões WS-* que são utilizados para efetuar a comunicação entre a aplicação e o autenticador.

Se a cada página que tentássemos acessar fossemos redirecionado para o STS, isso seria algo totalmente inviável. Sendo assim, é necessário que uma vez autenticado, essa sessão seja mantida durante as requisições que fazemos para esta mesma aplicação. É justamente neste momento que entra em cena o módulo SessionAuthenticationModule. A finalidade dele é monitorar o cookie que é criado e representa o usuário que foi autenticado pelo STS. Caso esse cookie esteja presente e seja válido, esse módulo faz o trabalho necessário para manter a propriedade User da classe HttpContext configurada, sem precisar efetuar - novamente - a visita ao STS. Abaixo temos os dois módulos adicionados (elemento httpModules) ao runtime do ASP.NET na relying party:

<httpModules>
  <add name="WSFederationAuthenticationModule"
       type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, ..." />
  <add name="SessionAuthenticationModule"
       type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, ..." />
</httpModules>

É importante dizer que além destes módulos modificarem a execução de uma aplicação ASP.NET, eles fornecem uma série de membros (métodos, eventos e propriedades) que podem ser utilizados pela aplicação, mas esses membros serão abordados em um futuro artigo.

Mas estes dois módulos não são os únicos elementos que são executados durante o processo de autenticação do usuário. Internamente eles fazem uso de diversas outras classes que possuem um papel importante ou que servem apenas como um ponto de estensibilidade. Para ter uma visão mais clara disso, analise a imagem abaixo, que foi baseada em uma apresentação do Vittorio Bertocci durante o PDC 2009:

A classe SecurityTokenHandler (namespace Microsoft.IdentityModel.Tokens) que vemos depois do módulo WSFederationAuthenticationModule, é uma classe abstrata e serve como base para outros token handlers. Token handler, como o próprio nome diz, é o responsável por validar o token gerado pelo STS, que dependendo do tipo de autenticação utilizado, o token pode estar formatado de uma maneira/versão específica. Como essa classe é abstrata, há várias implementações dela debaixo deste mesmo namespace, onde cada implementação visa um formato diferente (Saml, UserName, etc.), e o qual será autenticado depende da requisição.

Na sequência visualizamos a classe ClaimsAuthenticationManager. Por padrão, essa classe não faz absolutamente nada, mas dá a chance de customizá-la através do método virtual Authenticate, permitindo interceptar a primeira requisição, onde o token gerado pelo STS é enviado para a aplicação. Com ela, podemos customizar e validar as claims que foram retornadas pelo STS, fazendo alguma validação, adicionando novas claims ou até mesmo fazendo transformações/traduções.

Finalmente, temos a classe ClaimsAuthorizationManager, que permite definir um lugar centralizado para definir as regras de autorização. Essa classe também fornece um método chamado CheckAccess, que retorna uma valor boleano indicando se o acesso será ou não concedido. Para poder tomar a decisão para conceder ou negar o acesso, este método recebe como parâmetro uma instância da classe AuthorizationContext, que contém os seguintes membros: Action, Principal e Resource. Estensibilidade é um assunto longo e complexo, e que não será abordado neste artigo, pois exige um artigo exclusivo para isso.

Assistente para Configuração

A ideia de toda a explicação acima foi tentar mostrar os passos necessários para configurar, manualmente, tudo o que é necessário para acoplar o WIF no ASP.NET, abordando os principais elementos que devem estar presentes nos arquivos de configuração da relying party e do STS. Só que configurar isso manualmente pode ser complexo e propício a erros, pois devido a quantidade de informações que devemos definir, eventualmente podemos esquecer de algo importante, que evitará do WIF funcionar adequadamente.

Para tornar a configuração de ambas as partes simples e intuitiva, a Microsoft disponibiliza um utilitário chamado Federation Utility (FedUtil.exe), que é instalado juntamente com o SDK do WIF. Esse assistente tem a finalidade de nos guiar durante a configuração de uma relying party. Quando instalamos o SDK, ao clicar com o botão direito do mouse em cima de um projeto ASP.NET, uma nova opção está disponível, chamada Add STS Reference. O utilitário é inicializado a partir desta aplicação, e nos ajudará a escolher/criar um STS. As imagens abaixo ilustram os dois primeiros passos disponíveis:

Na primeira imagem definimos o caminho físico até o arquivo Web.config, e logo abaixo temos a URI desta mesma aplicação (relying party). O passo a seguir, que está ilustrado na imagem subsequente, oferece três opções para a configuração do STS, e dependendo da opção escolhida, refletirá em configurações específicas no arquivo Web.config mencionado no primeiro passo. Abaixo temos a lista com as três opções e suas respectivas descrições:

  • No STS: Ao selecionar esta opção, tudo o que será feito na aplicação é a inserção do módulo ClaimsPrincipalHttpModule, conforme discutimos no início do artigo. Como sabemos, isso habilitará que qualquer tipo de autenticação que seja utilizado no projeto, seja transformado em claims.
  • Create a new STS project in the current solution: Esta opção cria na mesma solução um projeto ASP.NET que servirá como um STS, e depois disso, já irá alterar o arquivo Web.config da relying party, fazendo com que ela confie neste STS recém criado.
  • Use an existing STS: Como podemos notar, essa opção permite especificarmos um endereço de um STS existente, em que a aplicação corrente irá confiar.

Avançado alguns passos deste assistente, há uma tela em que são listadas as claims oferecidas por aquele STS, tela qual você pode avaliar se aquele STS fornece as claims necessárias para a sua aplicação, permitindo você evitar a referência à este STS caso ele não atenda. A imagem abaixo exibe esta tela informativa:

Controles

Ao referenciar o assembly que corresponde ao WIF em uma aplicação ASP.NET, automaticamente dois controles são adicionados à toolbox do Visual Studio: FederatedPassiveSignInStatus e FederatedPassiveSignIn, onde ambos estão debaixo do namespace Microsoft.IdentityModel.Web.Controls. Assim como já acontece nos controles de autenticação do ASP.NET, o controle FederatedPassiveSignInStatus alterna a exibição de um HyperLink contendo o endereço para efetuar o login ou o logout, dependendo do status atual do usuário.

Recapitulando o que foi falado acima, quando o WIF detecta que o usuário não está autenticado, automaticamente irá redirecioná-lo para o STS, e tudo isso graças ao atributo passiveRedirectEnabled do elemento wsFederation, que quando estiver definido como True, terá esse comportamento. Mas imagine que você, por algum motivo, confie em mais do que um STS, ou ainda, quer dar a opção ao usuário dele se autenticar usando o STS ou um outro formato mais tradicional, como o próprio Membership. Neste caso, o redirecionamento não poderá acontecer automaticamente, e é neste momento que entra em cena o controle FederatedPassiveSignIn.

Neste caso, a relying party terá uma página que permitirá ao usuário escolher o modo de autenticação desejado, e o controle em questão, permitirá configurarmos todas as informações pertinentes ao STS que ele referencia. As suas principais propriedades são: Issuer e Realm, onde você deve definir o endereço do autenticar e da aplicação que o utiliza, respectivamente; ainda temos uma outra propriedade chamada SignInMode, que aceita duas opções expostas pelo enumerador SignInMode: Session e Single. A primeira opção (padrão) fará com que um cookie seja criado para persistir a sessão do usuário, enquanto a opção Single é utilizada apenas para uma única requisição. Abaixo temos o código ASPX correspondente ao controle FederatedPassiveSignIn, com as propriedades definidas, mas para que ele funcione de forma correta, é necessário definir o atributo passiveRedirectEnabled do elemento wsFederation como False.

<%@ Register
    Assembly="Microsoft.IdentityModel, ...."
    Namespace="Microsoft.IdentityModel.Web.Controls"
    TagPrefix="wif" %>

<wif:FederatedPassiveSignIn
    ID="FederatedPassiveSignIn1"
    runat="server"
    Issuer="https://administrativo.minhaempresa.com.br:444/STS01/"
    Realm="https://aplicacoes.minhaempresa.com.br/App01/"
    SignInMode="Session" />

Metadados

Para referenciar um serviço em uma aplicação, geralmente recorremos ao documento WSDL que ele fornece. Dentro deste documento há uma série de informações, que descrevem grande parte das capacidades do serviço, tais como: as operações fornecidas, o modelo de segurança, entre outras coisas. De forma análoga, o STS precisa informar para aquelas aplicações que desejam utilizá-lo, o que ele fornece, como por exemplo: endereço do mesmo para a validação do usuário, quais claims estão disponíveis, certificado usado para a proteção das mensagens que são trocadas, etc.

Todas essas informações são fornecidas através de um documento XML, localizado fisicamente no STS, e que todas as aplicações que desejam consumí-lo, podem e devem consultar esse arquivo para conhecer as informações necessárias para efetuar a configuração local (aquelaque encontra-se na seção microsoft.IdentityModel no arquivo Web.config). Ao rodar o utilitário FedUtil.exe, ele consulta esse documento para extrair as informações e efetuar a configuração necessária no arquivo Web.Config informado no primeiro passo do assistente.

Esse arquivo tem o nome de FederationMetadata.xml, e fica contido em uma pasta chamada FederationMetadata/2007-06, mas também é criado na aplicação que referencia o STS, com uma estrutura um pouco diferente. A ideia deste arquivo de metadado do lado do cliente, é fornecer ao STS o endereço da aplicação, podendo assim, o STS configurar as aplicações para quais ele poderá emitir tokens.

Estrutura da Requisição e Resposta

Quando utilizamos FormsAuthentication em um projeto ASP.NET, ao tentar acessar uma página protegida quando não estamos autenticados, somos redirecionados para a página de login especificada no Web.config, e um item chamado ReturnUrl é adicionado como query string, identificando a página que foi solicitada, para que se o usuário for autenticado, automaticamente será redirecionado para a mesma.

Ao utilizar o WIF, teremos algo semelhante, mas ao ser redirecionado para o STS que validará o usuário, vários parâmetros serão adicionados como query string, para que o STS possa utilizá-los durante o processo de validação, geração de tokens e, finalmente, o redirecionamento de volta para a aplicação. Esses parâmetros são definidos através da especificação do WS-Federation, que rege a estrutura das mensagens. Abaixo temos a lista dos possíveis parâmetros utilizados para efetuar a requisição (RST), com suas respectivas descrições:

  • wa: Este parâmetro identifica a action a ser executada pelo STS. Esse parâmetro pode receber dois valores, sendo o primeiro o wssignin1.0 para instruir o STS a a validar e gerar o token para o usuário. Já o segundo, wssignout1.0, diz ao STS para proceder o logout do usuário.
  • wtrealm: Representa a URI da aplicação que está solicitando a autenticação do usuário (relying party). O STS usará essa URI para identificar a aplicação solicitante e, eventualmente, efetuar alguma verificação interna, customizando as claims geradas para cada uma delas. Além disso, esse parâmetro serve também para que o STS saiba para onde deverá responder.
  • wctx: Este parâmetro define a URL original que o usuário tentou acessar, e assim como a ReturnUrl do ASP.NET, esse parâmetro serve para redirecionar o usuário para o local exato dentro da aplicação (relying party), depois da autenticação realizada.
  • wct: Este parâmetro indica o horário atual na aplicação solicitante, garantindo assim que o cálculo das eventuais expirações sejam feitas de forma coerente, já que o STS pode estar em um fuso, enquanto a relying party pode estar em outro.

Uma vez redirecionado, o STS fará o que for necessário para validar o usuário, e depois que isso for realizado, novamente o usuário será redirecionado devolta para a aplicação que ele tentou acessar. Neste momento, ao invés de ser uma requisição normal, através do método GET, um POST será efetuado. Isso se deve pelo fato de que o token gerado pode ser maior que 4KB, e isso é uma limitação imposta pelos navegadores na URL, impedindo que o método GET seja utilizado. Quando o POST é feito para a aplicação (relying party), o módulo WSFederationAuthenticationModule entra em cena, processando a resposta gerada pelo STS, como já vimos acima.

Para exemplificar, abaixo temos o endereço da requisição que foi montado pelo WIF, redirecionando o usuário para o STS referenciado com os parâmetros que identificam a relying party.

https://administrativo.minhaempresa.com.br:444/STS01/?
    wa=wsignin1.0&
    wtrealm=https%3a%2f%2faplicacoes.minhaempresa.com.br%2fApp01%2f&
    wctx=rm%3d0%26id%3dpassive%26ru%3d%252fApp01%252fDefault.aspx&
    wct=2010-03-12T00%3a55%3a37Z

Como vimos acima, uma vez que o usuário é autenticado e o token gerado, o STS efetua um POST para a aplicação solicitante. O conteúdo desta resposta é um HTML com um formulário (tag <form />), e dentro dele há vários campos ocultos, que representam os parâmetros do WS-Federation para a resposta (RSTR), e inclui também um pequeno código JavaScript para efetuar uma espécie de um auto-submit o formulário. Os parâmetros do WS-Federation utilizados aqui são:

  • wa: Contém a ação executada pelo STS.
  • wctx: Contém a URL original que o usuário tentou acessar na aplicação solicitante.
  • wresult: O resultado em si, que contém o token emitido pelo STS.

SSO - Single Sign-On

Como comentado acima, o WIF utiliza cookies no ambiente passivo para evitar que o usuário seja redirecionado a todo momento para o STS. Como sabemos, há uma regra em que uma aplicação (site) somente pode acessar os cookies que foram emitidos por ela, em nome daquele domínio em que ela se encontra. Mas dessa forma, como podemos garantir o Single Sign-On (SSO)? (SSO é a possibilidade de se autenticar uma única vez, em um local único, e assim poder acessar todas as aplicações que confiam neste autenticador, sem a necessidade de efetuar novamente o login)

Para que isso seja possível, o cookie pode ser gerado para o domínio da relying party ou para o domínio do STS. A segunda opção é a utilizada para garantir o SSO, já que o cookie é gerado em nível de STS e somente a partir daquele domínio é que poderemos acessá-lo. Uma vez que você possui várias aplicações confiando neste STS, os redirecionamentos são realizados até ele, mas ao detectar a presença de um cookie válido para aquele token, o formulário de login não será apresentando, redirecionando o usuário devolta para a aplicação solicitante, garantindo assim um ambiente confiável.

Conclusão: Neste extenso artigo, vimos como funciona os bastidores e como configuar o WIF em uma aplicação ASP.NET Web Forms. Como a Microsoft se preocupou em manter o mesmo modelo de programação quando falamos de autenticação e autorização, o simples fato de acoplarmos um dos módulos que vimos acima no pipeline do ASP.NET, já faz com que magicamente a identidade do usuário seja transformada em claims, e toda a complexidade de redirecionamentos entre as aplicações (relying parties e STSs) também já está embuitida nestes módulos. E como se não bastasse, ainda há o utilitário FedUtil.exe que nos auxilia na configuração, evitando conhecer o schema do WIF. Além disso, o WIF com ASP.NET facilita a implementação de SSO, tarefa que é extremamente árdua sem o uso dele.

Tags: , , ,

ASP.NET | Security | WIF

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