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

Flexibilizando a requisição e resposta à serviços REST

by Israel Aece 3. August 2010 12:46

Ao criar um serviço WCF para ser acessado através do modelo REST, devemos decorar as operações que o compõem o contrato com os atributos WebGetAttribute ou WebInvokeAttribute, dependendo de como as requisições devem chegar até elas.

As requisições para estas operações podem ser enviadas e recebidas através de dois formatos: Xml ou Json, e para configurar isso, podemos recorrer à duas propriedades fornecidas pelos dois atributos listados acima. Essas propriedades são: RequestFormat e ResponseFormat, onde ambas recebem uma das opções expostas pelo enumerador WebMessageFormat.

Isso quer dizer que devemos definir, de forma estática, o formato que a mensagem será aceita e como ela será devolvida para os clientes. O grande problema desta técnica é que as vezes você pode ter um mesmo serviço sendo consumindo por clientes diferentes, que lidam melhor com um formato específico. Por exemplo, você pode ter um mesmo serviço sendo consumido por uma aplicação Silverlight, que possui um suporte melhor ao Xml, e ao mesmo tempo, o mesmo serviço sendo consumido por um código jQuery, que lida melhor com o formato Json. Para tornar o serviço flexível, tínhamos que fazer isso de forma imperativa, ou seja, dentro da implementação da operação, tínhamos que validar qual o formato desejado pelo cliente, e com isso especificar no contexto da requisição qual o formato que deve ser utilizado pelo runtime do WCF para gerar o resultado. O código abaixo ilustra de forma resumida essa condição:

public class Servico : IContrato
{
    public string Ping(string value)
    {
        WebOperationContext.Current.OutgoingResponse.Format =
            VerificarFormato(...) == "json" ? WebMessageFormat.Json : WebMessageFormat.Xml;

        return "resultado da operação";
    }
}

Geralmente o formato é fornecido por uma query string adicional ou analisando os headers da requisição HTTP, que informa o formato através do content-type. O código acima permite definirmos o formato da resposta baseando-se na preferência do usuário, mas como podemos perceber, há muito código para avaliar isso, e me obriga a misturar a minha implementação com código de infraestrutura.

Para facilitar, a Microsoft criou na versão 4.0 do WCF, uma nova propriedade na classe WebHttpBehavior, chamada AutomaticFormatSelectionEnabled. Trata-se de uma propriedade boleana, que quando definida como True, irá interpretar a requisição e gerar a resposta no mesmo formato estipulado pelo cliente, olhando para a propriedade content-type que está nos presente nos headers da requisição. Para habilitar, podemos recorrer ao seguinte código:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Service">
        <endpoint
          address=""
          binding="webHttpBinding"
          contract="IService"
          endpointConfiguration="edpConfig" />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="edpConfig">
          <webHttp automaticFormatSelectionEnabled="true" />
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Com esta opção habilitada, meu contrato fica totalmente independente do formato, ou seja, se o cliente está requisitando em formato Json, o WCF será capaz de acatar a requisição, processá-la e devolver o resultado no mesmo formato. O mesmo acontece com o Xml. Finalmente, você pode ter um único serviço sendo capaz de receber e retornar mensagens no mesmo formato do cliente, não os obrigando mais ele a trabalhar com o formato específico, que as vezes diferente daquele que é o mais comum.

Tags: , , , , ,

WCF

Escolhendo o gerenciamento de estado no ASP.NET

by Israel Aece 29. July 2010 16:24

Uma das principais necessidades em uma aplicação Web é a manutenção de estado. O protocolo HTTP determina que todas as aplicações que rodam sobre ele, não mantém nenhuma espécie de informação do lado do servidor, ou seja, ao requisitar qualquer recurso, uma vez que o conteúdo é enviado ao cliente solicitante, tudo o que foi gerado do lado do servidor para atende-lo, será descartado. Caso uma nova requisição ocorrer, mesmo que seja para o mesmo recurso, tudo será reconstruído.

Mas o que é o "estado"? Trata-se de dados que representam certas informações para o usuário ou para a aplicação, e que precisam ser armazenadas em algum local para conseguirmos restaurá-las no momento em que precisarmos delas. Depois que a requisição foi atendida pelo servidor, se ele precisar armazenar informações para aquele usuário, precisamos fazer uso de alguma das alternativas que são disponibilizadas pela tecnologia que estamos utilizando, e com isso, sermos capazes de manter esses dados mesmo que o usuário esteja totalmente "desconectado" da aplicação, para que quando ele voltar ao servidor, os dados continuarão disponíveis, refazendo o estado da requisição antes dela ser enviada ao navegador.

