WCF – Comunicação P2P

Quando ouvimos dizer sobre programas P2P, sempre nos vem a mente aqueles softwares que compartilham diversos conteúdos (ilegal ou não, não é o caso a ser discutido aqui), e que geralmente estão em formato de músicas, vídeos, livros, etc. A ideia é cada ponto publicar o que deseja disponibilizar, para que outros pontos que estão dentro da mesma rede, possam baixar o que lhes interessam.

Mas publicar conteúdo não é a única finalidade deste tipo de comunicação. Podemos usufruir deste tipo de rede para colaboração, que é algo cada vez mais frequente nas empresas, e além disso, a possibilidade de distribuir processamento entre os vários pontos, para que todos consigam cooperar na execução de uma tarefa maior.

Cada um dos pontos que fazem parte de uma rede P2P, são conhecidos como nó (node) ou peer, e a rede como mesh (mesh network). Mas uma rede P2P pode ser definida de duas formas, onde a primeira é considerada pura, ou seja, não existirá um servidor – centralizador – envolvido, e cada ponto atuará como cliente e servidor. Já a outra forma, conhecida como híbrida, teremos um servidor fazendo parte da rede, mas a finalidade dele é apenas de rastrear ou de gerenciar a comunicação entre os pontos, mas não é responsável por armazenar qualquer tipo informação.

Para implementarmos uma rede P2P, a Microsoft disponibilizou dentro do .NET Framework um namespace chamado System.Net.PeerToPeer, que contém todos os tipos necessários para a construção de uma rede deste tipo. Mas como o WCF é o pilar de comunicação dentro do .NET Framework, a Microsoft resolveu permitir a exposição de serviços através do modelo P2P, podendo reutilizar o conhecimento que temos em serviços tradicionais para expor tarefas/dados através deste modelo. Isso evitará conhecermos detalhes de mais baixo nível, que é algo que teríamos que fazer se fossemos utilizar as classes contidas no namespace System.Net.PeerToPeer.

Apesar de utilizar o WCF como meio de criação de aplicações que estarão conectadas via P2P, temos algumas mudanças consideráveis em relação ao contrato e ao consumo destes serviços, justamente pela natureza dele. Para explicar melhor, vamos explorar o contrato, que é o primeiro passo a ser realizado para a construção do serviço. Abaixo temos a interface que possui um método chamado EnviarMensagem que possui duas strings como parâmetro, que representam o remetente e a mensagem, respectivamente. O principal detalhe a ser notar aqui, é a propriedade CallbackContract do atributo ServiceContractAttribute. Geralmente utilizamos uma interface de callback separada, mas como as mensagens enviadas e recebidas terão o mesmo formato, o contrato de callback deverá ser o mesmo que o contrato que descreve o serviço. Isso é referido como contrato simétrico.

[ServiceContract(CallbackContract = typeof(IContrato))]
public interface IContrato
{
    [OperationContract(IsOneWay = true)]
    void EnviarMensagem(string de, string mensagem);
}

Quando desenvolvemos um serviço WCF “tradicional”, o próximo passo é a criação da aplicação que servirá como hospedeira da classe que representará o serviço, e que deverá utilizar a classe ServiceHost (ou uma de suas derivadas) para gerenciar a execução do serviço. Mas como falamos acima, uma das alternativas de redes P2P é não possuir um servidor, ou seja, a criação de uma aplicação para hospedar o serviço, neste caso, não existirá.

Com essa grande diferença, tudo o que precisaremos fazer é a configuração do lado do cliente. E como sabemos, todo serviço WCF deve possuir três características essenciais, conhecidas como Address, Binding e Contract. Aqui o binding será o NetPeerTcpBinding, que foi criado justamente para abstrair toda a comunicação feita sobre o protocolo P2P, e que internamente, recorrerá aos tipos fornecidos pelo namespace System.Net.PeerToPeer, conforme falamos acima.

Seguindo em frente, devemos definir o endereço do serviço, que na verdade será o nome da rede (mesh) que todos os clientes farão parte. Mantendo a mesma padronização imposta pelo WCF, aqui o endereço deverá ser prefixado com net.p2p, a na sequência o nome do serviço. Um pouco mais abaixo, temos a configuração do binding, definindo a porta em que as mensagens serão processadas. Definindo a porta 0 (zero), fará com que o WCF escolha uma porta que não esteja sendo utilizada no momento.

Por fim, e não menos importante, temos o resolver. Como o próprio nome diz, o resolver é o responsável por manter e resolver os IDs daqueles que estão conectados na rede. Quando o canal é aberto, o WCF utiliza o resolver para resolver o ID daqueles que estão conectados à rede, e assim criar uma rede com pontos interconectados. O atributo mode define qual modelo de resolver será utilizado, e você pode escolher uma em três opções, que estão definidas através do enumerador PeerResolverMode, que estão listados abaixo:

  • Pnrp: Utiliza o padrão PNRP (Peer Name Resolution Protocol), que é um protocolo desenvolvido pela Microsoft que habilita a publicação e resolução dinâmica de nomes.
  • Custom: Com esta opção, você pode criar um serviço para a resolução de nomes e IDs daqueles que estão envolvidos na rede. Um exemplo mais detalhado será mostrado abaixo.
  • Auto: Quando configurado com a opção Auto e houver um resolver customizado definido, ele será utilizado; caso contrário, tentará fazer uso do protocolo Pnrp.

Abaixo temos toda a configuração do arquivo App.config da aplicação. Note que neste primeiro momento, estamos utilizando o resolver definido como Pnrp:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address=”net.p2p://servicos/chat”
                binding=”netPeerTcpBinding”
                bindingConfiguration=”bindingConfig”
                contract=”AplicacaoDeChat.IContrato”
                name=”ChatEndpoint”/>
    </client>
    <bindings>
      <netPeerTcpBinding>
        <binding name=”bindingConfig”
                 port=”0″>
          <security mode=”None” />
          <resolver mode=”Pnrp” />
        </binding>
      </netPeerTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Utilizar o PNRP parece ser a melhor alternativa, mas as vezes você está em um ambiente em que ele não é suportado, ou por algum motivo, você quer controlar como esse procedimento é realizado. Para isso que a opção Custom foi criada. Isso nos permitirá construir um serviço WCF, e lá customizar toda a regra de resolução de nomes/IDs.

Para essa customização ser feita, o primeiro passo é criar um serviço que fará essa tarefa. Felizmente o WCF já traz uma classe pronta, que corresponde ao serviço de resolução. Essa classe é chamada de CustomPeerResolverService, e implementa uma interface que define o seu contrato: IPeerResolverContract. Apesar de haver vários membros, essa interface fornece alguns principais métodos, que são autoexplicativos: Register, Resolve e Unregister. Caso essa implementação não te atenda, tudo o que você precisa fazer é criar a sua própria implementação, utilizando esta mesma interface.

Como ele será um serviço WCF puro, precisamos criar uma aplicação que sirva como hospedeira para ele, recorrendo ao ServiceHost. Note que logo abaixo temos o arquivo de configuração correspondente, que está expondo o serviço através do protocolo TCP:

CustomPeerResolverService cprs = new CustomPeerResolverService();

using (ServiceHost host = new ServiceHost(cprs, new Uri[] { }))
{
    cprs.Open();
    host.Open();

    Console.WriteLine(“[ Serviço de Resolução Ativo ]”);
    Console.ReadLine();
}

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name=”System.ServiceModel.PeerResolvers.CustomPeerResolverService”>
        <endpoint address=”net.tcp://localhost:8282/prs”
                  binding=”netTcpBinding”
                  bindingConfiguration=”bindingConfig”
                  contract=”System.ServiceModel.PeerResolvers.IPeerResolverContract” />
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name=”bindingConfig”>
          <security mode=”None”/>
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Quando este serviço estiver ativo, podemos definir o resolver customizado nas aplicações, e como já sabemos, devemos definir o atributo mode como Custom, e logo no interior do elemento resolver, especificarmos as configurações de acesso ao serviço de resolução, tais como o endereço, o binding e sua respectiva configuração. O código abaixo ilustra a mesma configuração que vimos acima, mas agora utilizando um resolver customizado ao invés do PNRP:

<bindings>
  <netPeerTcpBinding>
    <binding name=”bindingConfig”
             port=”0″>
      <security mode=”None” />
      <resolver mode=”Custom”>
        <custom address=”net.tcp://localhost:8282/prs”
                binding=”netTcpBinding”
                bindingConfiguration=”peerBindingConfig” />
      </resolver>
    </binding>
  </netPeerTcpBinding>
  <netTcpBinding>
    <binding name=”peerBindingConfig”>
      <security mode=”None” />
    </binding>
  </netTcpBinding>
</bindings>

Depois do resolver definindo (não importa qual modelo seja), chega o momento que criarmos a aplicação que fará uso deste tipo diferenciado de serviço. Como sabemos, não há um serviço rodando que podemos efetuar a referência. Tudo o que precisaremos fazer aqui é o “consumo direto”, criando manualmente as classes para efetuar essa comunicação.

Para o consumo, é necessário a criação de um canal de comunicação, e o responsável por isso é a classe ChannelFactory<TChannel>. Mas neste caso, o canal deverá ser duplex (bidirecional), o que nos obriga a utilizar uma factory especializada para esta situação, que é a classe DuplexChannelFactory<TChannel>. O construtor desta classe deve receber a instância da classe InstanceContext, que possui acesso à classe do lado do cliente que implementa a interface (contrato) de callback. Além dela, ainda é necessário apontar o nome do endpoint que está no arquivo de configuração, para que a factory possa criar os canais utilizando todas as configurações previamente estabelecidas.

Como estamos construindo um chat, estamos implementando o contrato de callback no próprio formulário. O método EnviarMensagem é o callback, que será disparado quando uma mensagem chegar, enquanto o método Enviar_Click é o tratador do evento Click do botão, que será disparado quando a aplicação desejar enviar uma mensagem. Abaixo temos o formulário já implementado, com algumas linhas de código omitidas por questões de espaço:

public partial class Conversacao : Form, IContrato
{
    private IContratoChannel channel;
    private string nomeDoUsuario;

    public Conversacao(string nomeDoUsuario)
    {
        InitializeComponent();

        this.nomeDoUsuario = nomeDoUsuario;

        this.channel = new DuplexChannelFactory<IContratoChannel>(
            new InstanceContext(this), “ChatEndpoint”).CreateChannel();
        this.channel.Open();
    }

    public void EnviarMensagem(string de, string mensagem)
    {
        this.Mensagens.Text += 
            string.Format(“{0}:{2}{1}{2}{2}”, de, mensagem, Environment.NewLine);
    }

    private void Enviar_Click(object sender, EventArgs e)
    {
        this.channel.EnviarMensagem(this.nomeDoUsuario, this.Mensagem.Text);
    }
}

Distribuindo as Mensagens

Aparentemente tudo funciona da forma correta, mas é importante ter cuidado com o modo em que as mensagens são enviadas para a rede. O P2P utiliza broadcast, ou seja, se uma aplicação mandar uma mensagem para a rede, todos os pontos que estiverem conectados, receberão a mesma. Na imagem acima temos a sensação de que somente as duas pessoas estão conversando, mas se uma outra instância desta mesma aplicação entrar no ar, ela também começará a receber as mensagens.

O WCF possui algumas formas para controlar a distribuição das mensagens entre os pontos que compõem a rede, onde a primeira é através de uma contagem e a segunda utilizando um filtro. No primeiro caso, podemos modificar o nosso contrato, criando ao invés de strings que representam as informações do mensagem, uma classe para incorporar todos os parâmetros necessários, e além disso, devemos incluir um novo campo para contabilizar o número de encaminhamentos que a mensagem deverá fazer.

Como a API que estamos utilizando abstrai grande parte da comunicação entre os pontos, podemos apenas incluir no contrato da mensagem um campo decorado com o atributo PeerHopCountAttribute, que será decrementado a cada ponto que ela for entregue, e quando atingir o número 0, por mais que existam ainda pontos, eles não receberão a respectiva mensagem. Abaixo temos a classe Mensagem com os campos que compõem a estrutura do negócio e um campo chamado Hops, que será utilizando apenas quando estiver sendo exposto através do binding NetPeerTcpBinding.

[MessageContract]
public class Mensagem
{
    [PeerHopCount]
    public int Hops;

    [MessageBodyMember]
    public string De;

    [MessageBodyMember]
    public string Para;

    [MessageBodyMember]
    public string Texto;
}

É importante dizer que o contrato que criamos acima (IContrato), também foi mudado para receber esta nova classe ao invés do parâmetros separados.

Para finalizar, podemos também fazer o uso de filtros, que permite criar alguma espécie de regra para entregar ou não a mensagem. Note que coloquei na classe Mensagem um campo chamado Para que não havia na implementação inicial. A finalidade deste campo é analisar se o ponto que está recebendo a mensagem é mesmo o usuário à qual se destina. Caso seja, entregaremos a mensagem e iremos parar a propagação para os pontos seguintes; caso contrário, não entregaremos e iremos optar por propagar a mensagem adiante.

A criação de um filtro consiste em implementar a classe abstrata PeerMessagePropagationFilter, que nos obriga a sobrescrever o método ShouldMessagePropagate. Esse método possui um parâmetro que corresponde a mensagem que está chegando e um segundo parâmetro do tipo PeerMessageOrigination, que determina se a origem dela é local ou remota. Esse parâmetro é útil para determinar se a mensagem partiu da própria aplicação, e que no nosso caso, deveremos entregar para ela própria para conseguir exibir o conteúdo na tela.

Esse método deve retornar uma das opções definidas no enumerador PeerMessagePropagation, que determinará se a mensagem deverá ser entregue apenas localmente, remotamente, ambos ou não deve fazer nada. Para exemplificar, abaixo temos a classe UserNameChatFilter, que analisará o conteúdo da classe Message que vem como parâmetro e determinará se ele deve ou não ser entregue para a aplicação local, olhando para a origem da mensagem e para o atributo Para do corpo da mensagem.

internal class UserNameChatFilter : PeerMessagePropagationFilter
{
    private string username;

    public UserNameChatFilter(string username)
    {
        this.username = username;
    }

    public override PeerMessagePropagation ShouldMessagePropagate(
        Message message, PeerMessageOrigination origination)
    {
        if (origination == PeerMessageOrigination.Local)
        {
            return PeerMessagePropagation.LocalAndRemote;
        }
        else
        {
            Mensagem temp =
                (Mensagem)TypedMessageConverter.Create(typeof(Mensagem), null).FromMessage(message);

            string to = temp.Para;
            return to == username ? PeerMessagePropagation.Local : PeerMessagePropagation.Remote;
        }
    }
}