As alternativas que temos depende da tecnologia que estamos utilizando. No ambiente Microsoft, a tecnologia para desenvolvimento de aplicações Web é o ASP.NET. Ele, por sua vez, traz uma porção de opções para utilizarmos, e atualmente temos: Application, Caching, Controles Hidden, ViewState, Cookie, Session, Profile, QueryString e Context.Items. Para você escolher o que melhor se encaixa a sua necessidade, é necessário avaliar detalhes como segurança e performance. São essas duas categorias que mais influenciam na escolha de qual opção utilizar para manter as informações.

Para tentar auxiliar na escolha de cada um, criei um fluxograma que tenta exibir a melhor opção de acordo com a necessidade, balanceando entre escopo, performance e segurança. Abaixo temos a imagem que representa esse fluxograma, e na sequência, uma descrição mais detalhada de cada condicional que vemos nele.

  • Compartilhar: O compartilhamento consiste em ter uma informação que pode estar acessível entre vários usuários que acessam a mesma aplicação. Isso quer dizer que qualquer alteração que um usuário faça, já refletirá para todos aqueles que a acessam.
  • Dependência ou Expiração: Algumas vezes, a informação que é compartilhada entre os usuários poderá ter critérios de invalidação. Se você tem algo na memória e quer determinar uma forma de removê-la de lá, você pode optar por dependência ou expiração. A primeira delas consiste em ser reativo, ou seja, seremos notificado quando alguma mudança ocorrer na origem da informação, enquanto a segunda opção, devemos especificar um valor para determinar quando ela deverá ser removida.
  • Acessível entre requisições (escopo de usuário): A ideia desta condição é determina se os dados precisam ou não sobreviver entre a navegação das páginas da aplicação.
  • Dados Complexos ou Sigilosos (escopo de usuário): Para as informações que devem sobreviver entre todas as páginas da aplicação, podemos dividir em dados complexos ou sigilosos. Informações simples, como por exemplo, o idioma selecionado pelo usuário, podemos recorrer aos Cookies para armazená-las. Já aqueles dados que vão muito além do que simples strings, como é o caso de um carrinho de compras, podemos recorrer a recursos mais poderosos, como Session ou Profile.
  • Persistência: Utilizar Session ou Profile dependerá, principalmente, se você quer ou não ter a capacidade de persistir as informações. A principal diferença entre armazenar o carrinho de compras na Session ou não Profile, é que na segunda opção, as informações sobreviverão entre reinicializações do navegador e do servidor, ao contrário do que acontece com a Session [1], que é descartada ao fechar o navegador.
  • Transferência: A transferência consiste em levar dados de uma página para a outra, que geralmente parametrizam as tarefas que esta segunda página irá desempenhar.
  • Transferência no Servidor: Caso você já esteja executando algum código no servidor, então você pode optar pelo Context.Items para enviar os dados para uma outra página, sem a necessidade de voltar ao navegador, e a partir dali, ir para a página solicitada. Utilizar o Context.Items garante que as informações sejam passadas de forma transparente, e o usuário ou até mesmo os interceptadores de requisições HTTP não conseguirão capturar esses dados. Agora, se tivermos a necessidade de parametrizar publicamente a chamada de uma página, então a QueryString é a melhor saída, mas apenas lembre-se de que ela possue limitação de tamanho e você não deve passar dados sensíveis ali.
  • Manter dados na própria página: Para casos onde precisamos manter dados para uso excluso da página que estamos utilizando, podemos recorrer aos controles Hidden [2]. Esse tipo de controle é capaz de guardar informações simples, e o seu uso serve apenas para manter informações que não sejam sensíveis, estando tão vulneráveis quanto as QueryStrings.

[1] - A configuração padrão da Session no ASP.NET faz com que as informações sejam armazenadas InProc, ou seja, na memória do próprio servidor Web. Você pode alterar esse comportamento, e eleger um servidor SQL Server para persistir essas informações. Mas para essa situação, o Profile pode ser melhor, já que possui várias outras características interessantes, como a possibilidade de tipar as informações que serão armazenadas, migração de usuários anônimos, etc.

[2] - No caso do ASP.NET Web Forms, o ViewState também pode ser utilizado para manter as informações em nível de página, mas que irá recorrer ao uso de controles Hidden para o armazenamento delas. É importante dizer que o ViewState é armazenado nestes controles de forma codificada, ou seja, é possível extrair o que armazenamos ali. Seguindo a mesma linha de alguns itens anteriores, você não deve colocar informações sigilosas dentro dele.

Todos as opções para armazenamento do estado são basicamente dicionários, ou seja, utilizam a chave como sendo uma string. Já o valor dependerá do que está utilizando, por exemplo, no caso de Caching, Application, Session, Profile ou Context.Items, você poderá armazenar qualquer objeto, desde que ele esteja decorado com o atributo SerializableAttribute. As outras opções apenas trabalham com simples strings como valor.