Depois da classe criada, temos que adicioná-la à execução, e para isso vamos recorrer ao método GetProperty<T> da classe genérica DuplexChannelFactory<TChannel>. Esse método genérico é utilizado para extrair da channel stack, objetos que são específicos ao modelo de comunicação. Neste caso, vamos tentar recuperar o objeto PeerNode, que corresponde ao ponto atual. Essa classe possui uma propriedade chamada MessagePropagationFilter, que pode receber uma instância de alguma classe que implemente um filtro customizado (PeerMessagePropagationFilter), assim como fizemos acima. O código abaixo ilustra como acoplar o filtro à execução:

this.channel.GetProperty<PeerNode>().MessagePropagationFilter = 
    new UserNameChatFilter(nomeDoUsuario);

Com isso, ao enviar uma requisição para um usuário específico, o filtro entrará em ação, avaliando se deve receber a mensagem ou propagar para o próximo ponto existente na rede. Apesar de conseguirmos atingir o objetivo, talvez esse modelo não seja a melhor saída em algumas situações, inclusive aqui. Como na maioria das vezes queremos conversar com alguém específico, o melhor seria manter um contato direto, ao invés de inundar a rede em busca desta pessoa.

Conclusão: Apesar de mudar um pouco a forma de como se cria serviços WCF, vimos neste artigo que temos mais uma possibilidade de criar aplicações conectadas, que agora também nos permite usufruir de uma rede P2P utilizando um modelo se comunicação ligeiramente diferente do qual estamos habituados a trabalhar.

P2PComWCF.zip (24.53 kb)

Validando a postagem do WIF

Quando estamos utilizando um STS para autenticar uma aplicação ASP.NET (ambiente passivo), somos redirecionados para a aplicação autenticadora, nos identificamos e caso seja um usuário válido, o token será criado e postado para a aplicação (relying party) que requisitou. Essa aplicação utilizará o WIF para extrair as informações do token, validá-las e deixá-las disponíveis para serem utilizadas.

O token é serializado em formato XML, contendo todas as informações de infraestrutura (envolvendo os artefatos de segurança) e as claims. Como o conteúdo da serialização é extenso, o STS envia isso através do body de uma requisição HTTP, que é realizada através do método POST, e é aqui que o problema reside.

Por questões de segurança, o ASP.NET não permite postar requisições que contenham em seu corpo tags, para evitar problemas de cross-site scripting (XSS). Como o WIF posta uma mensagem com conteúdo XML, o ASP.NET barra logo nos primeiros estágios da requisição. Uma solução que existe desde as primeiras versões do ASP.NET, é desligar essa verificação na página que recebe a requisição com o token do WIF. Para isso basta definir o atributo ValidateRequest da diretiva @Page para False.

Desligando essa verificação, iremos conseguir receber o token do WIF, mas abriremos uma porta para que pessoas maliciosas possam explorar e, consequentemente, danificar a nossa aplicação. Felizmente na versão 4.0 do ASP.NET a Microsoft permitiu que se customize essa validação, criando um validador próprio que podemos analisar a requisição, e mesmo que ela esteja postando tags, não quer dizer que ela seja maliciosa, assim como é o caso do token do WIF.

Para criar um validador customizado, devemos criar uma classe que herde da classe RequestValidator. Essa classe fornece um método virtual chamado IsValidRequestString, que deve ser sobrescrito na classe derivada, que determinará se a requisição é válida ou não. Esse método recebe vários parâmetros, que fornecem todas as informações necessárias para criarmos a regra de validação. Entre esses parâmetros temos o contexto da requisição (HttpContext), uma string que representa o valor a ser validado, que trabalha em conjunto com o um parâmetro do tipo RequestValidationSource, que especifica qual dado da requisição está sendo validado (Forms, QueryStrings, Headers, Cookies, etc.).

Como precisamos saber se a requisição trata-se de um token gerado pelo WIF, ele próprio traz métodos que permitem a criação de uma mensagem (RSTR) a partir do conteúdo de uma requisição HTTP. Sendo assim, se o conteúdo da postagem for válido e representa um token, então quer dizer que a requisição contém tags, mas são tags que correspondem a uma espécie de informação que sabemos como lidar, ou seja, não é maliciosa. O exemplo abaixo ilustra como efetuar esta implementação, recorrendo a tipos que o WIF fornece:

public class ValidadorDoToken : RequestValidator
{
    protected override bool IsValidRequestString(HttpContext context, string value,
        RequestValidationSource requestValidationSource, string collectionKey, out int validationFailureIndex)
    {
        validationFailureIndex = 0;

        if (requestValidationSource == RequestValidationSource.Form &&
            collectionKey.Equals(WSFederationConstants.Parameters.Result, StringComparison.Ordinal))
        {
            SignInResponseMessage message =
                WSFederationMessage.CreateFromFormPost(context.Request) as SignInResponseMessage;

            if (message != null)
            {
                return true;
            }
        }

        return base.IsValidRequestString(context, value, requestValidationSource,
            collectionKey, out validationFailureIndex);
    }
}

Como todas as informações da requisição são passadas para o validador, note que a condicional verifica se estamos recebendo o body da requisição, e como o body pode ter vários campos, estamos interessados apenas naquele campo que corresponde ao token serializado que é representado pelo campo wresult.

O ASP.NET MVC também impõe este mesmo tipo de validação, que felizmente recorre a este mesmo validador, ou seja, podemos criar um único e utilizar por qualquer tipo de projeto, WebForms ou MVC. Apenas é necessário tomar cuidado quando você decora a ação (método) com o atributo ValidateInputAttribute. A finalidade deste atributo é justamente permitir a validação da requisição, mas na versão 4.0 do ASP.NET, a adição deste validador customizado fez com aquele atributo seja executado depois do validador, que por sua vez, roda logo no evento BeginRequest.

Curso de WCF 4.0 em Campinas

wcf 4.0Dia após dia as aplicações estão cada vez mais conectadas. Essa interligação viabiliza novas oportunidades de negócios, agilidade e eficiência no processo de uma empresa, beneficiando assim todos aqueles que estão envolvidos.

Dentro da plataforma .NET da Microsoft, o WCF (Windows Communication Foundation) é o pilar de comunicação, que fornece uma infinidade de funcionalidades para expor, consumir e gerenciar serviços, sejam eles para serem consumidos em uma rede local ou através da internet.

Como essa necessidade está cada vez mais presente no dia a dia de cada desenvolvedor, o WCF possui cada vez mais espaço, já que ele substitui todas as tecnologias de aplicações distribuídas criadas pela Microsoft, incorporando em uma plataforma estensível e consistente, os principais modelos de comunicação que existem atualmente.

Tendo em vista todo esse cenário, eu vou ministrar um curso oficial de WCF 4.0, abordando desde os primeiros passos até cenários mais complexos, como roteamento, estensibilidade, segurança (incluindo claims), performance, etc. Abaixo estão listados os tópicos que serão abordados:

  • Módulo 1: Arquitetura Orientada à Serviços
  • Módulo 2: Introdução ao WCF
  • Módulo 3: Hospedagem de Serviços
  • Módulo 4: Contratos
  • Módulo 5: Endpoints e Behaviors
  • Módulo 6: Teste e Debug
  • Módulo 7: Segurança
  • Módulo 8: Tópicos Avançados (Estensibilidade, Processamento Assíncrono e Roteamento)