Tags:

ASP.NET

Detectando a desconexão - Parte 2

by Israel Aece 29. July 2010 09:16

Há algum tempo eu mostrei aqui como podemos proceder para detectar a desconexão do cliente de um serviço WCF. Aquela solução tem a finalidade de detectar a desconexão do cliente ao tentar enviar um callback para ele, onde podemos analisar o estado do canal de comunicação com o mesmo, e se estiver em estado falho ou se já foi fechado, podemos desprezar a notificação.

O problema daquela técnica é que o serviço somente saberá que o cliente não está mais ativo, quando tentarmos acessar o canal para se comunicar com ele. Se o cliente já encerrou a aplicação, de forma normal ou não (matando o processo), a referência dele ainda existirá dentro do nosso serviço.

Para sermos notificados de que o cliente foi encerrado, novamente, de forma normal ou não, podemos recorrer à implementação da interface IInputSessionShutdown. Para bindings que suportam sessões, essa interface pode ser aplicada ao runtime, e sermos notificados quando o cliente encerrar a conexão com o serviço. Essa interface fornece dois métodos: DoneReceiving e ChannelFaulted. O primeiro método é disparado quando o cliente não puder mais receber as notificações, pois ele encerrou o canal de comunicação com o serviço; já o método ChannelFaulted é disparado quando o canal de comunicação do cliente entra em estado falho, ou seja, algum problema ocorreu na aplicação consumidora do serviço, que não foi capaz de encerrar o proxy de forma explicíta. Abaixo temos uma implementação simples deste recurso:

public class SessionShutdownTrigger : IInputSessionShutdown
{
    public void ChannelFaulted(IDuplexContextChannel channel)
    {
        Console.WriteLine("ChannelFaulted");
    }

    public void DoneReceiving(IDuplexContextChannel channel)
    {
        Console.WriteLine("DoneReceiving");
    }
}

Como podemos notar, ambos métodos recebem como parâmetro um objeto que implementa a interface IDuplexContextChannel, que corresponde ao canal de comunicação do cliente. Com esta classe criada, precisamos acoaplá-la ao runtime do WCF, e para isso recorremos aos pontos de estensibilidade que o WCF fornece, mais precisamente, ao uso da interface IContractBehavior, que nos permite modificar, examinar ou estender aspectos pertinentes à um contrato. Ao implementar essa interface, entre os vários métodos que ela fornece, temos o método ApplyDispatchBehavior, que nos permite interceptar e aplicar algum recurso customizado ao dispatcher do serviço. Justamente por isso, como parâmetro recebemos uma instância da classe DispatchRuntime, que por sua vez, fornece uma propriedade chamada InputSessionShutdownHandlers, que nos permite adicionar instâncias de classes que implementam a interface IInputSessionShutdown. O código abaixo ilustra essa classe:

public class SessionShutdownTriggerBehaviorAttribute : Attribute, IContractBehavior
{
    public void ApplyDispatchBehavior(ContractDescription contractDescription,
        ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.InputSessionShutdownHandlers.Add(new SessionShutdownTrigger());
    }

    //Outros Métodos
}

Essa classe por si só não funciona. Note que ela herda da classe Attribute, que me permite decorar a classe que representa o serviço, e com isso, ao rodá-lo, o WCF será capaz de perceber a presença deste atributo, e executar o método ApplyDispatchBehavior, incluindo a instância da classe SessionShutdownTrigger que criamos acima e, finalmente, quando a cliente encerrar o canal de comunicação, seremos notificados que isso ocorreu, e podemos executar algum código pertinentes aquele cliente, sem a necessidade de somente detectarmos isso quando precisarmos efetivamente comunicar com ele. Abaixo temos o contrato do serviço com esse atributo decorado:

[SessionShutdownTriggerBehavior]
public class Servico : IContrato
{
    public string Ping(string value)
    {
        return value;
    }
}

Tags:

WCF

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

Acessando serviços de forma relativa

by Israel Aece 20. July 2010 22:36

Como sabemos, aplicações Silverlight podem recorrer a recursos que rodam no servidor que a hospeda utilizando WCF. Com isso, a aplicação que serve como host para o serviço é uma aplicação ASP.NET tradicional (WebForms ou MVC, Web Site ou Web Application), onde efetivamente criaremos o serviço WCF para que servirá a aplicação Silverlight.

Uma vez com o serviço pronto, tudo o que precisamos fazer para consumir o serviço no Silverlight, é a referência para o mesmo, utilizando a opção Add Service Reference, fornecida pela IDE do Visual Studio .NET. Como é feito em qualquer aplicação cliente, o Visual Studio extrairá as informações do documento WSDL e criará o proxy do lado do cliente, incluindo um arquivo chamado ServiceReferences.ClientConfig, que conterá as configurações de acesso ao serviço referenciado. O código abaixo ilustra esse arquivo:

<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IContrato"
                 maxBufferSize="2147483647"
                 maxReceivedMessageSize="2147483647">
          <security mode="None" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:1551/ServicoDeTeste.svc"
                binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_IContrato"
                contract="Servico.IContrato"
                name="BasicHttpBinding_IContrato" />
    </client>
  </system.serviceModel>
</configuration>

Como podemos notar, endereço do serviço WCF está definido de forma absoluta. Isso implica em alguns problemas que podemos ter durante a distribuição/instalação do projeto no servidor. Esse arquivo é comprimido para dentro de um outro arquivo, com extensão *.xap, e que por sua vez, é depositado no diretório ClientBin do projeto ASP.NET que hospeda a aplicação Silverlight. Ao colocar isso no servidor onde ele deverá rodar, provavelmente o endereço do serviço mudará, e ficará complicado e trabalhoso para alterar.

Para melhorar isso, a versão 4.0 do Silverlight permite especificar, de forma relativa, o endereço para o serviço, ou seja, não precisamos mais definir o endereço completo até o arquivo *.svc. Apesar da IDE ainda não ser inteligente o bastante para utilizar esse recurso ao referenciar o serviço, nada nos impede de alterar isso diretamente no arquivo de configuração do lado do cliente, apontando relativamente para o serviço WCF. Para o teste, eu criei um serviço na raiz da aplicação ASP.NET, e como a aplicação Silverlight é colocada na pasta ClientBin desta mesma aplicação, tudo o que precisamos fazer é subir um nível (../). Abaixo podemos visualizar essa ligeira mudança, mas que traz uma grande flexibilidade.

<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IContrato"
                 maxBufferSize="2147483647"
                 maxReceivedMessageSize="2147483647">
          <security mode="None" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="../ServicoDeTeste.svc"
                binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_IContrato"
                contract="Servico.IContrato"
                name="BasicHttpBinding_IContrato" />
    </client>
  </system.serviceModel>
</configuration>

Tags: , ,

WCF

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

Influencia do maxRequestLength no WCF

by Israel Aece 9. July 2010 10:17

Como eu comentei neste outro artigo, podemos utilizar no WCF um recurso chamado streaming, que é uma alternativa interessante quando precisamos enviar grandes quantidades de informações, que nestes cenários, o mais comum é o envio ou o recebimento de arquivos. Para que tudo isso funcione, precisamos nos atentar a efetuar algumas configurações no WCF com relação as cotas e timeouts, que impõem limites durante o tráfego das informações.

Em princípio, esses são os únicos cuidados que devemos ter. Poderemos começar a ter outros problemas, se esse serviço for hospedado no IIS. Para que fosse possível hospedar um serviço WCF no IIS, a Microsoft utilizou a estrutura do pipeline do ASP.NET para receber e desviar a requisição para serviços WCF para o seu respectivo handler. Antes de qualquer análise das cotas que configuramos para o serviço, há um detalhe importante que devemos nos preocupar. Trata-se da propriedade MaxRequestLength da classe HttpRuntime, que determina a quantidade máxima (em KBytes) de conteúdo que uma requisição do ASP.NET poderá receber, onde o seu valor padrão é 4 MB. Essa propriedade influencia quando queremos efetuar o upload de um arquivo grande através de uma aplicação ASP.NET.

Como a requisição chega para o IIS, que por sua vez delega o processamento para a estrutura do ASP.NET, o mesmo valida o tamanho deste conteúdo, e se for maior do que o valor estipulado nesta propriedade, você receberá uma exceção do tipo HttpException, com a seguinte mensagem: Maximum request length exceeded. É algo até complicado de se diagnosticar, já que essa exceção não é propagada para o cliente que consome o serviço, e o tracing do WCF neste caso, ajudará a desvendar este problema. De qualquer forma, quando for hospedar um serviço WCF no IIS, é importante que você sincronize as cotas com o atributo maxRequestLength exposto pelo elemento httpRuntime, assim como podemos notar no exemplo abaixo, que está configurado para receber um arquivo de até 100 MB:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <httpRuntime maxRequestLength="102400"/>
  </system.web>
  <system.serviceModel>
    <services>
      <service name="Service">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="IService"
                  bindingConfiguration="config" />
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="config"
                 messageEncoding="Mtom"
                 transferMode="Streamed"
                 maxBufferSize="104857600"
                 maxBufferPoolSize="104857600"
                 maxReceivedMessageSize="104857600">
        </binding>
      </basicHttpBinding>
    </bindings>
</configuration>

Tags: , ,

WCF

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