Como pré-requisitos para este curso, é necessário possuir familiaridade com a plataforma .NET, com o Visual Studio 2008/2010 e experiência em alguma linguagem .NET (C# ou VB.NET), incluindo conceitos relacionados a orientação à objetos.

O curso será ministrado na People Computação, na cidade de Campinas, em São Paulo, com toda infraestrutura fornecida por eles. A People é um centro oficial autorizado Microsoft, que ministra e aplica cursos e exames oficiais, e é onde eu ministro treinamentos desde meados de 2005.

Com 24 horas de duração, o treinamento será realizado toda terça-feira e quinta-feira, das 18:30 às 22:30 (sendo 4 horas por dia), iniciando em 23 de novembro até 09 de dezembro de 2010. A sala terá capacidade para apenas 12 alunos. Para maiores informações, valores e como se inscrever, entre em contato através do telefone (19) 3739-6400.

Stacks de Comunicação do Silverlight

Como sabemos, aplicações Silverlight rodam no cliente, dentro do seu navegador, ou mais recentemente, também temos a possibilidade de desacoplar a aplicação do mesmo, dando a impressão ao usuário de que é uma aplicação que roda localmente. O fato da aplicação rodar “desconectada” do servidor, havia uma grande necessidade de expor funcionalidades e dados para ela, e o WCF eleito como a “ponte” entre os dois lados, para estabelecer a comunicação, permitindo com que as aplicações cheguem até a sua origem e executem alguma tarefa ou extraiam dados necessários para que elas possam trabalhar.

Desde as primeiras versões do Silverlight que temos APIs dentro dele para comunicação HTTP e a API do WCF (System.ServiceModel.dll), para abstrair toda a complexidade de consumir um serviço SOAP. Essas APIs recorrem aos recursos expostos pelo navegador, estando assim condicionadas as suas limitações. Apesar de algumas dessas limitações, como era o caso da propagação de erros do serviço para o cliente, gerenciamento de cookies e chamada concorrente para serviços, essas APIs funcionavam razoavelmente bem. Mas a questão é que os serviços tem evoluído cada vez mais, obrigando a Microsoft a melhorar o modelo de comunicação dentro do Silverlight.

O que tínhamos até a versão 2.0 é o que chamamos de Browser Http Stack. Com ela, conseguíamos efetuar comunicações com serviços, mas com algumas limitações, pois estávamos condicionados aos recursos que o navegador oferecia, afinal, ela é apenas um wrapper para ele. Além de não conseguir interpretar um erro SOAP corretamente, uma outra limitação é a possibilidade de executar serviços além dos tradicionais verbos POST e GET do protocolo HTTP. Como serviços REST estão cada vez mais populares, existe uma grande necessidade de suportar mais do que estes dois verbos.

Para resolver estes tipos de problemas, e tentar acomodar novas funcionalidades, a Microsoft incluiu na versão 3.0 do Silverlight, uma nova forma de comunicação, chamada de Client Http Stack. A palavra “Client” é porque agora a comunicação utiliza os recursos fornecidos pelo sistema operacional, burlando as limitações impostas pelo navegador. Além de resolver os problemas que vimos acima, ela permite interpretar todos os headers, melhor forma de manipular cookies, códigos de status das respostas, algumas melhorias na parte de autenticação, etc.

Uma das preocupações da Microsoft foi em manter a modelo de utilização das classes que você já utiliza para efetuar a comunicação com algum desses serviços, podendo alterar entre uma das stacks, sem precisar mudar algo no código que faz o consumo do serviço.

Por padrão o Silverlight continua utilizando o modelo tradicional (Browser Http Stack), e se você quiser utilizar o Client Http Stack, terá que explicitamente dizer isso à ele. Para isso, devemos utilizar o método estático RegisterPrefix da classe WebRequest (namespace System.Net). O primeiro parâmetro consiste na URI (http://www.site.com.br) ou apenas o prefixo (http ou https) para onde quer se comunicar. Já o segundo parâmetro indica qual dos modelos utilizar (Browser ou Client). Esse método retorna um valor boleano, indicando se o registro foi ou não realizado com sucesso. O código abaixo ilustra como podemos proceder para efetuarmos essa configuração:

WebRequest.RegisterPrefix(“http://&#8221;, WebRequestCreator.ClientHttp);

Caso queira retornar a configuração padrão, então poderá utilizar a propriedade estática BrowserHttp, também exposta pela classe WebRequestCreator, assim como é exibida abaixo:

WebRequest.RegisterPrefix(“http://&#8221;, WebRequestCreator.BrowserHttp);

Autorização com WIF

Dentro do desenvolvimento de software, o termo autorização descreve o processo de verificar se um determinado usuário possui ou não o acesso à um determinado recurso. Mas é importante dizer que a autorização acontece depois da autenticação, pois primeiramente é necessário saber quem o usuário é, para depois saber que direitos ele tem no sistema.

Recentemente eu comentei em alguns artigos sobre a utilização de um sistema de identidade para autenticar os usuários. Uma das principais finalidades é a terceirização do processo de autenticação, delegando isso à uma outra aplicação/serviço, e assim, todas as aplicações que confiarem naquele autenticador (STS), poderão fazer uso do mesmo para autenticar seus respectivos usuários, ficando essas aplicações totalmente isentas em como isso deve acontecer.

Como comentando nos artigos acima, o WIF (Windows Identity Foundation) abstrai grande parte da complexidade para fazer isso tudo funcionar, mas a autorização ainda continua sendo uma responsabilidade da própria aplicação. A finalidade deste artigo é apresentar algumas alternativas para efetuar a autorização nas aplicações, utilizando as claims emitidas por um determinado autenticador para refinar o acesso.

Grande parte das aplicações fazem uso do modelo baseado em papéis (roles), onde concedem ou negam acesso à algum recurso da aplicação se o usuário está ou não contido em um grupo específico. Tecnicamente falando, sempre recorremos a utilização de alguma implementação da interface IPrincipal, que fornece um método chamado IsInRole, que dado o papel retorna um valor boleano indicando se o usuário corrente está ou não contido nele.

Como já comentei anteriormente, a autorização baseada em claims é muito mais flexível do que isso, ou seja, não estamos condicionados a somente trabalhar com papéis (simples strings), mas também avaliar se o usuário poderá ou não acessar um determinado recurso a partir da sua idade, ou somente se o seu cartão de crédito estiver válido, e assim por diante.

Uma das grandes preocupações que a Microsoft teve, foi em manter compatibilidade com o modelo de objetos que utilizamos desde a primeira versão do .NET Framework, mas conseguindo já tirar proveito deste novo modelo. E foi justamente pensando nisso, que ela criou as interfaces IClaimsIdentity e IClaimsPrincipal. Elas já foram comentadas com mais detalhes neste artigo, e somente para recapitular, uma das principais propriedades é a Claims, exposta pela interface IClaimsIdentity, que fornece o conjunto de claims que foram emitidas pelo autenticador para o usuário atual.

A propriedade RoleClaimType também foi criada com propósito de compatibilidade. Como todas as informações emitidas por um STS chegam para uma relying party através de claims, é necessário saber qual ou quais as claims representam os papéis (IPrincipal.IsInRole). Com isso, podemos continuar utilizando a autorização baseada em papéis, mas nos bastidores, são claims que estão sendo utilizadas. Com isso, por mais que começamos a utilizar as técnicas de autenticação através do WIF, a autorização pode continuar da forma que está, sem a necessidade de sofrer qualquer alteração.

Como sabemos, entre as claims que existem predefinidas nas especificações, ainda podemos criar claims customizadas. Isso quer dizer que um STS pode emitir uma claim sem vincular ela ao tipo role, que por padrão, a aplicação utiliza quando chamamos o método IsInRole. Mas felizmente isso pode ser configurado de forma declarativa, onde cada aplicação determina qual a claim que ela vai quer utilizar/avaliar quando o método IsInRole for chamado. Para efetuar essa customização, podemos recorrer ao sub-elemento roleClaimType do elemento samlSecurityTokenRequirement, assim como é mostrado abaixo:

<samlSecurityTokenRequirement>
  <roleClaimType value= “http://MinhaAplicacao/departamento&#8221; />
</samlSecurityTokenRequirement>

Como comentado acima, podemos continuar trabalhando com a segurança baseada em papéis. E para isso, utilizamos o método IsInRole exposto pela interface IPrincipal ou através da classe PrincipalPermission, ou ainda, do atributo PrincipalPermissionAttribute, que nos permite trabalhar de forma imperativa ou declarativa, respectivamente. Todas deverão recorrer as claims do tipo role (http://schemas.microsoft.com/ws/2008/06/identity/claims/role) ou alguma outra, conforme configuramos acima. Mas o WIF também fornece um novo par de classes para trabalhar explicitamente com claims, a saber: ClaimsPrincipalPermission e ClaimsPrincipalPermissionAttribute. Ambas também servem para utilizar de forma imperativa ou declarativa, respectivamente, mas como já podemos perceber, vai além de somente utilizar claims como roles. Podemos aqui utilizar outras claims que foram emitidas pelo STS, e tornar o processo de autorização muito mais inteligente.

Como comentado, o uso pode ser de forma imperativa ou declarativa. Utilizar um modo ou outro tem suas vantagens e desvantagens. De qualquer forma, a utilização é extremamente fácil, pois é semelhante ao modelo que já estamos acostumados a trabalhar dentro da plataforma .NET. Para exemplificar, o exemplo abaixo mostra a utilização de ambas as formas:

[ Modelo Imperativo ]

public string AlgumaOperacao()
{
    new ClaimsPrincipalPermission(“AlgumaOperacao”, “Executar”).Demand();

    //Processamento do Método
}

[ Modelo Declarativo ]

[ClaimsPrincipalPermission(SecurityAction.Demand, Resource = “AlgumaOperacao”, Action = “Executar”)]
public string AlgumaOperacao()
{
    //Processamento do Método
}

Apesar de aparentemente funcionar como o modelo baseado em papéis, o WIF nos induz a não fixar o valor das claims pelo nosso código, que é algo que mais cedo ou mais tarde sempre acaba mudando, e justamente por isso que temos as propriedades Resource e Action, mas não devemos nos preocupar com o que significado delas agora, pois o seu uso está condicionado ao ambiente em que são utilizadas, e que serão detalhados mais abaixo. Um detalhe importante também é que as duas classes que representam a permissão não são responsáveis por efetivamente validar se o usuário poderá ou não acessar aquele recurso; isso é delegado à uma outra classe, chamada ClaimsAuthorizationManager, qual veremos a seguir.

O processo de autorização é altamente customizável, o que nos permite interceptar o momento em que o runtime avalia se o usuário possui ou não permissão, e com isso alterar o comportamento padrão, nos baseando em outras claims que foram emitidas para tomar a decisão se o usuário deverá ou não acessar o recurso.

Independentemente de qual ambiente estamos falando (ativo ou passivo), as classes que lidam com o processo de autorização são as mesmas. A diferença consiste nas informações que são colocadas dentro dessas classes, pois elas são contextualizadas, condicionados ao ambiente. A principal classe que irá gerenciar isso é a ClaimsAuthorizationManager, que fornece um local central para analisarmos a requisição e determinar se o usuário poderá ou não acessar o recurso. Essa classe possui um único método chamado CheckAccess, que é disparado antes de acessar o recurso em si, e dado o contexto da requisição, retorna um valor boleano indicando se o usuário terá acesso ao mesmo, e que por padrão sempre permite o acesso.

Como dito acima, o método CheckAccess recebe o contexto da requisição como parâmetro, fornecendo todas as informações necessárias para a tomada de decisão. Esse parâmetro é do tipo AuthorizationContext, e possui três principais propriedades: Principal, Resource e Action. A primeira propriedade retorna uma instância da classe ClaimsPrincipal, que representa o usuário atual. Já a propriedade Resource determina o recurso que você está tentando acessar, e isso varia de acordo com o ambiente. Finalmente, a propriedade Action determina a operação que está sendo aplicada ao recurso, e assim como a propriedade Resource, também varia de acordo com o ambiente.

Quando implementamos a classe ClaimsAuthorizationManager para o ambiente passivo, a propriedade Resource trará o caminho e nome da página ASPX que estamos tentando acessar e a Action deve retornar se está sendo acessada via GET ou via POST. Já no ambiente ativo, a propriedade Resource deverá representar o endereço do serviço que está sendo acessado, enquanto a propriedade Action deverá refletir a SOAPAction da operação. A propriedade Principal tem o mesmo significado para ambos ambientes, ou seja, representar o usuário que está tentando acessar o recurso. Como há algumas outras diferenças entre os dois ambientes, vamos analisar a sua implementação individualmente a partir de agora.

Autorização no Ambiente Passivo – ASP.NET

A implementação da classe ClaimsAuthorizationManager para o ambiente passivo, consistirá em avaliar se a página (ASPX) que o usuário está acessado poderá ou não ser acessado, de acordo com uma regra determinada. Cada página que for acessada por aquele usuário, o runtime do WIF em conjunto com o ASP.NET invocará o método CheckAccess, que é onde terá toda a lógica de autorização.

Como exemplo, vamos somente permitir o acesso à uma determinada página se o usuário for maior que 21 anos de idade. Como o STS emite uma claim chamada dataDeNascimento, podemos efetuar o cálculo em cima dela, para avaliar se o usuário poderá ou não acessar a página. Abaixo temos a implementação:

public class AutorizadorDePaginas : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        if (context.Resource.First().Value == “http://localhost:2721/PaginaRestrita.aspx&#8221;)
        {
            var claimDeDataDeNascimento =
                (
                    from in context.Principal.Identities[0].Claims
                    where c.ClaimType == “http://MinhaAplicacao/dataDeNascimento&#8221;
                    select c
                ).FirstOrDefault();

            if (claimDeDataDeNascimento != null)
            {
                DateTime dataDeNascimento = Convert.ToDateTime(claimDeDataDeNascimento.Value);
                return (DateTime.Now.Subtract(dataDeNascimento).Days / 365) >= 21;
            }

            return false;
        }

        return true;
    }
}

Em primeiro lugar verificamos se a página que está sendo acessada deve ser verificada quanto a permissão. Como sabemos que devemos filtrar o acesso a partir da idade, então vamos em busca de uma claim que represente a data de nascimento do usuário, e se encontrada, verificamos se ele já possui mais que 21 anos. Se ele tiver, então ele pode acessar, do contrário, terá o acessado negado.

Mas essa implementação por si só não funciona. Temos que saber como acoplá-la ao pipeline de processamento do ASP.NET. Na verdade, o responsável por invocar o método CheckAccess será o módulo ClaimsAuthorizationModule, que por padrão, não está configurado. Esse módulo se vincula ao evento AuthorizeRequest do objeto HttpApplication, e quando é disparado, irá extrair o autorizador configurado na seção do WIF e, consequentemente, irá passar a instância da classe AuthorizationContext, onde o Resource representará o caminho até a página ASPX e a Action deve representar o verbo HTTP (GET ou POST). Abaixo temos a configuração no arquivo Web.config que correspondem ao módulo e ao autorizador:

<system.web>
  <!– Outras Configurações –>
  <httpModules>
    <!– Outros Módulos –>
    <add name=”ClaimsAuthorizationModule”
         type=”Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, …”/>
  </httpModules>
</system.web>
<microsoft.identityModel>
  <service>
    <!– Outras Configurações –>
    <claimsAuthorizationManager
        type=”Servico.AutorizadorDePaginas, Servico, Version=1.0.0.0, …”/>
  </service>
</microsoft.identityModel>

Autorização no Ambiente Ativo – WCF

Já no WCF vamos trabalhar de forma parecida ao que vimos acima, ao que diz respeito a forma de implementar a classe responsável pela autorização do usuário. As ligeiras diferenças estão nas informações que são colocadas na classe AuthorizationContext e como ela é acoplada à execução.

Neste ambiente, a propriedade Resource representará o serviço que está sendo acessado, mais precisamente, o endereço do mesmo. Já a propriedade Action determinará a SOAPAction que o usuário quer invocar. A SOAPAction é um header que é incluído na requisição HTTP, e determina intenção da requisição SOAP. Esse header é representado por uma URI que não aponta necessariamente para um local válido na internet, e que indicará qual o método a ser invocado.

A ideia aqui é justamente analisar a SOAPAction, verificando se trata-se de uma operação que exige a validação da permissão do usuário. Caso seja, então vamos verificar se ele possui a idade suficiente para acessar a operação em questão. Abaixo temos a implementação, bem próxima a qual fizemos no ambiente passivo:

public class AutorizadorDeOperacoes : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        if (context.Action.First().Value == “http://tempuri.org/IConsultasFinanceiras/RecuperarLimiteDeCredito&#8221;)
        {
            var claimDeDataDeNascimento =
                (
                    from c in context.Principal.Identities[0].Claims
                    where c.ClaimType == “http://MinhaAplicacao/dataDeNascimento&#8221;
                    select c
                ).FirstOrDefault();

            if (claimDeDataDeNascimento != null)
            {
                DateTime dataDeNascimento = Convert.ToDateTime(claimDeDataDeNascimento.Value);
                return (DateTime.Now.Subtract(dataDeNascimento).Days / 365) >= 21;
            }

            return false;
        }

        return true;
    }
}

A principal diferença aqui é como o WIF acopla isso à execução no WCF, que deve executá-la sempre quando uma nova requisição chegar ao serviço. Para que o serviço faça uso do WIF, é necessário submetermos a instância da classe ServiceHost para o método estático ConfigureServiceHost da classe FederatedServiceCredentials, assim como já foi mostrado neste artigo. Assim como no ASP.NET, o que o WIF faz aqui é utilizar um ponto de estensibilidade do WCF para adicionar a implementação do ClaimsAuthorizationManager, e para isso, o WIF já possui implementado uma classe que deriva de ServiceAuthorizationManager, que é a IdentityModelServiceAuthorizationManager, e em seu método CheckAccessCore captura o autorizador implementado/configurado pelo WIF e invoca o seu método CheckAccess, abastecendo a instância da classe AuthorizationContext com informações que estão dentro do contexto da operação (OperationContext).

Sendo assim, tudo o que precisamos aqui é configurar o autorizador customizado, para que o runtime do WIF possa capturá-lo e, consequentemente, entregar ao WCF para efetuar a validação. O código abaixo ilustra essa configuração, que é realizada da mesma forma que no ambiente passivo:

<microsoft.identityModel>
  <service>
    <!– Outras Configurações –>
    <claimsAuthorizationManager
        type=”Servico.AutorizadorDeOperacoes, Servico, Version=1.0.0.0, …”/>
  </service>
</microsoft.identityModel>

Claims Explícitas

Para ratificar o que disse acima, em ambos cenários não temos as claims espalhadas pela aplicação. Como isso pode mudar, o ideal é manter centralizado, para facilitar a alteração caso ela seja necessária.

O mesmo vale para as classes que falamos acima: ClaimsPrincipalPermission e ClaimsPrincipalPermissionAttribute. Quando as utilizamos, nós vimos que tudo o que precisamos colocar ali é o Resource e Action, que determinam informações sobre o acesso e não sobre a claim que deve ser utilizada/avaliada.

Essas classes não são responsáveis por fazerem a verificação. Intermamente elas recorrem a implementação da classe ClaimsAuthorizationManager, que deve estar configurada da forma que vimos acima. Caso não tenhamos um autorizador explicitamente definido, o WIF utilizará uma implementação padrão, que sempre concederá o acesso. O mais interessante aqui é que as informações que colocamos nestas classes também são enviadas para a classe que herda de ClaimsAuthorizationManager, pois elas invocam o método CheckAccess, abstecendo o AuthorizationContext com as respectivos informações.

Nativamente o WIF não traz suporte para vinculação de claims à um determinado recurso, mas podemos criar algo customizado para isso. A ideia é ter algum repositório para catalogar as políticas de acesso à determinados recursos, por exemplo, para acessar uma determinada página ASPX ou uma operação de um serviço, precisamos verificar se a quantidade de anos da claim dataDeNascimento é maior que 21. Esse repositório pode ser um arquivo Xml, uma base de dados ou até mesmo o próprio arquivo de configuração (*.config). Criando algo customizado, nos permite tornar a implementação do ClaimsAuthorizationManager mais simples, dinâmica e ainda mantendo a centralização.

Conclusão: Vimos neste artigo o processo de autorização de uma aplicação que opta por fazer o uso do WIF para terceirizar a autenticação do usuário. Apesar de autorização ainda ser responsabilidade da relying party, o WIF se preocupou em fornecer classes para tornar esse processo mais suave, e não menos importante, mantendo compatibilidade com o legado, que são aplicações que se baseiam em papéis para refinar o acesso aos seus respectivos recursos.

Inspetor de mensagens para o WIF

Recentemente o Dominick Baier lançou um inspetor de requisições para o Fiddler, que tem a finalidade de mostrar as mensagens geradas pelo WIF de uma forma mais amigável. Abaixo podemos visualizar a inspetor já instalado e em funcionamento. Note que podemos visualizar as claims que foram emitidas pelo STS, facilitando eventuais depurações que talvez serão necessárias durante a utilização do WIF.

Efetuando chamadas entre domínios

Muitas vezes construímos serviços para que sejam consumidos por aplicações que estão hospedadas no mesmo local. Um exemplo é quando criamos esses serviços para expor alguma funcionalidade, e que sejam consumidos através do jQuery ou do Silverlight. Ambas tecnologias possuem bibliotecas que facilitam a comunicação com este tipo de serviço.

Mas isso tudo funciona muito bem enquanto os serviços que são acessados estão dentro do mesmo domínio. Se desejar consumir serviços que estão além do domínio de onde a aplicação está hospedada, teremos que recorrer a técnicas diferentes, dependendo da tecnologia que estamos utilizando. A finalidade do artigo é mostrar como devemos proceder para permitir o consumo em cada uma delas.

jQuery

Como sabemos, o jQuery é uma biblioteca que facilita várias tarefas em JavaScript, abstraindo grande parte da complexidade que teríamos se fossemos utilizar uma funcionalidade diretamente. Uma dessas abstrações é o consumo de serviços, como já detalhei neste outro artigo.

O consumo de serviços funciona bem desde que ele esteja debaixo do mesmo domínio da aplicação Web, que é aquela que consome o serviço. Se ele estiver além, então precisamos de alguma técnica para possibilitar que essa tarefa seja realizada. Uma opção seria a criação de um serviço nesta aplicação cliente, que serviria como um wrapper, e este por sua vez, consumiria diretamente o serviço, sem as imposições de chamada entre domínios que o navegador impõe. Com isso, o código JavaScript irá consumir este serviço local, que por sua vez, encaminharia a requisição para o serviço remoto.

Apesar desta técnica funcionar, ela acaba sendo uma solução ruim, já que teremos que envolver outros elementos para realizar uma tarefa relativamente simples. Felizmente o jQuery fornece nativamente o suporte a uma técnica conhecida como JSONP (JSON with Padding).

O seu funcionamento não é muito complicado. Como disse acima, requisições entre domínios não são permitidas, mas há uma única exceção: a tag , ou seja, podemos definir no elemento src (source) a URL para um recurso que está além do nosso domínio, que o navegador não irá proibir o acesso. O que o JSONP faz é justamente o uso dela, criando dinamicamente esta tag, e definindo no atributo src a URL do serviço que estamos tentando acessar.

Na verdade isso não funciona sozinho, ou seja, precisa de uma certa colaboração por parte do serviço, para que o cliente possa processar o resultado da forma correta. Além dos parâmetros que são exigidos pelo método do serviço, o jQuery inclui um parâmetro chamado callback, que é o nome de uma função criada temporariamente no cliente. Aqui entra em cena a infraestrutura do serviço, que deve ser capaz de retornar o resultado (dados) envolvido nesta função, e quando o mesmo chegar ao cliente, ele invocará para capturar o resultado e encaminhá-lo para o nosso código, e assim iremos manipular da forma que acharmos mais conveniente.

Para fazer tudo isso funcionar, precisamos nos atentar à alguns detalhes do lado do cliente e do lado do serviço. Do lado do cliente, tudo o que precisamos fazer é indicar para a API de AJAX do jQuery que ela deve utilizar JSONP. Para isso, devemos recorrer ao atributo dataType, que indica o tipo de dado/formato que você está esperando que o servidor te retorne. No nosso caso, vamos apontar jsonp, assim como é mostrado no código abaixo:

    function Recuperar() {
        $.ajax(
            {
                type: “GET”,
                url: “http://localhost:1446/ServicoDeUsuarios.svc/Recuperar&#8221;,
                data: “nome=Israel”,
                dataType: “jsonp”,
                contentType: “application/json”,
                success:
                    function (usuario) {
                        alert(usuario.Nome);
                    }
            }
        );
    }

Como já sabemos, o serviço também precisa colaborar com isso. Para que ele consiga gerar o resultado da forma que esperamos, precisamos efetuar uma configuração no binding. Para expor um serviço para clientes AJAX, o WCF fornece um binding chamado WebHttpBinding e, consequentemente, é ele mesmo que expõe uma propriedade chamada CrossDomainScriptAccessEnabled, que recebe um valor boleano indicando se o serviço poderá ou não ser invocado através de outros domínios. Quando definido como True (o padrão é False), ele retornará o resultado da forma que o JSONP espera, e com isso, a chamada será efetuada com sucesso. O código abaixo ilustra a configuração de um serviço que poderá ser invocado através de outros domínios:

<?xml version=”1.0″?>
<configuration>
  <system.web>
    <compilation debug=”true”
                 targetFramework=”4.0″ />
  </system.web>
  <system.serviceModel>
    <services>
      <service name=”Servico.ServicoDeUsuarios”>
        <endpoint address=””
                  binding=”webHttpBinding”
                  bindingConfiguration=”bindingConfig”
                  contract=”Servico.IContratoDeUsuarios” />
      </service>
    </services>
    <bindings>
      <webHttpBinding>
        <binding name=”bindingConfig”
                 crossDomainScriptAccessEnabled=”true” />
      </webHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Se monitoramos as requisições através de uma ferramenta como o Fiddler, podemos reparar o que acontece nos bastidores. Abaixo temos a requisição e a sua respectiva resposta, com alguns headers omitidos para tornar a leitura mais simples. Note que o fato de definirmos o atributo dataType como jsonp fez com que um parâmetro chamado callback fosse atribuído à coleção de querystrings da requisição. Em seguida, no corpo da resposta temos os dados gerados do lado do servidor envolvidos na função que foi gerada do lado do cliente.

[ Requisição ]
GET http://127.0.0.1:1446/ServicoDeUsuarios.svc/Recuperar?callback=jsonp1284996021792&nome=Israel HTTP/1.1
Accept: */*
Host: 127.0.0.1:1446
Connection: Keep-Alive

[ Resposta ]
HTTP/1.1 200 OK
Date: Mon, 20 Sep 2010 15:20:24 GMT
Content-Length: 51
Content-Type: application/x-javascript
Connection: Close

jsonp1284996021792({“Codigo”:123,”Nome”:”Israel”});

Silverlight

Serviços que são consumidos por clientes Silverlight também sofrem o mesmo problema quando precisam consumir serviços que estão além de seu domínio de origem. Como sabemos, uma aplicação Silverlight não funciona sozinha, ou seja, ela é sempre hospedada em uma aplicação Web normal, e consumir serviços que estão debaixo dessa aplicação, não haverá qualquer problema e nenhuma tarefa extra precisa ser realizada, já que esse tipo de comunicação é permitida.

Mas ao tentar consumir um serviço que está fora do domínio de origem da aplicação, então receberemos uma exceção do tipo CommunicationException, indicando que a chamada entre domínios não é permitida.

Para resolver este problema, temos que colocar um arquivo Xml no mesmo local onde o serviço encontra-se hospedado (no servidor remoto), indicando que este serviço poderá ser consumido pelo cliente A, B ou C, ou se desejar, por qualquer aplicação. O Silverlight pode trabalhar com dois tipos de arquivos: ClientAccessPolicy.xml ou CrossDomain.xml. O primeiro arquivo foi desenvolvido pela própria Microsoft, enquanto o segundo já é utilizado por aplicações Flash, e para reutilizar, a Microsoft também incorporou no Silverlight a capacidade de utilizar este mesmo arquivo, sem a necessidade de criar um segundo só para atender as requisições a partir de clientes Silverlight.

Quando a aplicação Silverlight for efetuar a requisição para um serviço, ela primeiramente tenta fazer o download do arquivo ClientAccessPolicy.xml no mesmo domínio do serviço; caso não encontre, então ela tentará efetuar o download do arquivo CrossDomain.xml, e se mesmo assim não encontrá-lo, então a exceção mencionada acima será disparada. Ao encontrar um destes dois arquivos, o Silverlight analisará se no seu conteúdo, existe uma entrada dizendo que o serviço pode ser consumido pela aplicação cliente em questão, e se puder, efetuará a chamada para o serviço.

Abaixo existe a estrutura do arquivo ClientAccessPolicy.xml, já configurado para permitir que somente uma determinada aplicação possa consumí-lo. E você pode elencar ali quantas aplicações quiser, e se desejar, pode colocar apenas uma única entrada, definindo o atributo uri como *, que determina que toda e qualquer aplicação poderá consumir os serviços que rodam naquele local.

<?xml version=”1.0″ encoding=”utf-8″?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers=”*”>
        <!–
            Habilitar para todos os clientes
            <domain uri=”*”/>
        –>
        <domain uri=”http://localhost:2611/&#8221; />
      </allow-from>
      <grant-to>
        <resource path=”/”
                  include-subpaths=”true”/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

Com tudo isso configurado, podemos ver na imagem abaixo a requisição procurando pelo arquivo acima, e depois de encontrado e analisado, procede para a chamada efetiva para o respectivo serviço:

Conclusão: Vimos no decorrer deste artigo as possibilidades que temos para permitir que um serviço WCF possa ser consumido através de domínios diferentes, algo que é cada vez mais comum em um mundo cada dia mais conectado.

ChamadasEntreDominios.zip (213.04 kb)

Recursos das Palestras do TechEd Brasil 2010

Conforme havia comentado aqui, efetuei duas palestras no TechEd Brasil 2010, onde a primeira foi para falar sobre serviços REST no WCF, e a segunda sobre WIF.

Gostaria de agradecer a todos os presentes, e é importante dizer que qualquer dúvida, crítica ou sugestão é sempre bem-vinda. Obrigado também ao Rodolfo Roim, Rogerio Cordeiro, João Paulo Clementi, Fabio Hara, Renato Haddad e ao Waldemir Cambiucci, que me ajudaram direta ou indiretamente para que essas palestras fossem realizadas.

Substituindo ASMX por WCF no servidor

Os ASP.NET Web Services (ASMX) são baseados na especificação WS-I Basic Profile, gerando as mensagens na versão 1.1 do SOAP. Eles foram que foram introduzidos desde a primeira versão do .NET Framework, e mais recentemente foram substituídos pelo WCF.

Para manter a interoperabilidade, a Microsoft criou um binding chamado BasicHttpBinding, que possui as mesmas características dos serviços ASMX. Com isso, podemos substituir tanto o serviço quanto o cliente utilizando a API do WCF. Ao optar pela substituição do lado do serviço, precisamos nos preocupar com a SOAPAction.

A SOAPAction é um header que é incluído na requisição HTTP, e determina intenção da requisição SOAP. Esse header é representado por uma URI que não aponta necessariamente para um local válido na internet. O problema reside justamente aqui. Imagine que temos um serviço (ASMX), que disponibiliza apenas uma única operação:

[WebService(Namespace = “http://tempuri.org/&#8221;)]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Servico : System.Web.Services.WebService
{
    [WebMethod]
    public string Ping()
    {
        return “ping!”;
    }
}

Os clientes que o consomem devem enviar as requisições da seguinte forma:

POST /Servico.asmx HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.4952)
Content-Type: text/xml; charset=utf-8
SOAPAction: “http://tempuri.org/Ping&#8221;
Host: 127.0.0.1:51982
Content-Length: 288
Expect: 100-continue
Connection: Keep-Alive

<?xml version=”1.0″ encoding=”utf-8″?>
    <soap:Envelope …>
        <soap:Body>
            <Ping xmlns=”http://tempuri.org/&#8221; />
        </soap:Body>
</soap:Envelope>

Agora queremos substituir o serviço para o WCF. Optamos então por criar o contrato e, consequentemente, o implementamos em uma classe que representará o serviço. A interface do contrato está definida abaixo:

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    string Ping();
}

O ponto mais importante é expor o serviço através do binding BasicHttpBinding, que não será abordado aqui. Se analisarmos o WSDL gerado pelo serviço, veremos que a SOAPAction gerada é: http://tempuri.org/IContrato/Ping. Como podemos perceber, ela leva o nome do contrato. Com isso, os clientes existentes não conseguirão mais enviar mais mensagens para o serviço, já que o WCF utiliza a SOAPAction para identificar a operação a ser disparada, e como ela é diferente, não a encontrará. Abaixo temos a descrição do erro que ocorre:

Unhandled Exception: System.Web.Services.Protocols.SoapHeaderException: The message with Action ‘http://tempuri.org/Ping&#8217; cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver.  Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).

Para resolvermos este problema, temos que recorrer a propriedade Action que é definida no atributo OperationContractAttribute, e lá colocarmos a mesma SOAPAction que os clientes estão enviando, mantendo assim a compatibilidade e não sendo necessária qualquer mudança por parte daqueles que o consomem.

[ServiceContract]
public interface IContrato
{
    [OperationContract(Action = “http://tempuri.org/Ping&#8221;)]
    string Ping();
}

Tomando este cuidado, podemos tranquilamente substituir serviços construídos em ASMX para WCF, sem afetar qualquer cliente que já consuma o mesmo. É importante dizer também que é possível consumir um serviço escrito em ASMX através da API do WCF do lado cliente, mas isso está fora do escopo deste artigo. E para finalizar, sempre se atente aos namespaces.

WCF Tooling

A criação de serviços WCF envolve a construção de um contrato (interface), e depois a classe que o representará. Em seguida, para podermos expor ele para que os clientes possam consumir, precisamos utilizar algum host. Isso exige a configuração dos endpoints, behaviors, bindings, etc., e isso pode ser feito de forma imperativa ou declarativa.

Se tivermos toda a implementação pronta (a interface do contrato e a classe do serviço), podemos recorrer à algumas ferramentas que o WCF fornece, evitando assim escrever código para hospedar e/ou consumir o serviço. Utilizando essas ferramentas nos permite avaliar o comportamento, talvez analisar a performance, e tudo isso, sem a necessidade de gastar tempo configurando o servidor (o IIS por exemplo) ou criar um código para consumir o serviço. Para nos auxiliar, temos três ferramentas, que estão descritas abaixo:

  • Service Configuration Editor: Este editor fornece uma interface gráfica para efetuarmos a configuração do serviço WCF, e que resultará na criação ou edição de um arquivo de configuração (App.config ou Web.config). Ele exigirá que você informe em qual assembly está o serviço, identificará o(s) contrato(s) que ele implementa, e depois disso, você configura os endpoints, bindings, behaviors, etc., tudo de acordo com a sua necessidade.
  • WCF Service Host: Este utilitário recebe como parâmetro o assembly que contém o serviço e seu contrato. Além disso, um segundo parâmetro (que também é obrigatório), é o arquivo com extensão *.config onde está contida todas as configurações daquele serviço.
  • WCF Test Client: Este utilitário tem a finalidade de simular um cliente, para que você consiga efetuar o consumo do serviço sem a necessidade de criar uma aplicação para isso. Ele recebe como parâmetro o endereço do documento WSDL, e com isso, irá extrair todas as informações e criará um formulário para que possamos informar os eventuais parâmetros para as operações, e depois exibirá o resultado.

Essas ferramentas já são conhecidas. Quando você cria um projeto utilizando a template WCF Service Library, ao rodá-la, verá que ambos utilitários serão inicializados para testar o serviço. Através das imagens abaixo, podemos visualizar o serviço sendo configurado pelo Service Configuration Editor. Em seguida, ele sendo hospedado no WCF Service Host e, finalmente, o WCF Test Client que irá consumi-lo.

O editor nós podemos acessá-lo diretamente através do Visual Studio, clicando em cima do arquivo de configuração correspondente, e em seguida, na opção Edit WCF Configuration. Se quiser acessar diretamente, você pode recorrer ao menu Tools e depois WCF Service Configuration Editor. Já os outros utilitários estão disponíveis através do prompt de comando do próprio Visual Studio. Abaixo temos a utilização de cada um deles:

C:>WcfSvcHost /service:C:TempMeuServico.dll /config:C:TempMeuServico.dll.config
C:>WcfTestClient http://localhost:8383/?wsdl

É importante dizer que esses utilitários não funcionam em alguns cenários, como por exemplo, quando utilizamos callbacks. Neste caso, a única saída será recorrer a criação de um projeto para o consumo deste serviço.