WCF Relay Bindings

Um dos pilares do .NET Services (Windows Azure) é o .NET Service Bus. Este “pedaço” fornece uma segura e consistente forma de comunicação entre aplicações e serviços, mesmo se eles estejam em diferentes organizações. Ele possibilita a centralização da comunicação entre essas partes, dando suporte aos mais variados protocolos existentes, utilizando os padrões mundialmente conhecidos como SOAP, WS-* e REST.

Aqueles que já estiverem familiarizados com o WCF, ao baixar o SDK do .NET Service poderão notar que existem alguns novos bindings que podem ser incorporados aos serviços WCF, permitindo que eles sejam expostos e invocados através do .NET Service Bus. Superficialmente falando, o .NET Service Bus funcionará como um relay entre o cliente e o serviço (lembre-se que eles estão, muitas vezes, em diferentes companhias) e, justamente por isso, que esses novos bindings possuem a palavra “Relay” em seu nome. Para a maioria dos bindings existentes no WCF, existe um correspondente em seu modo “relayed”:

Ainda há outros dois relay bindings que não possuem um correspondente na API do WCF: NetOnewayRelayBinding e o NetEventRelayBinding, quais são utilizados em casos específicos desta nova arquitetura.

Suporte ao protocolo UDP

Continuando minhas deambulações com o Visual Studio .NET 2010, notei que a Microsoft incorporou ao .NET Framework 4.0 novos tipos para suportar o protocolo UDP no WCF. Similiar ao TCP, pelo fato de enviar e receber pacotes na rede, possui algumas diferenças consideráveis, como o fato de não manter conexão ativa entre as duas extremidades, sessões e garantia de entrega (não existe o conceito de acks no UDP).

Justamente por não ter todo o handshake e o overhead que existe para suportar as funcionalidades que falamos acima, o protocolo UDP possui uma performance bem melhor em relação ao TCP. Na versão atual, não há um binding exclusivo para este protocolo e, para fazer o uso dele, é necessário recorrer a um binding customizado, configurando-o com a codificação binary e utilizando o meio de transporte UDP, sempre através de elementos de binding. Além disso, ainda há um novo schema: soap.udp. O código abaixo ilustra a sua utilização:

Uri address = new Uri(“soap.udp://localhost:8383”);
CustomBinding cb =
    new CustomBinding(
        new BindingElement[] { 
            new BinaryMessageEncodingBindingElement(), 
            new UdpTransportBindingElement()});

using (ServiceHost host = new ServiceHost(typeof(Servico), new Uri[] { address }))
{
    host.AddServiceEndpoint(typeof(IContrato), cb, “srv”);
    host.Open();

    ((UdpTransportBindingElement)cb.Elements[0]).ManualAddressing = false;

    using (ChannelFactory<IContrato> f = 
        new ChannelFactory<IContrato>(cb, new EndpointAddress(address)))
    {
        f.CreateChannel().Ping();
    }
}

WCF – Segurança – Autenticação e Autorização Customizadas

Como já foi detalhado neste artigo, o WCF fornece várias possibilidades de gerenciar a autenticação e autorização dentro dos serviços. Uma dessas possibilidades é customizar como o WCF deverá autenticar e autorizar o cliente, analisando as suas credenciais, verificando se essas são válidas em um determinado repositório, determinar quais são os direitos que o cliente tem no serviço e, finalmente, conceder ou negar o acesso a alguma operação baseando-se em seus privilégios. A finalidade deste artigo é analisar os passos necessários para essa customização.

Uma das grandes necessidades que se tem atualmente é permitir ao cliente fornecer um usuário e senha e, do lado do serviço, verificar se ele é válido ou não em algum repositório, como um banco de dados. O mais próximo disso que existe dentro do WCF é a integração com o MembershipProvider para autenticação e RoleProvider para autorização, fornecidos pelo ASP.NET 2.0. Podemos recorrer a estas APIs, que seguem o padrão Provider Model (System.Web), para a criação customizada de um provider que atenda a nossa necessidade e, depois disso, acoplá-las no WCF. Para obter um maior controle, estas APIs não serão utilizadas neste exemplo.

Como a idéia é mostrar como efetuar a autenticação e autorização de forma customizada, o foco do artigo será validar o usuário e recuperar seus respectivos papéis de arquivos XML. É importante dizer que isso apenas servirá como exemplo para o artigo e não deve ser utilizado em um ambiente real, devido aos problemas de performance e, principalmente, de segurança. Dois arquivos, padrão XML, serão utilizados como “base de dados”, sendo um para o armazenamento dos usuários e outro para os papéis destes usuários. Abaixo é exibida a estrutura destes dois arquivos:

<?xml version="1.0" encoding="utf-8" ?>
<users>
  <user name="IsraelAece" password="123" />
  <user name="JulianoAece" password="456" />
</users>
UsersRepository.xml  

 

<?xml version="1.0" encoding="utf-8" ?>
<rolesRepository>
  <user name="IsraelAece">
    <role name="Administrator" />
    <role name="IT" />
  </user>
  <user name="JulianoAece">
    <role name="IT" />
  </user>
</rolesRepository>
RolesRepository.xml  

Como podemos notar, o primeiro arquivo serve de repositório para todos os usuários cadastrados no sistema, armazenando o seu nome (que servirá como login) e a senha de acesso. Já o segundo arquivo, armazena os papéis que um determinado usuário tem no sistema, e a relação se dá pelo próprio nome do usuário, através do atributo name do elemento user.

Antes de falar sobre as peculiaridades do WCF, precisamos entender alguns conceitos de segurança que existem dentro da plataforma .NET desde a versão 1.0. Duas Interfaces são utilizadas como base para os mecanismos de autenticação e autorização: IIdentity e IPrincipal (namespace System.Security.Principal), respectivamente. A Interface IIdentity fornece três propriedades autoexplicativas: Name, AuthenticationType e IsAuthenticated. Já a segunda possui dois membros que merecem uma atenção especial. O primeiro deles é a propriedade Identity que retorna a instância de uma classe que implemente a Interface IIdentity, representando a identidade do usuário; já o segundo membro trata-se de um método chamado IsInRole que, dado uma papel, retorna um valor boleano indicando se o usuário corrente possui aquele papel. Como podemos notar, as classes de autenticação e autorização trabalham em conjunto.

Dentro do namespace System.Threading existe uma classe chamada Thread. Essa classe determina como controlar uma thread dentro da aplicação. Essa classe, entre vários membros, possui uma propriedade estática chamada CurrentPrincipal que recebe e retorna uma instância de um objeto que implementa a Interface IPrincipal. É através desta propriedade que devemos definir qual será a identity e principal que irá representar o contexto de segurança para a thread atual.

Há algumas implementações das Interfaces IIdentity e IPrincipal dentro do .NET Framework, como é o caso das classes GenericIdentity, WindowsIdentity, GenericPrincipal e WindowsPrincipal. Apesar das classes GenericIdentity e GenericPrincipal servirem para o exemplo, vamos criar a nossa própria implementação, que neste caso chamará: XmlIdentity e XmlPrincipal.

As propriedades expostas pela Interface IIdentity são de somente-leitura, o que nos obriga a passar as informações como o tipo de autenticação e o nome do usuário através de um construtor. Justamente por isso a classe XmlIdentity deve fornecer um construtor com, no mínimo, estes dois parâmetros, podendo inclusive criar diferentes versões dele para suportar as propriedades que você julgar necessário, já que desta forma temos controle total. O código abaixo mostra na íntegra a implementação desta classe que será utilizada por todo o exemplo:

using System;
using System.Security.Principal;

namespace Host.XmlSecurity
{
    internal class XmlIdentity : IIdentity
    {
        private string _authenticationType;
        private bool _isAuthenticated;
        private string _name;

        public XmlIdentity(string authenticationType, string name)
        {
            this._authenticationType = authenticationType;
            this._name = name;
            this._isAuthenticated = (name != string.Empty);
        }

        public string AuthenticationType
        {
            get
            {
                return this._authenticationType;
            }
        }

        public bool IsAuthenticated
        {
            get
            {
                return this._isAuthenticated;
            }
        }

        public string Name
        {
            get
            {
                return this._name;
            }
        }
    }
}

O próximo passo é criar a classe responsável por armazenar as informações necessárias para efetuar a autorização. Como falado anteriormente, esse tipo de classe deve implementar a Interface IPrincipal e, neste caso, chamaremos de XmlPrincipal. Essa classe também deve fornecer um construtor que permita informarmos a identidade (classe que implemente a Interface IIdentity) e os papéis que aquele usuário possuir, e ambas informações serão armazenadas em campos privados desta mesma classe. Uma propriedade chamada Roles foi criada apenas por conveniência, expondo os papéis daquele usuário.

Por fim, o método IsInRole tem papel extremamente importante. Ao utilizar o modo declarativo ou imperativo para verificar se o usuário possui um papel específico, indiretamente o WCF irá interrogar este método, que deverá retornar um valor boleano indicando se o usuário possui ou não aquele papel. Basicamente ele deverá percorrer o array de strings (que são os papéis) e verificar se o papel que é passado como parâmetro está contido neste array. Abaixo está a classe XmlPrincipal, e podemos notar que em seu construtor, além dos papéis, ela também recebe a identidade do usuário, que está tipificada como XmlIdentity.

using System;
using System.Linq;
using System.Security.Principal;

namespace Host.XmlSecurity
{
    internal class XmlPrincipal : IPrincipal
    {
        private string[] _roles;
        private XmlIdentity _identity;

        public XmlPrincipal(XmlIdentity identity, string[] roles)
        {
            this._identity = identity;
            this._roles = roles;
        }

        public IIdentity Identity
        {
            get
            {
                return this._identity;
            }
        }

        public string[] Roles
        {
            get
            {
                return this._roles;
            }
        }

        public bool IsInRole(string role)
        {
            return (from r in this.Roles where r == role select r).Count() > 0;
        }
    }
}

O que vimos nos códigos acima não é exclusividade do WCF. A partir de agora vamos começar a analisar as classes que podem ser utilizadas para a customização da autenticação e autorização dentro do WCF. O primeiro detalhe importante é que para utilizar alguns tipos, devemos referenciar dois assemblies na aplicação que corresponde ao serviço: System.IdentityModel.dll e System.IdentityModel.Selectors.dll. Esses assemblies possuem vários tipos utilizados para gerir os processos de autenticação, autorização, tokes, claims, a customização de tudo isso, entre várias outras utilidades.

Como a autenticação sempre ocorre antes da autorização, vamos iniciar por ela, analisando a classe que permitirá tal customização. Para customizar a validação do login e senha informados pelo usuário, temos uma classe abstrata chamada UserNamePasswordValidator (namespace System.IdentityModel.Selectors) que especifica como será efetuada essa validação, sobrescrevendo o método Validate que, por sua vez, recebe o login e senha como parâmetro e retorna uma valor boleano. Como o exemplo irá extrair essas informações de um arquivo XML (UsersRepository.xml), é dentro deste que devemos efetuar a busca. A implementação desta classe é exibida abaixo:

using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;

namespace Host.XmlSecurity
{
    internal class XmlAuthentication : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (!XmlSecurityHelper.ValidateUser(userName, password))
                throw new SecurityTokenValidationException("Usuario Invalido.");
        }
    }
}

Como podemos ver, o login e senha são encaminhados para o método estático ValidateUser da classe XmlSecurityHelper, que efetua a validação e retorna True caso um usuário com este login e senha seja encontrado dentro dele. É importante dizer que a classe XmlSecurityHelper não faz parte do .NET Framework. Ela foi customizada e utiliza o LINQ To XML para encontrar as informações dentro do arquivo. Para poupar espaço, ela não será exibida aqui mas, ao efetuar o download do exemplo, você poderá explorá-la. Caso você utilize algum outro repositório como o SQL Server, você pode efetuar neste mesmo local uma query para determinar a existência do usuário. Caso nenhum usuário seja encontrado, uma exceção do tipo SecurityTokenValidationException é disparada, evitando que o cliente acesse o serviço.

É importante dizer que o usuário somente estará autenticado depois do retorno deste método, pois o WCF fará as manipulações necessárias para que isso aconteça. Se analisar a propriedade estática CurrentPrincipal da classe Thread, verá que a identidade do usuário recém validado ainda não estará lá. Para evitar maiores problemas, não se deve confiar nesta propriedade antes deste método retornar. Tudo o que veremos a partir de agora somente estará acessível ao usuário caso ele tenha sido devidamente autenticado.

Depois da classe que valida a existência do usuário, chega o momento de customizar a autorização do mesmo. Essa customização consiste em dois passos: a criação de uma política de autorização e, opcionalmente, a criação de um gerenciador de autorização. O segundo passo somente se faz necessário quando desejamos centralizar a validação em um único lugar, evitando poluir a classe que representa o serviço com informações relacionadas a segurança. De qualquer forma, falaremos mais detalhadamente sobre este segundo passo mais adiante.

O primeiro passo é a construção de uma política de autorização de usuários. Para criar esta política é necessário implementar a Interface IAuthorizationPolicy (namespace System.IdentityModel.Policy). A classe que implementa esta Interface não tem a finalidade de autorizar o usuário, mas será responsável por criar a classe principal referente a ele, extrair os seus papéis e devolver a instância da classe principal para o WCF, que fará uso dela posteriormente para determinar se ele tem ou não acesso a um determinado recurso/operação.

Ao implementar a Interface IAuthorizationPolicy em uma classe, você será obrigado a customizar os três membros fornecidos por ela. O primeiro deles, a propriedade Id, retorna uma string que identifica o componente; já a propriedade Issuer retorna uma das opções fornecidas pelo enumerador ClaimSet, indicando quem é o emissor daquela política. Finalmente, o último e mais importante membro desta Interface, é o método Evaluate. Esse método será executado em todas as requisições, e a finalidade dele, é avaliar se o usuário se enquadra nos requerimentos desta política e, além disso, podemos utilizar este método para definir o contexto de segurança do usuário atual, através das classes identity e principal.

Como parâmetro, este método recebe uma instância da classe EvaluationContext que representa os resultados das políticas de autorização que foram avaliados. O motivo do método Evaluate retornar uma valor boleano é porque o WCF permite adicionar várias políticas de autorização, e o retorno deste método irá determinar se a política seguinte deverá ou não ser analisada. Este método também recebe como parâmetro um object, e se dentro deste método você mudar o valor dele (não nulo), esta informação será encaminhada para as políticas subsequentes. Abaixo é exibida parcialmente a classe que implementa a Interface IAuthorizationPolicy, focando apenas no método Evaluate:

using System;
using System.Collections.Generic;
using System.IdentityModel.Claims;
using System.IdentityModel.Policy;
using System.Security.Principal;

namespace Host.XmlSecurity
{
    internal class XmlAuthorizationPolicy : IAuthorizationPolicy
    {
        public bool Evaluate(EvaluationContext evaluationContext, ref object state)
        {
            IIdentity identity = GetIdentityFromClient(evaluationContext);
            XmlIdentity xmlIdentity = 
new XmlIdentity(identity.AuthenticationType, identity.Name); evaluationContext.Properties["Principal"] = new XmlPrincipal( xmlIdentity, XmlSecurityHelper.GetRolesByUserName(xmlIdentity.Name)); return true; } private static IIdentity GetIdentityFromClient(EvaluationContext evaluationContext) { IIdentity identity = null; object propertyIdentities = null; if (!evaluationContext.Properties.TryGetValue("Identities",
out propertyIdentities)) throw new Exception("Nenhuma identidade foi encontrada."); IList<IIdentity> identities = propertyIdentities as IList<IIdentity>; if (identities != null && identities.Count > 0) identity = identities[0]; return identity; } //Outros membros } }

A primeira tarefa a ser executada dentro do método Evaluate é extrair as credenciais do usuário, pois não há como saber os papéis sem antes encontrar quem é o usuário. Para mais legibilidade, um método privado e estático chamado GetIdentityFromClient foi criado para isso, retornando a identidade do usuário corrente. Este método recorre à propriedade Properties da classe EvaluationContext, que retorna um dicionário contendo a coleção de informações que não estão relacionadas aos claims e “Identities” é uma delas.

Neste cenário, este método sempre retornará uma instância da classe GenericIdentity mas, como criamos a nossa própria versão de identidade (XmlIdentity), devemos fazer uso dela neste momento, instanciando-a e passando para o seu construtor as informações que estão a identidade corrente do usuário. A propriedade AuthenticationType retornará uma string contendo o tipo que efetuou a validação do usuário, que no nosso caso foi o “XmlAuthentication”, enquanto a propriedade Name retorna o nome do usuário autenticado.

Depois da nova identidade criada devemos criar a principal, representada pelo tipo XmlPrincipal que vimos mais acima. O único construtor fornecido por esta classe possui dois parâmetros: a identidade (classe que implemente a Interface IIdentity) e uma string contendo os papéis do usuário. A identidade já foi criada e está armazenada na variável xmlIdentity e os papéis serão extraídos do arquivo XML (RolesRepository.xml), através do método estático GetRolesByUserName da classe XmlSecurityHelper. A instância da classe XmlPrincipal será acomodada no mesmo dicionário de onde extraímos a identidade do usuário, ou seja, na propriedade Properties da classe EvaluationContext, sob a chave “Principal”.

Como falado anteriormente, as classes que implementam a Interface IAuthorizationPolicy não efetua a autorização em si, que consiste em verificar se o usuário tem ou não permissão para acessar um determinado recurso ou operação. Para proteger um recurso ou uma operação como um todo, podemos recorrer a forma declarativa ou imperativa de efetuar a verificação. No modo declarativo, decoramos a operação do serviço com o atributo PrincipalPermissionAttribute (namespace System.Security.Permissions), que através da propriedade Role podemos informar o papel que o usuário deverá possuir para acessá-la. O exemplo de código abaixo ilustra esta técnica:

using System;
using System.Security.Permissions;

namespace Host
{
    public class Servico : IContrato
    {
        [PrincipalPermission(SecurityAction.Demand, Role = "Administrator")]
        public string RecuperarDados()
        {
            //Executará somente se o usuário possuir o papel "Administrator"

            return "Resultado";
        }
    }
}

Com o modelo acima, caso o usuário não possua o papel “Administrator”, uma exceção do tipo SecurityAccessDeniedException será disparada e o método não será executado. Já para ter um controle mais refinado sobre os papéis e direitos que o usuário terá dentro da operação, podemos recorrer ao modo imperativo e, através do método IsInRole da classe que representa a principal, verificamos se ele possui ou não um determinado papel. A classe XmlAuthorizationPolicy que vimos acima foi responsável por criar a XmlPrincipal e, quando foi devolvido para o WCF, ele se encarregou de armazená-la na propriedade estática CurrentPrincipal da classe Thread. Abaixo temos a mesma operação, só que agora com um maior controle, mas não deixando de se preocupar quando o usuário não possuir os papéis necessários para executar alguma tarefa.

using System;
using System.Threading;
using Host.XmlSecurity;

namespace Host
{
    public class Servico : IContrato
    {
        public string RecuperarDados()
        {
            XmlPrincipal xmlPrincipal = (XmlPrincipal)Thread.CurrentPrincipal;

            if (xmlPrincipal.IsInRole("Administrator"))
            {
                Console.WriteLine("Name: " + xmlPrincipal.Identity.Name);
                Console.WriteLine("Identity Type: " + 
xmlPrincipal.Identity.GetType().FullName); Console.WriteLine("Authentication Type: " + xmlPrincipal.Identity.AuthenticationType); Console.WriteLine("Is In IT Role? " + xmlPrincipal.IsInRole("IT")); } return "Resultado"; } } }

Com esta última técnica, temos um controle maior sobre como conceder ou negar acesso a um determinado recurso através dos papéis do usuário. Mas os grandes problemas que existem em ambas as técnicas é a “poluição” da classe que representa o serviço (regras de negócios) com códigos exclusivos de segurança e uma possível duplicação de código. Visando facilitar isso, o WCF disponibiliza uma classe chamada ServiceAuthorizationManager (namespace System.ServiceModel).

Essa classe fornece métodos para a verificação de autorização das operações do serviço, sendo invocados em todas as requisições realizadas. Além disso, ela é responsável por carregar todas as políticas de autorização existentes (classes que implementam a Interface IAuthorizationPolicy), invocando o método Evaluate de cada uma delas. Uma vez que todas as políticas de autorização forem avaliadas, a classe ServiceAuthorizationManager terá acesso ao conjunto final de papéis e, a partir daí, tomar decisões baseando-se neles.

Para customizar, podemos criar uma classe de gerenciamento de autorização, obviamente herdando da classe ServiceAuthorizationManager. Essa classe fornece dois métodos virtuais chamados CheckAccess e CheckAccessCore, que retornam um valor boleano indicando se o usuário corrente tem ou não permissão de acesso. Escolher entre um deles dependerá se você precisa ou não de dados que estão no corpo da mensagem para tomar a decisão de autorização de acesso. O método CheckAccess possui um overload que, além de fornecer o contexto da operação corrente, disponibiliza um segundo parâmetro do tipo Message, que representa a mensagem atual. Caso um desses métodos, quando sobrescrito, retornar False por algum motivo, uma exceção do tipo SecurityAccessDeniedException será disparada, informando o cliente que ele não possui direitos de acesso.

Como nosso exemplo não precisa analisar nenhum conteúdo da mensagem, então sobrescreveremos diretamente o método CheckAccessCore. Este método recebe como parâmetro o contexto atual, representado pela classe OperationContext e, é através dela que iremos extrair a instância da classe XmlPrincipal, criada por nossa política de autorização (XmlAuthorizationPolicy). Na sequência utilizaremos a coleção de headers para determinar qual operação está sendo invocada e, para isso, recorremos à propriedade Action. A nossa regra consistirá em verificar se a operação requerida é a RecuperarDados e, caso seja, somente se o usuário atual possui o papel Administrator poderá acessá-la, conforme é mostrado abaixo:

using System;
using System.Security.Principal;
using System.ServiceModel;

namespace Host.XmlSecurity
{
    internal class XmlAuthorizationManager : ServiceAuthorizationManager
    {
        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            base.CheckAccessCore(operationContext);
            XmlPrincipal xmlPrincipal = GetCurrentXmlPrincipal(operationContext);

            if (operationContext.IncomingMessageHeaders.Action == 
                "http://www.projetando.net/IContrato/RecuperarDados")
                if (!xmlPrincipal.IsInRole("Administrator"))
                    return false;

            return true;
        }

        //O método GetCurrentXmlPrincipal foi omitido
    }
}

Depois de todas essas implementações que foram feitas, as classes por si só não funcionam. Elas precisam ser acopladas na execução do serviço, mas precisamente no host (ServiceHost) que hospeda o serviço para que o host em conjunto com o runtime do WCF faça uso delas. Para efetuar a configuração delas, podemos optar pelo modelo declarativo ou imperativo mas, por questões de espaço, ela será realizada utilizando o modelo declarativo, ou seja, através do arquivo App.config ou Web.config.

Para utilizar a autenticação baseada em UserName/Password que o WCF fornece sob o protocolo HTTP, será necessário utilizar um certificado, pois toda a segurança será garantida pela mensagem (para mais detalhes sobre a segurança de serviços WCF, consulte este artigo). Sem a utilização deste, não seria possível garantir a integridade e confidencialidade da mensagem, comprometendo as informações e, principalmente, permitindo que alguém intercepte a mensagem e capture os dados sigilosos.

O código abaixo ilustra todas as configurações necessárias para fazer com que as classes que implementamos acima funcionem. Para explicar melhor, vamos dividir o arquivo em duas seções: uma para falar das configurações básicas do serviço (bindings, endpoints, etc.) e a segunda para detalhar a configuração da autenticação e autorização.

Como podemos notar, estamos definindo o serviço no arquivo de configuração contendo dois endpoints, sendo um para a publicação dos metadados (WSDL) e o outro para enviar requisições para o serviço. Tanto o serviço como os metadados estão acessíveis através do protocolo HTTP. O endpoint do serviço utiliza o binding wsHttpBinding e, para configurá-lo, define no atributo bindingConfiguration o valor srvBindingConfig que aponta para uma seção um pouco mais abaixo. Nesta seção definimos que o modo de segurança será baseado na mensagem e o tipo da credencial será UserName, obrigando o cliente a fornecer o login e senha antes de executar a operação. É importante notar que o serviço, através do atributo behaviorConfiguration, aponta para uma seção de behaviors chamada srvBehaviorConfig que falaremos a seguir.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Host.Servico" behaviorConfiguration="srvBehaviorConfig">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8778/"/>
          </baseAddresses>
        </host>
        <endpoint
          address="mex"
          binding="mexHttpBinding"
          contract="IMetadataExchange" />
        <endpoint 
          address="srv" 
          binding="wsHttpBinding" 
          contract="Host.IContrato" 
          bindingConfiguration="srvBindingConfig" />
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <binding name="srvBindingConfig">
          <security mode ="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="srvBehaviorConfig">
          <serviceCredentials>
            <serviceCertificate
              findValue="3c f2 d0 c0 ef 8e 0a 96 42 36 e6 54 5f 67 50 e0"
              storeLocation="LocalMachine"
              storeName="My"
              x509FindType="FindBySerialNumber" />
            <userNameAuthentication 
              userNamePasswordValidationMode="Custom" 
              customUserNamePasswordValidatorType="Host.XmlSecurity.XmlAuthentication, Host" />
          </serviceCredentials>
          <serviceAuthorization 
            principalPermissionMode="Custom"
            serviceAuthorizationManagerType="Host.XmlSecurity.XmlAuthorizationManager, Host">
            <authorizationPolicies>
              <add policyType="Host.XmlSecurity.XmlAuthorizationPolicy, Host" />
            </authorizationPolicies>
          </serviceAuthorization>
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>
*.config  

O behavior de serviço chamado srvBehaviorConfig é onde iremos definir toda a configuração de segurança do nosso serviço. No primeiro sub-elemento, chamado serviceCredentials, como o próprio nome diz, devemos configurar as credenciais que serão utilizadas pelo serviço e pelo cliente. Este elemento possui várias configurações para os mais diferentes meios de autenticação. Entre eles temos os elementos serviceCertificate e userNameAuthentication. O primeiro irá definir um certificado para ser utilizado para a proteção da mensagem, como já comentado anteriormente. O segundo elemento, userNameAuthentication fornece duas propriedades: userNamePasswordValidationMode e customUserNamePasswordValidatorType. Definindo a primeira delas como Custom diz ao WCF que vamos customizar a autenticação do usuário utilizando a classe que é definida no atributo customUserNamePasswordValidatorType e, de acordo com nosso exemplo, é responsabilidade da classe XmlAuthentication.

O segundo sub-elemento, serviceAuthorization, é responsável por configurar como será realizada a autorização do usuário. Entre os atributos fornecidos por esse elemento, temos: principalPermissionMode e serviceAuthorizationManagerType. Assim como na configuração anterior, o primeiro atributo determina que a configuração será customizada, enquanto a segunda especificará qual será a classe responsável por gerenciar a autorização (XmlAuthorizationManager). Ainda sobre este elemento, ele possui uma coleção chamada authorizationPolicies, onde podemos adicionar as classes que implementam a Interface IAuthorizationPolicy, já discutida acima.

Observação: Os tipos que são especificados no arquivo de configuração devem possuir o nome completo, incluindo possíveis namespaces e, obrigatoriamente, o nome do assembly onde ele reside.

Configuração do Cliente

Ao efetuar a referência do serviço no cliente, automaticamente algumas configurações já são definidas com os valores corretos, como por exemplo, a configuração do binding, o modo de segurança e a forma de fornecimento das credenciais para o serviço. Lembrando que o serviço expõe um certificado para proteger o envio e/ou recebimento das mensagens de forma segura e, ao efetuar a referência, a chave pública já é fornecida e devidamente configurada.

Depois destas considerações, a única diferença será o fornecimento explícito das credenciais do usuário antes de invocar a operação. Para fornecer as credenciais (login e senha), utilizamos a propriedade ClientCredentials fornecida pelo proxy (ClientBase<TChannel>). Essa propriedade retorna uma instância da classe ClientCredentials que, por sua vez, possui várias propriedades que permitem ao cliente configurar suas credenciais. Entre elas temos a propriedade UserName, do tipo UserNamePasswordClientCredential, que expõe as propriedades UserName e Password. O código abaixo ilustra como acessar essas propriedades, definir os valores e invocar a operação:

using System;
using Client.Servico;

using (ContratoClient proxy = new ContratoClient())
{
    proxy.ClientCredentials.UserName.UserName = "IsraelAece";
    proxy.ClientCredentials.UserName.Password = "123";

    Console.WriteLine(proxy.RecuperarDados());
}

Conclusão: O artigo demonstrou a customização da autenticação e autorização de um serviço WCF baseando-se no modelo de role-based security, mas pode-se adotar as mesmas estratégias para fazer com que o serviço utilize o modelo de identity-based security, apesar de que a Microsoft trabalha em cima de uma nova API para facilitar a construção deste modelo. A finalidade do artigo foi tentar exemplificar detalhadamente como efetuar tal customização que, em um ambiente real, deverá ter alguns cuidados extras que se deverá ter durante o seu desenvolvimento e que não foram abordados no artigo por estar fora do escopo do mesmo.

SegurancaCustomizada.zip (224.56 kb)

WCF – Introdução

A Microsoft disponibiliza várias tecnologias para o desenvolvimento de aplicações distribuídas. Cada uma delas é voltada para uma necessidade específica, e entre essas tecnologias temos: ASP.NET Web Services, WSE – Web Services Enhancements, .NET Remoting, COM+ – Enterprise Services e MSMQ – Message Queue. Cada uma delas possui sua própria API, com vários tipos que devem ser estudados para que possamos desenvolver uma aplicação que exponha ou consuma recursos destas tecnologias.

Quando iniciou a criação do .NET Framework 3.0, um entre quatro dos grandes pilares que havia dentro dele era o Indigo que mais tarde recebeu o nome de Windows Communication Foundation, ou simplesmente WCF. O WCF unificou as várias tecnologias de programação distribuídas na plataforma Microsoft em um único modelo, baseando-se na arquitetura orientada à serviços (SOA). Essa nova API facilita consideravelmente o aprendizado e desenvolvimento, já que o WCF está totalmente desacoplado das regras de negócios que serão expostas pelo serviço. A finalidade deste artigo é mostrar uma introdução ao WCF, construindo passo-à-passo um exemplo simples de como criar e consumir um serviço.

Para começar a fazer uso do WCF, tudo o que precisamos é referenciar em nossa aplicação o assembly System.ServiceModel.dll. Esse assembly possui a maioria dos tipos necessários para a construção de um serviço ou de um cliente. Ainda há outros assemblies que complementam o WCF, como é o caso do suporte à serviços baseados em REST, mas que serão abordados em artigos específicos.

Estrutura

A estrutura de um serviço WCF não é muito complexa, pois devemos utilizar conceitos puros de programação .NET para a criação do contrato e da classe que representará o serviço. Além disso, o WCF também suporta a utilização de tipos complexos, como classes que criamos para atender uma determinada necessidade.

O primeiro passo na criação do serviço é a definição do contrato. É o contrato que determinará quais operações estarão expostas, quais informações essas operações necessitam para ser executadas e também qual será o tipo de retorno. O contrato nada mais é que uma Interface que, por sua vez, deverá possuir os métodos (apenas a sua assinatura) que serão expostos. A Interface que servirá como contrato deverá ser obrigatoriamente decorada com o atributo ServiceContractAttribute pois, caso contrário, uma exceção do tipo InvalidOperationException será disparada antes da abertura do host.

Nem sempre todos os membros expostos pela Interface devem ser expostos para o serviço e, justamente por isso, todas as operações que serão disponibilizadas devem ser decoradas com o atributo OperationContractAttribute. Vale lembrar que o WCF obriga a termos no mínimo uma operação definida com este atributo, já que não faz sentido publicar um serviço que não tenha nenhuma operação a ser executada. Caso a Interface não possua nenhuma operação definida com este atributo, uma exceção do tipo InvalidOperationException também será disparada antes da abertura do host. O código abaixo exibe uma Interface simples, que servirá como exemplo para o artigo:

using System;
using System.ServiceModel;

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    Usuario RecuperarUsuario(string nome);
}

Para fins de exemplo, esta Interface apenas terá um único membro, mas ela poderá conter vários outros e, como já dito acima, você controla a visibilidade destes membros através do atributo OperationContractAttribute. Como podemos notar, o método RecuperarUsuario retorna uma instância da classe Usuario. Neste momento dois novos atributos entram em cena: DataContractAttribute e DataMemberAttribute, ambos contidos no namespace System.Runtime.Serialization, fornecido pelo assembly System.Runtime.Serialization.dll.

Os data contracts são uma forma que se tem de publicar possíveis estruturas de dados que podem ser trocadas durante o envio e recebimento de uma mensagem. A utilização do atributo DataContractAttribute determina que uma classe poderá ser exposta através de um serviço WCF, e deve ser aplicado a todas as classes que estão referenciadas, como parâmetro ou tipo de retorno, em um contrato (Interface). Já os tipos primitivos, como String, DateTime, Int32, não precisam disso, já que podem ser serializados diretamente.

Já o atributo DataMemberAttribute deve ser aplicado nos campos e propriedades que o tipo possui e que devem ser expostos através do serviço. Esse atributo irá controlar a visibilidade do campo ou da propriedade para os clientes que consomem o serviço, não importando o modificador de acesso (public, private, etc.) que possui. O código abaixo define a classe Usuario:

using System;
using System.Runtime.Serialization;

[DataContract] //Opcional com .NET 3.5 + SP1
public class Usuario
{
    [DataMember] //Opcional com .NET 3.5 + SP1
    public string Nome { get; set; }
}

Observação: A partir do Service Pack 1 do .NET Framework 3.5 esse comportamento foi mudado. Visando o suporte ao POCO (Plain Old C# Objects), a Microsoft tornou mais flexível a utilização de data contracts em serviços WCF, não obrigando às classes, propriedades e campos serem decorados com os atributos acima citados. Com isso, apenas propriedades do tipo escrita/leitura serão serializadas. A partir do momento que você decora a classe com o atributo DataContractAttribute, você também deverá especificar, via DataMemberAttribute, quais campos deverão ser serializados.

Vale lembrar também que o atributo XmlSerializableAttribute (namespace System.Xml.Serialization) e as Interfaces IXmlSerializable (namespace System.Xml.Serialization) e ISerializable (namespace System.Runtime.Serialization) também continuam sendo suportadas, permitindo que você customize como o objeto será serializados/deserializados pelo WCF ou qualquer outro recurso fornecido .NET Framework.

Uma vez que o contrato do serviço esteja definido e os possíveis tipos que ele expõe também estejam devidamente configurados, o próximo passo é a criação da classe que representa o serviço. Esta classe deverá implementar todos os membros expostos pela Interface que define o contrato do serviço, inclusive aqueles que não estão marcados com o atributo OperationContractAttribute, lembrando que a implementação de uma Interface em uma classe é uma imposição da linguagem, e não do WCF.

A implementação dos métodos poderá conter a própria regra de negócio, bem como pode servir de wrapper para algum outro componente ou serviço. Além disso, as classes que representam o serviço também podem configurar alguns outros recursos fornecidos pelo WCF e que estão acessíveis através de behaviors, como por exemplo: transações, sessões, segurança, etc., mas veremos isso mais detalhadamente abaixo. O WCF desacopla totalmente a regra do negócio de sua API e, justamente por isso, que é possível notar no código abaixo que a classe que representa o serviço não possui nenhuma configuração do WCF:

using System;

public class Servico : IContrato
{
    public Usuario RecuperarUsuario(string nome)
    {
        return new Usuario() { Nome = nome };
    }
}

Por si só esta classe não trabalha, pois deverá ser consumida pelo WCF. Mas afinal, como se determina que é esta classe responsável por atender as requisições? Isso é realizado através do host, ou melhor, da classe ServiceHost. Logo no construtor desta classe, você deverá passar uma instância da classe Type, apontando para a classe que representa o serviço e, obrigatoriamente, deverá implementar todos os possíveis contratos que são expostos através dos endpoints. A configuração do host para este exemplo será abordada na seção seguinte.

Grande parte dos atributos que vimos nesta seção disponibilizam várias propriedades que nos permitem interagir com o serializador/deserializador da mensagem e, além disso, permitem especificarmos algumas regras que serão validadas antes da abertura do host e, caso não sejam atendidas, uma exceção será disparada. Como essas propriedades influenciam nas mais variadas funcionalidades expostas pelo WCF, elas serão detalhadamente abordadas nos artigos que correspondem à sua utilização. Para conhecer os artigos disponíveis, consulte a listagem na seção Explorando outras Funcionalidades.

Hosting

Uma das grandiosidades do WCF é a possibilidade de utilizar qualquer tipo de aplicação como host, ou seja, ele não tem uma dependência de algum software, como o IIS (Internet Information Services), como acontecia com os ASP.NET Web Services. O WCF pode expor serviços para serem acessados através dos mais diversos tipos de protocolos, como por exemplo: HTTP, TCP, IPC e MSMQ.

Atualmente temos três alternativas de hosting: self-hosting, IIS e o WPAS. Como há vários detalhes na criação e gerenciamento do hosting, ficaria muito extenso publicar cada detalhe, vantagens e desvantagens que cada uma das técnicas possui. Para maiores detalhes, consulte este artigo que explora cada uma das funcionalidades expostas pelo WCF para a interação com o hosting.

O host é representado dentro do WCF pela classe ServiceHost ou uma de suas variações e, é através dela que efetuamos várias configurações, como endpoints, segurança, etc. Em seu construtor, ela espera a classe que representa o serviço, podendo ser definida através de seu tipo (classe Type) ou através de uma instância desta mesma classe anteriormente criada (Singleton). Para o exemplo utilizado neste artigo, a configuração parcial do host fica da seguinte forma:

using System;
using System.ServiceModel;

using (ServiceHost host = new ServiceHost(typeof(Servico),
    new Uri[] { new Uri("net.tcp://localhost:9393") }))
{
    //endpoints

    host.Open();
    Console.ReadLine();
}

Endpoints

Os endpoints são uma das características mais importantes de um serviço, pois é por onde toda a comunicação é realizada, pois fornece o acesso aos clientes do serviço WCF que está sendo disponibilizado. Para compor um endpoint, basicamente precisamos definir três propriedades que obrigatoriamente precisamos para poder trabalhar: address (A), binding (B) e contract (C) e, opcionalmente, definir alguns behaviors, que falaremos na sequência. A figura abaixo ilustra a estrutura dos endpoints e onde eles estão situados:

Figura 1 – Estrutura de um endpoint.

O address consiste em definir um endereço único que permitirá aos clientes saber onde o serviço está publicado. O endereço geralmente é definido através de uma instância da classe Uri. Essa classe fornece um construtor que recebe uma string, contendo o protocolo, o servidor, a porta e o endereço do serviço (usado para diferenciar entre muitos serviços no mesmo local), tendo a seguinte forma: scheme://host[:port]/[path]. Cada uma dessas configurações são representadas respectivamente pelas seguintes propriedades da classe Uri: Scheme, Host, Port e AbsolutePath.

O protocolo indica sob qual dos protocolos suportados pelo WCF o serviço será exposto. Atualmente temos os seguintes protocolos: HTTP (http://), TCP (net.tcp://), IPC (net.pipe://) e MSMQ (net.msmq://). O host refere-se à máquina onde o serviço irá ser executado, podendo inclusive referenciar o localhost. A porta permite especificarmos uma porta diferente do valor padrão e, quando omitida, ela sempre assumirá a porta padrão especificada pelo protocolo. E, finalmente, temos o path, que é utilizado quando desejamos diferenciar entre vários serviços expostos sob um mesmo protocolo, host e porta.

O binding indica como a comunicação será realizada com aquele endpoint, como por exemplo, qual transporte será utilizado (HTTP, TCP, etc), qual a codificação utilizada (Binary ou Text) para serializar a mensagem, segurança, suporte à transações, etc. O WCF disponibiliza vários bindings, e através da tabela abaixo podemos analisar as características de cada um deles (sendo as opções em negrito a configuração padrão):

Binding Características
BasicHttpBinding
  • Transporte: HTTP.

  • Segurança: None, Transport, Message e Mixed.

  • Suporte à Transações: Não.

  • Duplex: Não.

  • Sessions: Não.

  • Encoding: Text.

  • Modos de Transferência: Streaming e Buffered.

WebHttpBinding
  • Transporte: HTTP.

  • Segurança: None e Transport.

  • Suporte à Transações: Não.

  • Duplex: Não.

  • Sessions: Não.

  • Encoding: Text e MTOM.

  • Modos de Transferência: Streaming e Buffered.

WSHttpBinding
  • Transporte: HTTP.

  • Segurança: None, Transport, Message e Mixed.

  • Suporte à Transações: Sim.

  • Duplex: Sim.

  • Sessions: Sim.

  • Encoding: Text e MTOM.

  • Modos de Transferência: Buffered.

WSDualHttpBinding
  • Transporte: HTTP.

  • Segurança: None, Message e Mixed.

  • Suporte à Transações: Sim.

  • Duplex: Sim.

  • Sessions: Sim.

  • Encoding: Text e MTOM.

  • Modos de Transferência: Buffered.

WSFederationHttpBinding
  • Transporte: HTTP.

  • Segurança: None e Message.

  • Suporte à Transações: Sim.

  • Duplex: Não.

  • Sessions: Sim.

  • Encoding: Text e MTOM.

  • Modos de Transferência: Buffered.

NetTcpBinding
  • Transporte: TCP.

  • Segurança: None, Transport, Message e Mixed.

  • Suporte à Transações: Sim.

  • Duplex: Sim.

  • Sessions: Sim.

  • Encoding: Binary.

  • Modos de Transferência: Streaming e Buffered.

NetPeerTcpBinding
  • Transporte: Peer.

  • Segurança: Transport.

  • Suporte à Transações: Não.

  • Duplex: Sim.

  • Sessions: Não.

  • Encoding: –.

  • Modos de Transferência: Buffered.

NetNamedPipeBinding
  • Transporte: IPC.

  • Segurança: None e Transport.

  • Suporte à Transações: Não.

  • Duplex: Sim.

  • Sessions: Sim.

  • Encoding: Binary.

  • Modos de Transferência: Streaming e Buffered.

NetMsmqBinding
  • Transporte: MSMQ.

  • Segurança: None, Transport e Message.

  • Suporte à Transações: Sim.

  • Duplex: Não.

  • Sessions: Sim.

  • Encoding: Binary.

  • Modos de Transferência: Buffered.

MsmqIntegrationBinding
  • Transporte: MSMQ.

  • Segurança: Transport.

  • Suporte à Transações: Sim.

  • Duplex: Não.

  • Sessions: Não.

  • Encoding: –.

  • Modos de Transferência: Buffered.

Observação: Além da lista de bindings que vimos acima, temos ainda a classe CustomBinding que, como o próprio nome indica, possibilita a criação de um binding customizado, definindo qual o meio de transporte, codificação, suporte ou não à transações, etc.

Finalmente, a última característica de um endpoint é o contrato. Como já vimos acima, o contrato é representado por uma Interface e, uma vez que ele é definido como um contrato de serviço, são esses membros que serão disponibilizados aos clientes em forma de operações, definindo os parâmetros de entrada e o tipo de retorno e o formato da mensagem (request-reply, one-way ou duplex).

O que diferencia uma Interface normal de uma Interface que será utilizada como contrato de serviço. Os atributos que devem ser decorados na mesma (ServiceContractAttribute) e também naqueles membros que farão parte do serviço (OperationContractAttribute), devendo também se atentar aos tipos que são expostos, pois tipos complexos também devem ser marcados com um atributo especial (DataContractAttribute), e as propriedades que ele irá expor devem ser decoradas com o atributo DataMemberAttribute, tudo como já foi explicado acima.

Depois de conhecer cada uma das características de um endpoint, é necessário entender como criar e configurar um endpoint. Cada endpoint é representado por uma classe chamada ServiceEndpoint (namespace System.ServiceModel.Description) e possui várias propriedades que expõem exatamente as configurações de um endpoint como Address, Binding e Contract. A criação pode ser realizada de duas formas: uma delas criando e configurando a instância da classe ServiceEndpoint e adicionando-a à coleção de Endpoints do host, e a segunda e mais convencional forma é através do método AddServiceEndpoint da classe ServiceHost. O código abaixo ilustra a configuração de um endpoint:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using (ServiceHost host = new ServiceHost(typeof(Servico),
    new Uri[] { new Uri("net.tcp://localhost:3933") }))
{
    host.AddServiceEndpoint(typeof(IContrato), new NetTcpBinding(), "srv");

    host.Open();
    Console.ReadLine();
}

Entre os vários overloads que existem do método AddServiceEndpoint, um deles aceita uma instância da classe Type representando o contrato, a instância de um dos bindings disponíveis e, finalmente, o endereço que, por sua vez, poderá ser absoluto ou relativo. Outro detalhe importante é que precisamos ter um endereço (base address) definido no construtor da classe ServiceHost com o mesmo protocolo utilizado pelo binding que, no caso acima, deve ser net.tcp. E, para finalizar, você pode criar quantos endpoints desejar, inclusive com protocolos diferentes.

Behaviors

Até o momento criamos o contrato, os tipos que fazem parte dele e também a classe que representa o serviço. Com exceção da configuração host e dos atributos, nenhuma outra funcionalidade do WCF foi adicionada ao serviço. Como o exemplo é muito simples, nenhuma configuração adicional foi necessária, mas e quando os serviços necessitam o suporte de sessões, transações, funcionalidades de segurança, etc.?

Através de behaviors podemos modificar como a mensagem será processada pelo WCF, e boa parte das funcionalidades do WCF são expostas por intermédio de behaviors, como segurança, transações, throttle, metadados, etc. Como os behaviors são aspectos de execução do WCF, a inserção ou remoção deles não afeta a comunicação entre as partes.

Atualmente temos três escopos diferentes de behaviors: operation behavior, endpoint behavior e service behavior. O primeiro deles afetará apenas a execução de uma operação específica. Os endpoint behaviors são usados exclusivamente para um endpoint. Finalmente, os service behaviors possibilita a execução de algo válido para o serviço como um todo, independentemente de quantos endpoints houverem. O WCF já traz vários behaviors criados prontos para serem utilizados e, possibilita também criarmos nossos próprios behaviors, apenas implementando as Interfaces IOperationBehavior, IEndpointBehavior ou IServiceBehavior. Os behaviors customizados serão abordados em um futuro artigo.

Alguns behaviors são implementados em forma de atributo, podendo ser aplicado diretamente no tipo ou membro onde ele deverá influenciar. Um exemplo disso são os atributos OperationBehaviorAttribute e ServiceBehaviorAttribute, onde o primeiro deles pode ser aplicado em uma operação e customiza informações relacionadas à segurança e transações. Já o atributo ServiceBehaviorAttribute fornece uma infinidade de detalhes relacionados ao serviço, tais como: modo de gerenciamento de instância, sincronização, transações, etc. Essas configurações não estão relacionadas ao contrato, mas sim à classe que representa o serviço, e justamente por isso que os behaviors devem ser aplicados nela, conforme é exibido abaixo:

using System;
using System.ServiceModel;

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Servico : IContrato
{
    [OperationBehavior(TransactionScopeRequired = true)]
    public Usuario RecuperarUsuario(string nome)
    {
        return new Usuario() { Nome = nome };
    }
}

Há ainda outros behaviors que não são expostos como atributos, como é o caso do ServiceMetadataBehavior que é um behavior que é aplicado no serviço como um todo, gerenciando como expor os metadados (mais detalhes sobre ele abaixo). Para criar um behaviors de serviço, você deverá instanciá-lo e adicionar na coleção de Behaviors da classe ServiceHost, coleção que é exposta através da propriedade Description.

A forma declarativa (via atributos) de adicionarmos os behaviors não se resume apenas a isso. O WCF permite que behaviors sejam adicionados a partir de arquivos de configuração, o que torna esse processo muito mais flexível. Veremos mais detalhes sobre esta técnica mais abaixo.

Metadados – WSDL

Os metadados têm um papel importante dentro do WCF e de qualquer web service. Regidos por padrões mundialmente conhecidos, como WSDL (Web Services Description Language) ou o WS-Policy/WS-MetadataExchange, utilizam a linguagem XML/XSD para descrever um serviço, especificando suas operações e tipos de dados que ele suporta.

Por padrão, o WCF não publica os metadados, tendo que fazer isso explicitamente justamente para diminuir a superfície de ataque. Para efetuar essa publicação, basicamente adicionamos um behavior chamado ServiceMetadataBehavior que está contido no namespace System.ServiceModel.Description na coleção de behaviors do serviço. Esse behavior possui uma propriedade boleana chamada HttpGetEnabled que, quando definida como True, permite acessar o WSDL a partir de um navegador, ou melhor, de um endereço HTTP. Essa técnica é bastante utilizada para publicação deste documento, já que muitas vezes o serviço é exposto a partir de um protocolo que está inacessível devido às políticas de firewall.

Quando definimos a propriedade HttpGetEnabled como True, obrigatoriamente devemos ter um base address com um endereço HTTP especificado, caso contrário, uma exceção do tipo InvalidOperationException será disparada antes da abertura do host, informando a ausência de um endereço HTTP. Uma alternativa para isso é utilizar a propriedade HttpGetUrl da classe ServiceMetadataBehavior, especificando um endereço absoluto para o documento WSDL. O código a seguir exibe como efetuar essa configuração:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using (ServiceHost host = new ServiceHost(typeof(Servico),
    new Uri[] { new Uri("net.tcp://localhost:3933") }))
{
    host.Description.Behaviors.Add(
        new ServiceMetadataBehavior()
        {
            HttpGetEnabled = true,
            HttpGetUrl = new Uri("http://localhost:9393/")
        });

    host.AddServiceEndpoint(typeof(IContrato), new NetTcpBinding(), "srv");

    host.Open();
    Console.ReadLine();
}

No exemplo acima, o WSDL estará disponível a partir do endereço http://localhost:9393/, ou seja, estamos publicando o WSDL em um protocolo diferente do qual o serviço será disponibilizado. É importante dizer que os metadados não necessariamente precisam ser expostos via HTTP. Serviços podem disponibilizar a sua descrição a partir de TCP ou IPC. A única exceção é quando utilizamos o Message Queue que, por sua vez, não permite a publicação de metadados, obrigando-nos a escolher um protocolo diferente, como o HTTP.

Há ainda uma outra forma de publicar o documento WSDL, que é criando um endpoint específico para isso, conhecido como Metadata Exchange Endpoint ou simplesmente MEX Endpoint. Como qualquer outro endpoint, este tipo especial exige também um endereço, um contrato e um binding e, com exceção do endereço, as duas outras características possuem algumas considerações.

A primeira delas, o contrato, deverá ser sempre a Interface IMetadataExchange que, por sua vez, também está contido no namespace System.ServiceModel.Description. Os membros desta Interface são irrelevantes para o desenvolvedor do serviço, já que ela será usada exclusivamente pelo runtime do WCF. É importante notar que, mesmo que ela faça parte do serviço, ela não deve ser implementada na classe que o representa mas, mesmo utilizando esta técnica, é necessário que o behavior ServiceMetadataBehavior também esteja adicionado.

Para expor os metadados também é necessário efetuar algumas mudanças no binding. Para evitar a configuração do binding a cada endpoint de metadados que criamos, a Microsoft disponibilizou uma classe estática chamada MetadataExchangeBindings, que possui vários métodos que retornam bindings devidamente configurados para suportar a publicação dos metadados. Entre estes métodos temos: CreateMexHttpBinding, CreateMexHttpsBinding, CreateMexNamedPipeBinding e CreateMexTcpBinding, e escolher um deles dependerá do protocolo onde se deseja publicar o documento. O código abaixo ilustra como efetuar a configuração de um MEX Endpoint:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using (ServiceHost host = new ServiceHost(typeof(Servico),
    new Uri[] { new Uri("net.tcp://localhost:3933") }))
{
    host.Description.Behaviors.Add(new ServiceMetadataBehavior());

    host.AddServiceEndpoint(typeof(IContrato), new NetTcpBinding(), "srv");
    host.AddServiceEndpoint(
        typeof(IMetadataExchange), 
        MetadataExchangeBindings.CreateMexTcpBinding(), 
        "mex");

    host.Open();
    Console.ReadLine();
}

Observação: Ambas as formas de publicar os metadados também podem ser configuradas de forma declarativa, ou seja, através do arquivo de configuração. Veremos mais detalhes sobre isso na próxima seção deste artigo.

Configuração Declarativa vs. Imperativa

Grande parte das configurações que vimos no decorrer deste artigo pode ser feita de duas formas: declarativa ou imperativa, onde cada uma das duas tem suas vantagens e desvantagens. Com a forma declarativa temos uma maior flexibilidade, já que qualquer alteração a nível de configuração do serviço pode ser realizada sem a necessidade de recompilar a aplicação. Podemos abrir e editar o arquivo de configuração através de um editor de texto, mas isso está propício à erros, já que não é fortemente tipado. Para contornar esse problema, podemos utilizar um software fornecido pela própria Microsoft, chamado Microsoft Service Configuration Editor, que fornece uma interface gráfica para isso.

Já no modo imperativo, toda criação e manipulação de qualquer configuração, seja do binding, endpoints ou behaviors, será realizada diretamente via código (C# ou VB.NET). Entre as vantagens que temos com este modo é a facilidade para montar as configurações de forma dinâmica, podendo essas configurações virem de algum repositório, efetuar condicionais, etc., situações que o modo declarativo não suporta. Como essa técnica não coloca nenhuma informação no arquivo de configuração, configurações acidentais não danificam o sistema.

Como todo o exemplo foi baseado na configuração de forma imperativa, esta seção mostrará como efetuar a configuração do binding, behaviors e endpoints a partir do arquivo de configuração (App.config ou Web.config). O fato de utilizá-lo não elimina a necessidade de criar e abrir um host (ServiceHost); ao detectar que existe um arquivo contendo as configurações do serviço, elas serão carregadas a partir dele. Essa “amarração” é efetuada a partir do atributo name do elemento service, como vemos abaixo:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Host.Servico" behaviorConfiguration="srvBehaviorConfig">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:9393/"/>
          </baseAddresses>
        </host>
        <endpoint 
          name="Srv" 
          address="srv" 
          contract="Host.IContrato" 
          binding="netTcpBinding"
          bindingConfiguration="bindingConfig" />
        <endpoint 
          name="Mex" 
          address="mex" 
          contract="IMetadataExchange" 
          binding="mexTcpBinding" />
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name="bindingConfig">
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="srvBehaviorConfig">
          <serviceMetadata 
            httpGetEnabled="true"
            httpGetUrl="http://localhost:2322/"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>
*.Config  

No código acima, devemos notar que logo após o elemento system.serviceModel há um sub-elemento chamado services que é uma coleção de serviços, e cada serviço é representado pelo elemento service. Esse elemento possui um atributo chamado name que deve refletir o full name da classe que representa o serviço, incluindo o namespace. Na sequência definimos os possíveis base address do serviço e, como essa é uma configuração relacionada à um serviço específico, ela deverá estar dentro do elemento service.

Ainda dentro do elemento service há um sub-elemento chamado endpoint, onde podemos definir cada endpoint (address, binding e contract), podendo adicionar quantos forem necessários. Além da configuração do ABC, o elemento endpoint possui um atributo chamado bindingConfiguration que podemos apontar para uma outra seção dentro do mesmo arquivo de configuração, contendo a configuração do binding, como segurança, modos de transferência, etc. No exemplo acima, o endpoint “srv” define o atributo bindingConfiguration como bindingConfig.

O elemento service disponibiliza um atributo chamado behaviorConfiguration e que permite apontar para uma seção dentro do mesmo arquivo de configuração, contendo os possíveis behaviors de serviço e endpoint que, no exemplo acima, habilitamos a publicação dos metadados. A finalidade da configuração dos bindings e dos behaviors estar fora do elemento service é justamente por questões de reutilização, já que podemos ter a mesma configuração para vários serviços que são executados nesta mesma aplicação.

Como dito anteriormente, ainda é necessária a criação da classe ServiceHost, mas com uma pequena mudança. A classe que representa o serviço deve continuar sendo informada, enquanto o segundo parâmetro, um array de objetos do tipo Uri, deverá ser informado como um array vazio, caso contrário, uma exceção do tipo ArgumentNullException será disparada. Podemos notar através do código abaixo que não há mais a necessidade de configurar os endpoints ou os behaviors, pois eles serão carregados do arquivo de configuração.

using System;
using System.ServiceModel;

using (ServiceHost host = new ServiceHost(typeof(Servico), new Uri[] {  }))
{
    host.Open();
    Console.ReadLine();
}

Configuração do Cliente

Para que possamos fazer o uso do serviço em aplicações cliente, é necessário efetuar a referência deste serviço. A referência consiste em criar uma classe que encapsulará todo o acesso para o serviço, que também é conhecida como proxy. Este processo irá ler os metadados do serviço, criando o proxy com os mesmos membros expostos, dando a impressão ao consumidor que está chamando uma classe local mas, em tempo de execução, a requisição será encaminhada para o serviço remoto.

Podemos efetuar a criação do proxy de três formas diferentes. A primeira delas é através da referência do serviço por meio da IDE do Visual Studio .NET que, por sua vez, fornece uma opção chamada Add Service Reference que exige a referência para o serviço. A segunda opção é fazer uso do utilitário svcutil.exe que, dada uma URL até o serviço, ele também é capaz de gerar a classe que representará o proxy. Ambas as opções também automatizam a criação do arquivo de configuração, que fornece de forma declarativa todas as configurações do serviço.

Ao efetuar a referência, será criada uma representação local do contrato (Interface), bem como os tipos complexos que fazem parte do serviço. Além disso, a classe que representa o proxy herda diretamente da classe abstrata e genérica ClientBase<TChannel> que fornece toda a implementação necessária para permitir aos clientes se comunicarem com o respectivo serviço. Essa classe irá configurar em tempo de execução as propriedades necessárias para efetuar a requisição, extraindo tais informações do arquivo de configuração. É a partir desta classe que começamos a criar o código cliente para efetuar a requisição:

using System;
using Client.Servico;

using (ContratoClient proxy = new ContratoClient())
{
    Usuario usuario = proxy.RecuperarUsuario("Israel Aece");
    Console.WriteLine(usuario.Nome);
}

Observação: Se envolvermos o proxy em um bloco using (lembre-se de que ele será transformado em try/finally), é necessário tomarmos um certo cuidado. Não porque o método Dispose não será chamado, mas sim onde a execução deste método afetará a aplicação cliente. Vamos supor que algum erro ocorra durante a execução da operação e, como já era de se esperar, o bloco finally será disparado, chamando o método Dispose do proxy. Neste caso, o método Dispose não faz mais nada a não ser invocar o método Close. O problema aqui é que o método Close poderá exigir algumas atividades extras, necessitando fazer alguma outra comunicação com o serviço e, se neste momento algum erro ocorrer, uma exceção do tipo CommunicationObjectFaultedException será disparada, mascarando o real problema. Finalmente, a opção para contornar esse possível problema é a chamada do método Abort, que encerra imediatamente a comunicação entre o cliente e o serviço, assim como é demonstrado neste endereço.

A terceira e última forma que existe para efetuar a comunicação entre o cliente e o serviço é realizar toda a configuração de forma manual. Isso obriga a conhecer exatamente o binding e suas respectivas configurações, qual o endereço onde o serviço está publicado e, principalmente, a Interface do contrato e os tipos que ela expõe devem estar compartilhados entre o cliente e o serviço. Apesar deste compartilhamento ser simples de realizar, pois basta isolar os tipos em um assembly (DLL), o problema é quando o número de clientes aumenta consideravelmente, dificultando a distrubuição. De qualquer forma, abaixo consta um exemplo de como efetuar essa configuração:

using System;
using System.ServiceModel;

using (ChannelFactory<IContrato> srv = new ChannelFactory<IContrato>(new NetTcpBinding(), 
    new EndpointAddress("net.tcp://localhost:9393/")))
{
    Usuario u = srv.CreateChannel().RecuperarUsuario("Israel Aece");
    Console.WriteLine(u.Nome);
}

Explorando outras Funcionalidades

O WCF fornece diversas funcionalidades que visam performance, segurança, disponibilidade, entre outros que podemos incorporar em nossos serviços para torná-los muito mais ricos, flexíveis e de fácil manipulação. Cada uma das principais funcionalidades fornecidas por ele possui um artigo exclusivo que abordará na íntegra cada uma delas. Para acessar esses artigos, consulte a listagem abaixo:

  • Tipos de Mensagens: Tradicionalmente, em qualquer tipo de aplicação, podemos criar um método que faz alguma tarefa. Ao criá-lo, podemos consumí-lo na mesma aplicação ao até mesmo referenciar a classe em que ele está contido e também consumí-los nos mais variados projetos. Ao realizar a chamada para este método, devemos esperar a sua execução e, quando finalizada, damos continuidade na execução do programa. Ao criar uma operação em um serviço WCF, ela também se comportará da mesma forma. Mas essa não é a única alternativa fornecida pelo WCF. O foco deste artigo é explorar tais alternativas e como elas influenciam na configuração e implementação e execução do serviço.

  • Hosting: Como já sabemos, WCF – Windows Communication Foundation – é parte integrante do .NET Framework 3.0. Ele fornece um conjunto de classes para a construção e hospedagem de serviços baseando-se na arquitetura SOA (Service Oriented Architecture), podendo expor tais serviços para serem acessados através dos mais diversos tipos de protocolos, como por exemplo: HTTP, TCP, IPC e MSMQ. Atualmente existem três tipos de hosts para serviços construídos em WCF: self-hosting (através da classe ServiceHost), IIS (Internet Information Services) e WAS (Windows Activation Services – Windows Vista) e, é exatamente isso que este artigo abordará, ou seja, como configurar cada um desses hosts para expor os serviços construídos em WCF.

  • Error Handling: Independentemente de que tipo de aplicação estamos criando, erros sempre podem acontecer. O mesmo serve para serviços, não estando isentos disso. Em se tratando de serviços, os erros podem ser os mais variados possíveis, havendo problemas a nível de transporte (protocolo), na entrega/recebimento da mensagem, no runtime ou até mesmo (e é o mais comum) na execução da operação (método). O WCF fornece várias técnicas para analisar e tratar os possíveis erros que ocorrem durante a execução do serviço. O grande desafio aqui é como fazer com que este problema (erro) seja passado para o cliente que o consome independentemente da plataforma, dando à ele a capacidade de saber o que ocorreu e como contorná-lo, mantendo a aplicação cliente e proxy estáveis. Esse artigo abordará como devemos proceder para disparar erros, notificar o cliente e, como ele pode fazer para tratar os erros que ocorrem.

  • Gerenciamento de Instâncias: O gerenciamento de instância é uma técnica que é utilizada pelo WCF ou qualquer outra tecnologia de computação distribuída que determina como e por quem as requisições dos clientes serão atendidas. A escolha do modo de gerenciamento de instâncias interfere diretamente na escalabilidade, performance e transações de um serviço/componente, além de termos algumas mudanças à nível de implementação de contrato, que precisamos nos atentar para garantir que o mesmo funcione sob o modelo de gerenciamento escolhido. A finalidade do artigo consiste, basicamente, em mostrar cada uma das três técnicas disponíveis pelo WCF mas, também, abordando os seus respectivos benefícios e algumas técnicas que circundam esse processo e que, de alguma forma, estão ligadas e influenciam na escolha e/ou implementação. O artigo também abordará os cuidados que devemos ter na escolha e implementação de cada uma das técnicas fornecidas.

  • Sincronização: Ao expor um serviço para que ele seja consumido, devemos nos atentar à possibilidade deste serviço ser acessado simultaneamente. Isso ocorre quando múltiplas requisições (threads) tentam acessar o mesmo recurso ao mesmo tempo. A possibilidade de acessos simultâneos poderá acontecer dependendo do tipo de gerenciamento de instância escolhido para o serviço. A finalidade deste artigo é mostrar as três opções fornecidas pelo WCF para tratar a concorrência e, além disso, exibir algumas das várias técnicas de sincronização fornecidas pelo .NET Framework e que poderão ser utilizadas em conjunto com o WCF.

  • Transferência e Codificação de Dados: Um dos maiores benefícios que serviços da Web tem em relação à tecnologias de comunicação distribuídas é o uso de XML como base da codificação, permitindo assim, a interoperabilidade entre as mais diversas plataformas. Entretanto, a escolha do padrão de codificação e a forma de transferência destas informações a serem adotados pelo serviço influenciará diretamente na performance e interoperabilidade do mesmo e também daqueles que o consomem. O WCF fornece algumas técnicas e alternativas que podemos adotar durante a configuração ou criação de um determinado serviço. A finalidade deste artigo é mostrar como implementar tal configuração/criação e analisar os impactos (inclusive a nível de contrato), benefícios e restrições de cada uma destas técnicas.

  • Chamadas Assíncronas: Muitas vezes desenvolvemos um método para desempenhar alguma tarefa e, depois de devidamente codificado, invocamos o mesmo a partir de algum ponto da aplicação. Dependendo do que este método faz, ele pode levar certo tempo para executar e, se o tempo for consideravelmente alto, podemos começar a ter problemas na aplicação, pois como a chamada é sempre realizada de síncrona, enquanto o método não retornar, a execução do sistema que faz o uso do mesmo irá congelar, aguardando o retorno do método para dar seqüência na execução. A finalidade deste artigo é mostrar como implementar o processamento assíncrono tanto do lado do cliente (proxy) bem como do lado do servidor (contrato) em serviços WCF.

  • Throttling e Pooling: Através do gerenciamento de instância de um serviço podemos definir qual a forma de criação de uma instância para servir uma determinada requisição. Essa configuração que fazemos à nível de serviço, através de um behavior, não impõe nenhuma restrição na quantidade de instância e/ou execuções concorrentes que são realizadas e, dependendo do volume de requisições que o serviço tenha ou até mesmo a quantidade de recursos que ele utiliza, podemos degradar consideravelmente a performance. O Throttling possibilita restringirmos a quantidade de sessões, instâncias e chamadas concorrentes que são realizadas para um serviço. Além do Throttling, ainda há outra funcionalidade que pode ser utilizada em um serviço, que é o Pooling de objetos. Este artigo explicará como proceder para efetuar a configuração do Throttling e suas implicações; também falaremos supercialmente sobre a estrutura do Pooling e como implementá-lo.

  • Transações: Uma necessidade existente em muitas aplicações é assegurar a consistência dos dados durante a sua manipulação. Ao executar uma tarefa, precisaremos garantir que, se algum problema ocorrer, os dados voltem ao seu estado inicial. Dentro da computação isso é garantido pelo uso de transações. As transações já existem há algum tempo, e a finalidade deste artigo é mostrar as alternativas que temos para incorporá-las dentro de serviços e clientes que fazem o uso do WCF como meio de comunicação.

  • Reliable Messages: Ao consumir um serviço WCF podemos interagir com o mesmo através de diferentes mecanismos, tais como, request-reply ou one-way. Sabemos que, independente do tipo que você utilize, a mensagem trafega entre o cliente e o serviço através da rede, utilizando o protocolo especificado pelo binding. Com isso, uma das principais preocupações que se tem é com relação a garantia de entrega da mensagem ao seu destinatário, pois problemas com a rede podem acontecer, fazendo com que a mensagem seja interceptada ou simplesmente perdida. A finalidade deste artigo é apresentar uma técnica disponibilizada pelo WCF, para evitar que problemas como estes comprometam a consistência e execução de um serviço.

  • Message Queue: Ao efetuar uma chamada para uma operação de um determinado serviço, desejamos que ela seja sempre executada. Mas nem sempre há como garantir isso, já que o serviço que atende as requisições, por algum motivo, está indisponível naquele momento. Isso fará com que as requisições sejam rejeitadas e o cliente somente conseguirá executá-la quando o serviço estiver novamente no ar. Para garantir a entrega da mensagem e o processamento assíncrono da operação (mesmo quando o serviço estiver offline), o WCF faz uso do Microsoft Message Queue. Este artigo irá explorar as funcionalidades e, principalmente, os benefícios fornecidos por essa integração.

  • Serviços RESTFul: A versão 3.5 do Windows Communication Foundation introduziu uma nova forma de expor e consumir serviços. Esse novo modelo, também conhecido como Web Programming Model, permite o consumo destes serviços através dos mais variados clientes, como é o caso dos navegadores. A finalidade deste artigo é explorar os tipos que estão disponíveis para tornar isso possível.

  • Syndication: Web Syndication é uma forma popularmente conhecida que temos para publicar um conteúdo de um determinado site para outros sites ou pessoas. Esta técnica fornece aos seus consumidores um pequeno sumário ou, às vezes, em sua íntegra, conteúdos que foram recentemente adicionados ao site. A partir da versão 3.5, o WCF disponibiliza uma API para suportar a criação de serviços que expõem o seu conteúdo em um dos formatos conhecidos para o syndication e esta API que será abordada no decorrer deste artigo.

  • Tracing: Toda e qualquer tipo de aplicação sempre exige uma forma de armazenar possíveis erros que possam acontecer durante a sua execução. Isso irá ajudar imensamente para diagnosticarmos problemas que ocorrem e, conseqüentemente, facilitar na sua solução. Isso não é diferente em serviços WCF. Esse artigo tem a finalidade de demonstrar a integração que os serviços WCF possibilitam para a captura e persistência dos erros para uma posterior análise.

  • Know Types: É muito comum em qualquer linguagem orientada a objetos, criarmos uma classe base e que, a partir dela, criar classes derivadas. Além disso, um dos grandes benefícios que temos com a orientação a objetos é a possibilidade de declararmos uma variável do tipo da classe base e atribuirmos a ela uma instância de uma classe concreta e, da mesma forma, podemos ter uma função em que em seus parâmetros os seus tipos são especificados com o tipo da classe base e, conseqüentemente, podemos também passar instâncias das classes derivadas. Infelizmente não funciona da mesma forma quando falamos de serviços que são expostos a partir do WCF. Neste cenário, por padrão, você não pode usar uma classe derivada ao invés de uma classe base. É justamente esta funcionalidade disponibilizada pelo WCF que iremos analisar neste artigo.

  • Segurança: Um dos grandes desafios de um software é a segurança do mesmo. Em qualquer software hoje em dia a segurança não consiste apenas em autenticar um usuário, mas também que direitos ele tem dentro do software. As coisas ficam mais complicadas quando falamos de um ambiente distribuído, tornando o processo de autenticação e autorização um pouco mais complexo e, como se não bastasse, temos que nos preocupar com a proteção das requisições que viajam entre o cliente e o servidor. A finalidade deste artigo é exibir todas as possibilidades que temos para manipular a segurança em serviços WCF.

  • Integração com MembershipProvider e RoleProvider: Por padrão, serviços WCF utilizam as identidades e grupos do Windows para autenticação e autorização, respectivamente. Um dos grandes problemas é quando temos isso sendo disponibilizado através de uma aplicação web, pois iria requerer que todos os clientes que acessam via web estivessem devidamente cadastrados dentro do Windows; além disso, há o problema com relação aos grupos de usuários, já que muitas vezes não temos acesso para cadastrá-los e, quando isso não é um problema, podemos ter um problema adicional quando estivermos rodando em culturas de servidores diferentes. Com isso, dificilmente uma aplicação ou serviços que são expostos para a internet utilizam as contas e grupos do Windows. A solução é que felizmente o ASP.NET 2.0 fornece uma infraestrutura completa para o gerenciamento de autenticação e autorização, chamada de Provider Model. Essa integração é o tema deste artigo.

  • Autenticação e Autorização Customizada: O WCF fornece várias possibilidades de gerenciar a autenticação e autorização dentro dos serviços. Uma dessas possibilidades é customizar como o WCF deverá autenticar e autorizar o cliente, analisando as suas credenciais, verificando se essas são válidas em um determinado repositório, determinar quais são os direitos que o cliente tem no serviço e, finalmente, conceder ou negar o acesso à alguma operação baseando-se em seus privilégios. A finalidade deste artigo é analisar os passos necessários para essa customização.

  • Partial Trust: Na primeira versão do WCF – .NET Framework 3.0 – ele não era suportado em ambientes que estavam sob Partial Trust, o que obrigava muitos clientes a conceder mais direitos do que o necessário para poder executar/invocar um serviço escrito em WCF. Depois de muitas requisições, a Microsoft decidiu afrouxar essa segurança com o lançamento do .NET Framework 3.5, permitindo (com várias restrições) que serviços sejam invocados a partir de um ambiente parcialmente confiável. A finalidade deste artigo é exibir como criar um proxy para um serviço que expõe um endpoint não suportado neste ambiente.

  • Consumindo serviços no AJAX: Uma das grandes partes do .NET Framework 3.0 foi o WCF. Quando ele foi lançado, várias formas de acessar os serviços WCF também foram disponibilizadas. Entre as formas, conhecidas como endpoints, podemos citar algumas, tais como: HTTP, TCP e MSMQ. Com a vinda do ASP.NET AJAX, surgiu a necessidade de consumir serviços WCF diretamente dentro deste tipo de aplicação. Através do Visual Studio .NET 2008 e o .NET Framework 3.5, a Microsoft se preocupou com a necessidade de consumir serviços WCF no AJAX e aproveitou esta oportunidade para criar um binding. Este binding, chamado de WebHttpBinding, trata-se de um novo tipo de binding que permite a criação de um endpoint para ser consumido por aplicações AJAX e que será discutido neste artigo.

  • Expondo componente COM+: Com o surgimento do WCF, uma plataforma de comunicação unificada, a Microsoft não se esqueceu do legado, ou seja, de componentes grandes e complexos hospedados no COM+ e, possibilita a utilização do WCF para expor esse componente através do HTTP (ou qualquer outra forma). Ao contrário do que acontecia anteriormente com Web Services, não precisamos recorrer ao Component Services para isso. Junto com o SDK do .NET Framework 3.X, a Microsoft disponibiliza uma ferramenta chamada Microsoft Service Configuration Editor que, dentre todas as funcionalidades disponibilizadas, uma delas é a possibilidade de integração de um componente COM+ a um serviço WCF, que será tema deste artigo.

Conclusão: O WCF fornece uma grande quantidade de funcionalidades que facilmente podem ser adicionadas em serviços. Além disso, grande parte dessas funcionalidades podem ser configuradas de forma declarativa, através de arquivos de configuração que, na maioria dos casos, traz uma enorme flexibilidade. O artigo mostrou os conceitos básicos necessários para a criação e consumo de um serviço WCF, que são informações importantes para dar sequência na leitura dos artigos acima, que exploram cada uma das principais funcionalidades.

Ordem de Execução dos Behaviors

A utilização dos behaviors no WCF nos permite adicionar algum código customizado durante a execução do serviço ou cliente. Há tres diferentes escopos onde eles podem existir: serviço, endpoint ou operação. E, para customizar cada um deles, é necessário implementar as Interfaces IServiceBehavior, IEndpointBehavior e IOperationBehavior, respectivamente.

Para adicioná-los, podemos utilizar a forma imperativa, utilizando as coleções de Behaviors existentes em cada ponto. Além disso, como quase tudo no WCF, também há possibilidade de vincularmos esses behaviors através do modo declarativo, utilizando o arquivo de configuração ou simplesmente atributos (herdando da classe Attribute).

A ordem de execução destes behaviors acontece na mesma ordem em que eles são adicionados, independentemente do modo (declarativo ou imperativo). Sendo assim, quando criar e adicionar mais que um behavior customizado para efetuar alguma tarefa, atente-se a ordem em que eles são adicionados ao runtime. Vale lembrar que isso não se faz necessário quando os behaviors que são adicionados são completamente autonomos.

Headers vs. Properties

Quando escrevemos serviços WCF tanto do lado do cliente quanto do lado serviço, temos a disposição uma classe chamada OperationContext que, como o próprio nome diz, fornece acesso ao contexto da requisição atual através de uma propriedade estática chamada Current.

A partir desta propriedade podemos manipular a coleção de headers e properties (IncomingMessageHeaders, IncomingMessageProperties, OutgoingMessageHeaders e OutgoingMessageProperties) de entrada e saída. Cada uma dessas coleções tem finalidades diferentes. Os headers existentes em uma mensagem estão associados com o corpo da mesma e influenciarão no processamento dela, já que podem armazenar informações de correlação, transações, segurança, mensagens confiáveis, etc., tudo de acordo com as especificações WS-*, ou seja, esses headers são utilizados pela própria infraestrutura do WCF e ultrapassam possíveis intermediários, chegando até o seu destino final.

Já as properties são utilizadas “localmente”, não ultrapassando esses intermediários. O próprio WCF já utiliza isso em alguns casos como, por exemplo, nos protocolos existentes e suportados por ele. Caso o transporte seja realizado através do protocolo HTTP, os detalhes específicos da requisição/protocolo (HTTP Headers) são armazenados na coleção de properties, “fora” da mensagem. Se efetuarmos o tracing, podemos ver o seguinte resultado:

<MessageLogTraceRecord>
  <HttpRequest>
    <Method>POST</Method>
    <QueryString></QueryString>
    <WebHeaders>
      <AlgumaChave>AlgumValor</AlgumaChave>
      <SOAPAction>http://www.projetando.net/IUsuario/Adicionar</SOAPAction&gt;
      <Connection>Keep-Alive</Connection>
      <Content-Length>411</Content-Length>
      <Content-Type>text/xml; charset=utf-8</Content-Type>
      <Expect>100-continue</Expect>
      <Host>projetando.net</Host>
    </WebHeaders>
  </HttpRequest>
  <s:Envelope>
    <s:Header>
      <ActivityId>6269eb97-955f-4e4c-8477-e6379127de2f</ActivityId>
      <To>http://www.projetando.net/usuarios</To&gt;
      <Action>http://www.projetando.net/IUsuario/Adicionar</Action&gt;
    </s:Header>

    <s:Body>
      <Adicionar xmlns=”http://www.projetando.net“>
        <nome>Israel</nome>
        <email>israel@projetando.net</email>
      </Adicionar>
    </s:Body>
  </s:Envelope>
</MessageLogTraceRecord>

O que temos em vermelho são as properties relacionadas ao protocolo HTTP, mais precisamente aos HTTP Headers. Note que o elemento em negrito trata-se de um valor customizado que foi adicionado a esta coleção. O conteúdo em azul são os headers referentes a mensagem.

Propriedade IsReference

É muito comum haver situações onde retornamos um array de objetos para o cliente. Cada elemento deste array poderá conter instancias de objetos que possuem propriedades que referenciam outros objetos ou até mesmo referencia circulares. Imagine a seguinte situação:

[DataContract]
public class Gerente
{
    [DataMember]
    public string Nome { get; set; }
}

[DataContract]
public class Empregado
{
    [DataMember]
    public string Nome { get; set; }
    [DataMember]
    public Gerente Gerente { get; set; }
}

Note que a classe Empregado possui uma propriedade que aceita uma instancia da classe Gerente. Na implementação do serviço, é perfeitamente possível que exista um mesmo gerente para vários empregados, fazendo com que uma mesma instancia da classe Gerente seja reutilizada por várias instancias da classe Empregado. O código abaixo ilustra isso:

List<Empregado> list = new List<Empregado>();
Gerente g = new Gerente() { Nome = “Bill Buchanan” };

list.Add(new Empregado() { Gerente = g, Nome = “Jack Bauer” });
list.Add(new Empregado() { Gerente = g, Nome = “Chloe O’brien” });
list.Add(new Empregado() { Gerente = g, Nome = “Michelle Dessler” });

Utilizando a configuração padrão do WCF, a instancia da classe Gerente será serializada para cada instancia da classe Empregado, aumentando consideravelmente o tamanho, ainda mais se o objeto conter várias propriedades. Esse comportamento é semelhante ao que conhecemos como by-value e, podemos notar isso a partir do resultado desta serialização:

<ArrayOfEmpregado …=””>
  <Empregado>
    <Gerente>
      <Nome>Bill Buchanan</Nome>
    </Gerente>

    <Nome>Jack Bauer</Nome>
  </Empregado>
  <Empregado>
    <Gerente>
      <Nome>Bill Buchanan</Nome>
    </Gerente>

    <Nome>Chloe O’brien</Nome>
  </Empregado>
  <Empregado>
    <Gerente>
      <Nome>Bill Buchanan</Nome>
    </Gerente>

    <Nome>Michele</Nome>
  </Empregado>
</ArrayOfEmpregado>

A versão 3.5 do .NET Framework trouxe uma nova propriedade para a classe DataContractAttribute: IsReference. Essa propriedade recebe um valor boleano que, por padrão é False, indicando se possíveis referencias de objetos devem ser mantidas na geração do envelope SOAP. Com isso, podemos configurar a classe Gerente como:

[DataContract(IsReference = true)]
public class Gerente
{
    [DataMember]
    public string Nome { get; set; }
}

Ao executar o mesmo código acima, o resultado passa a ser o seguinte:

<ArrayOfEmpregado
  xmlns=”…”
  xmlns:i=”…”>
  <Empregado>
    <Gerente z:Id=”i1″ xmlns:z=”…”>
      <Nome>Bill Buchanan</Nome>
    </Gerente>

    <Nome>Jack Bauer</Nome>
  </Empregado>
  <Empregado>
    <Gerente z:Ref=”i1″ xmlns:z=”…”/>
    <Nome>Chloe O’brien</Nome>
  </Empregado>
  <Empregado>
    <Gerente z:Ref=”i1″ xmlns:z=”…”/>
    <Nome>Michele</Nome>
  </Empregado>
</ArrayOfEmpregado>

Note que não há mais replicação do objeto Gerente. A instancia da classe Gerente será serializada no primeiro empregado e, a partir daí, todos os empregados que utilizarem a mesma instancia apenas a referenciam.

Surrogates para DataContracts

Como já é sabido, podemos expor um serviço WCF que utiliza tipos complexos, ou seja, uma classe customizada, contendo as propriedades que a descrevem. Com isso, basta referenciá-la em nosso contrato e, consequentemente, os tipos utilizados serão expostos nos metadados (WSDL) do serviço, permitindo aos clientes criar uma representação desta classe. Para exemplificar, eis a classe com a Interface que descreve o contrato do serviço:

[DataContract]
public class Usuario
{
    [DataMember]
    public string Nome { get; set; }
}

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    Usuario Recuperar(Usuario u);
}

Imagine que por algum motivo, voce cria um novo tipo, chamado OutroUsuario e quer fazer o uso deste no contrato, mudando tanto o tipo do retorno quanto o tipo do parametro u do método Recuperar. Com esta mudança, o WSDL continuará referenciando o tipo antigo Usuario, não “quebrando” as referencias atuais. A idéia é alterar o tipo apenas do lado do servidor, não refletindo do lado do cliente. O código abaixo ilustra o tipo OutroUsuario, que será utilizado pelo serviço ao invés do tipo Usuario:

[DataContract(Name = “Usuario”)]
public class OutroUsuario
{
    public string XNome { get; set; }
    public string Dummy { get; set; }
}

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    OutroUsuario Recuperar(OutroUsuario u);
}

Da forma que o contrato está, novas referencias utilizarão o tipo OutroUsuario e, além disso, os clientes atuais mandarão uma instancia de um tipo não conhecido pelo contrato (Usuario) e, não será possível efetuar a deserialização.

Felizmente o WCF disponibiliza um recurso chamado surrogate (substituto). Com a sua utilização, podemos interceptar e customizar a serialização, deserialização e a projeção dos metadados, podendo inclusive substituir um tipo por outro. Para criar um surrogate, é necessário criar uma classe e implementar a Interface IDataContractSurrogate. Entre os principais métodos, temos: GetDataContractType, GetDeserializedObject e GetObjectToSerialize.

O método GetDataContractType é responsável por mapear um tipo em outro, sendo invocado na serialização, deserialização e na importação e exportação dos metadados do serviço. Já o método GetDeserializedObject é invocado quando acontecer a deserialização de um objeto (cliente para o serviço). E, finalmente, o método GetObjectToSerialize é invocado apenas na serialização do objeto (serviço para o cliente). Abaixo temos a implementação do surrogate para exemplo:

public class SurrogateParaUsuario : IDataContractSurrogate
{
    public Type GetDataContractType(Type type)
    {
        return type == typeof(OutroUsuario) ? typeof(Usuario) : type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        Usuario u = obj as Usuario;
        if (u != null)
            return new OutroUsuario() { Dummy = “ValorPadrao”, XNome = u.Nome };

        return obj;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        OutroUsuario o = obj as OutroUsuario;
        if (o != null)
            return new Usuario() { Nome = o.XNome + o.Dummy };

        return obj;
    }

    //outros métodos
}

Quando um cliente invocar a operação Recuperar, ele passará a instancia de uma classe Usuario. Quando a mensagem chegar até o serviço, o método GetDataContractType será executado, devendo retornar um Type que representará o tipo em que o objeto deverá ser convertido que, no nosso caso, será o tipo OutroUsuario. Esse método é executado para cada tipo encontrado na operação, descartando os tipos primitivos (string, int, etc.).

Ao analisar o método GetDeserializedObject vemos que a instancia do parametro obj é do tipo Usuario e, neste momento, fazemos o mapeamento do tipo Usuario para o tipo OutroUsuario, mantendo os valores das propriedades fornecidas pelo cliente. Já no método GetObjectToSerialize fazemos o processo inverso, já que o nosso cliente espera a instancia de um objeto Usuario.

Essa classe por si só não funciona. Existe um behavior chamado DataContractSerializerOperationBehavior que nos permite interagir com o serializador do WCF. Ele por sua vez, fornece uma propriedade chamada DataContractSurrogate que recebe uma instancia de uma classe que implemente a Interface IDataContractSurrogate e deverá ser configurada antes da abertura do host, como mostrado abaixo:

host.AddServiceEndpoint(typeof(IContrato), new WSHttpBinding(), “srv”);

OperationDescription od = host.Description.Endpoints[0].Contract.Operations[0];
od.Behaviors.Find<DataContractSerializerOperationBehavior>().DataContractSurrogate =
    new SurrogateParaUsuario();

host.Open();

WCF – Syndication

Web Syndication é uma forma popularmente conhecida que temos para publicar um conteúdo de um determinado site para outros sites ou pessoas. Esta técnica fornece aos seus consumidores um pequeno sumário ou, às vezes, em sua íntegra, conteúdos que foram recentemente adicionados ao site. A partir da versão 3.5, o WCF disponibiliza uma API para suportar a criação de serviços que expõem o seu conteúdo em um dos formatos conhecidos para o syndication e esta API que será abordada no decorrer deste artigo.

Um dos exemplos mais comuns que conhecemos atualmente que fazem o uso desta técnica são sites de conteúdo bastante dinâmico, como é caso de notícias, fóruns e blogs. Disponibilizar o seu conteúdo através do syndication é uma forma simples que temos de manter nossos leitores atualizados, sempre informando que um novo conteúdo foi adicionado ao site. Os sites que publicam este tipo de conteúdo geralmente disponibilizam um endereço onde o mesmo é listado. Esse endereço também é conhecido como feed, e os interessados podem assiná-lo a partir deste endereço, cadastrando-o em um agregador que nada mais é do que uma aplicação Web ou Windows e que tem a finalidade de gerenciar todos os feeds.

Esse conteúdo é exposto via protocolo HTTP, podendo estar em texto puro ou até mesmo em formato HTML. Atualmente temos dois formatos que regem o syndication, sendo eles: RSS (Really Simple Syndication) e ATOM, ambos baseados em XML. O RSS foi criado antes do ATOM e este, por sua vez, tende a suprir algumas necessidades que o formato RSS deixa a desejar e, justamente por isso, as divergências entre os dois formatos são inevitáveis. A comparação e o detalhamento de cada um dos formatos estão além do escopo deste artigo. Se desejar analisar mais detalhes sobre isso, consulte estes endereços: RSS e ATOM.

Devemos separar as classes que o WCF fornece para a criação do syndication em duas seções, tendo a primeira delas classes para a criação dos metadados e dos itens que irão compor o feed e, isso tudo, de forma totalmente independente do formato; já a segunda seção terá as classes de infraestrutura, específicas ao formato (RSS 2.0 ou ATOM 1.0) e que serão responsáveis pela serialização do feed.

As classes que fazem parte da API do syndication estão contidas no namespace System.ServiceModel.Syndication e este, por sua vez, dentro do assembly System.ServiceModel.Web.dll. As primeiras classes que vamos analisar são as classes responsáveis pela serialização dos objetos que irão compor o feed. A primeira classe que temos que analisar é a SyndicationFeedFormatter. Esta classe é marcada como abstrata, servindo como base para todos os outros formatadores e, a partir dela, nascem suas especializações, como o Rss20FeedFormatter e Atom10FeedFormatter. A primeira delas, Rss20FeedFormatter, é responsável pela serialização do feed no formato RSS 2.0, enquanto a segunda é responsável pela serialização no formato ATOM 1.0. Ainda há a versão genérica para os dois formatos, que deve ser utilizada caso você tenha um feed customizado. A imagem abaixo ilustra essa hierarquia:

Figura 1 – Hierarquia dos formatadores.

A criação de syndication a partir do WCF nos obriga a criar o contrato do serviço seguindo algumas exigências. A primeira delas é que o método que retornará os itens (que podem ser notícias, posts, etc.) deve retornar a instância de uma classe que herde da SyndicationFeedFormatter, ou seja, uma das quatro opções que vimos na imagem acima. Neste caso, se quisermos ter as duas versões e permitir que o usuário escolha o formato, teríamos que ter dois métodos diferentes e, cada um deles, retornar a instância concreta do formatador. Uma alternativa a isso é especificar o tipo do retorno do método para a classe base SyndicationFeedFormatter, e o método passaria a receber um parâmetro que indica o formato desejado e, depois do feed criado, você cria a instância concreta da classe correspondente ao formato escolhido. O código abaixo ilustra a criação do contrato baseando-se nesta segunda opção:

using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Syndication;

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface IBlog
{
    [OperationContract]
    [WebGet(UriTemplate = "GetPosts?formato={formato}")]
    SyndicationFeedFormatter GetPosts(string formato);
}

Apesar do contrato ter apenas um único método, há algumas características que o mesmo utiliza e que não é uma exclusividade do syndication. A primeira delas é o atributo ServiceKnownTypeAttribute. Esse atributo especifica os possíveis tipos que podem ser retornados para o cliente (para maiores detalhes sobre know types, consulte este artigo). Se a operação já retornasse o tipo concreto, não haveria a necessidade de utilizar este atributo. Já a segunda característica trata-se do atributo WebGetAttribute. Este atributo faz parte de um conjunto de tipos que foram adicionados no WCF (também na versão 3.5) para expor serviços a partir do protocolo HTTP e acessíveis através dos métodos GET, PUT, POST, etc. Se desejar conhecer mais detalhes sobre o padrão REST no WCF, visite este endereço. O syndication não exige a criação do envelope SOAP, e justamente por isso que utiliza um formato mais simples de acesso, como o REST.

Depois de conhecer e saber como criar o serializador de um formato específico, precisamos saber como proceder para construir o feed. Ao contrário do formatador, a construção do feed nada sabe sobre qual formato ele será disponibilizado. A idéia aqui é criar os objetos de forma genérica, deixando o formatador se encarregar de como o feed será disponibilizado.

Uma das classes mais importantes neste processo é a SyndicationFeed. É através dela que iremos construir o feed, desde as informações de metadados até a coleção de itens que o mesmo irá disponibilizar. As informações de metadados consistem em quem são os autores, o título, categorias e a descrição do feed. Além dessas informações, temos ainda propriedades onde podemos definir a data de última atualização, idioma e a URL para possíveis imagens. O código abaixo ilustra como criar e configurar a classe que representará o feed:

using System;
using System.ServiceModel.Syndication;

public class BlogService : IBlog
{
    public SyndicationFeedFormatter GetPosts(string formato)
    {
        SyndicationFeed feed = new SyndicationFeed();
        feed.Title = new TextSyndicationContent("Blog do Israel Aece");
        feed.Authors.Add(
            new SyndicationPerson() 
            { 
                Email = "israel@projetando.net"
                , Name = "Israel Aece" 
            });
        feed.Description = new TextSyndicationContent("Blog Técnico");
        feed.Categories.Add(new SyndicationCategory("WCF"));
        feed.Categories.Add(new SyndicationCategory(".NET"));

        //resto da implementação
    }
}

Analisando o código acima nos deparamos com algumas novas classes que merecem ser abordadas. Seguindo a ordem de aparição, temos a classe TextSyndicationContent que representa qualquer conteúdo a ser exibido para o usuário final (mais detalhes sobre ela abaixo). Logo na sequência temos a classe SyndicationPerson que representa o autor do feed, e finalmente a classe SyndicationCategory que tem a finalidade de representar uma categoria de um feed ou de um item. É também importante notar que um feed pode conter um ou vários autores, bem como uma ou várias categorias e, justamente por isso, que a classe SyndicationFeed disponibiliza as coleções Authors e Categories.

Dando sequência no exemplo, a próxima classe a ser mencionada é a SyndicationItem. Cada instância desta classe representa um item (notícias, post, etc.) dentro do feed, mas a sua serialização será feita de forma diferente, dependendo de qual formatador é utilizado. Entre as principais propriedades temos o título (propriedade Title), um resumo da matéria (propriedade Summary) e o endereço até a mesma (propriedade Links). Além das propriedades, essas informações já podem ser definidas logo no construtor desta classe, conforme é mostrado no código abaixo:

using System;
using System.ServiceModel.Syndication;

public class BlogService : IBlog
{
    public SyndicationFeedFormatter GetPosts(string formato)
    {
        SyndicationFeed feed = new SyndicationFeed();
        feed.Title = new TextSyndicationContent("Blog do Israel Aece");
        feed.Authors.Add(
            new SyndicationPerson() 
            { 
                Email = "israel@projetando.net"
                , Name = "Israel Aece" 
            });
        feed.Description = new TextSyndicationContent("Blog Técnico");
        feed.Categories.Add(new SyndicationCategory("WCF"));
        feed.Categories.Add(new SyndicationCategory(".NET"));

        SyndicationItem item1 =
            new SyndicationItem(
                "Message Queue",
                "Descrição do Artigo de <b>Message Queue</b>",
                new Uri("http://www.projetando.net/Sections/ViewArticle.aspx?ArticleID=92"));

        SyndicationItem item2 =
            new SyndicationItem(
                "Transações",
                "Descrição do Artigo de <b>Transações</b>",
                new Uri("http://www.projetando.net/Sections/ViewArticle.aspx?ArticleID=91"));

        feed.Items = new SyndicationItem[] { item1, item2 };
    }
}

Como exemplo, estamos definindo o conteúdo do feed a partir de itens estáticos mas, em uma aplicação real, isso deveria ser extraído de algum repositório, como por exemplo, o banco de dados. Independentemente da origem dos dados que irão compor o feed, cada item deve ser representado por uma instância da classe SyndicationItem e, depois de criado, adicionado de uma coleção temporária, como o List<SyndicationItem>. Isso é necessário pois a classe SyndicationFeed fornece uma propriedade chamada Items que recebe a coleção de itens do feed e esta, por sua vez, espera por uma coleção que implemente a Interface IEnumerable<SyndicationItem>.

Aqui vale uma pausa para comentarmos sobre as classes que encapsulam o conteúdo de um item. Os conteúdos são peças importantes durante a criação do feed e de seus respectivos itens. Os conteúdos não se referem somente ao texto da matéria, mas também trata-se do título, descrição, etc., e esses conteúdos podem ser um texto simples ou HTML. Para encapsular cada um desses diferentes tipos de conteúdos, foi criada uma classe abstrata chamada SyndicationContent e que serve como base para outras classes que lidam com um tipo de conteúdo específico. Para conhecer as classes atuais que representam um determinado tipo de conteúdo, vamos analisar a imagem abaixo:

Figura 2 – Hierarquia das classes de conteúdo.

A primeira delas, UrlSyndicationContent, representa uma URL para algum outro recurso ou até mesmo o endereço para o conteúdo publicado. Já a XmlSyndicationContent consiste em encapsular um conteúdo XML que não será visível ao usuário, e é geralmente utilizado para serializar algum objeto customizado. Finalmente temos a classe TextSyndicationContent, que representa um conteúdo a ser exibido para o usuário, e é capaz de exibir um conteúdo em texto simples ou códigos HTML. Um de seus construtores recebe uma string que representa o conteúdo e uma das opções do enumerador TextSyndicationContentKind que descreve o tipo do conteúdo. As opções fornecidas por esse enumerador são:

  • PlainText: Indica que o conteúdo deve ser texto puro, sem tags.

  • Html: Suporta tags HTML, mas elas serão codificadas.

  • XHtml: O conteúdo será serializado como XML, e as tags não serão codificadas.

Apesar de omitirmos a criação do TextSyndicationContent durante a criação do item (SyndicationItem), o construtor se encarrega de instanciar esta classe e definir o conteúdo com a string que é informada.

Para completar o código do método GetPosts ainda temos que definir o formatador. De acordo com a regra, temos que analisar o parâmetro formato que é passado para o método e, dependendo do seu valor, instanciar o serializador do RSS ou do ATOM. Ambos formatadores têm uma versão do construtor em que aceita uma instância da classe SyndicationFeed contendo as configurações do feed. Para deixar o código mais legível, um método chamado RecuperarFormatador foi criado para instanciar a classe concreta do serializador, de acordo com o parâmetro que é passado para a operação. O código abaixo ilustra essa condicional que determina o serializador:

private static SyndicationFeedFormatter RecuperarFormatador(
string formato,
SyndicationFeed feed) { if (formato == "rss") return new Rss20FeedFormatter(feed); return new Atom10FeedFormatter(feed); }

Hosting

A maioria dos casos onde se utiliza o recurso de syndication é em sites e, como eles são hospedados no IIS, provavelmente utilizaremos ele para servir como host para o serviço que construímos. Sendo assim, podemos adicionar no projeto do site que estamos construindo um item chamado WCF Service. Esse arquivo de extensão *.svc representa um serviço WCF e será ele que representará a classe do serviço, aquela que deve implementar a Interface que corresponde ao contrato. O Visual Studio .NET 2008 também disponibiliza uma template de projeto chamada WCF Service, onde podemos agrupar todos os serviços mas, se já existe o projeto do Web Site, então nada impede de criar dentro dele apenas o arquivo com extensão *.svc.

Como estamos utilizando recursos REST, então vamos expor o serviço a partir do binding WebHttpBinding para que seja possível acessar o feed a partir da URL, sem a necessidade do protocolo SOAP. Isso permitirá que alguns leitores, como é o caso do próprio Internet Explorer 7.0, interpretem o conteúdo XML no próprio navegador e o mostrem de forma amigável ao usuário que está querendo assiná-lo.

Ao utilizar o modelo baseado no arquivo *.svc, toda a configuração do endpoint deverá ser feita de forma declarativa, ou seja, através do arquivo Web.config. Neste tipo de serviço também não é necessária a criação de endpoints para expor os metadados (WSDL) do serviço, já que não faz sentido o usuário referenciá-lo. Para exemplificar a configuração do serviço, vamos analisar o código abaixo:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="BlogService">
        <endpoint address="" binding="webHttpBinding" contract="IBlog"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

O atributo name do elemento service obrigatoriamente deverá refletir o nome/tipo da classe que representa o serviço. Já o elemento endpoint fornece os atributos address, binding e contract que são autoexplicativos. Para maiores detalhes sobre hosting de serviços WCF, consulte este artigo.

Ao criar um arquivo do tipo WCF Service (*.svc), por padrão, ele possui uma diretiva chamada @ServiceHost que, entre seus atributos temos: Service e CodeBehind. O primeiro atributo reflete o tipo da classe que representa o serviço; já o atributo CodeBehind especifica o caminho físico até o arquivo do serviço. Isso é bem semelhante ao que acontece com o ASP.NET e os arquivos de code-behind. Além disso, ainda precisamos definir o atributo Factory que, quando omitido (que é o padrão), ele assume o tipo ServiceHostFactory (namespace System.ServiceModel.Activation) que, por sua vez, retornará instâncias da classe ServiceHost. Como estamos trabalhando com REST, então devemos alterar o valor do atributo Factory para System.ServiceModel.Activation.WebServiceHostFactory que retornará instâncias da classe WebServiceHost. Para nosso exemplo, a configuração deve ficar como:

<%@ ServiceHost 
        Language="C#" 
        Debug="true" 
        Service="BlogService" 
        Factory="System.ServiceModel.Activation.WebServiceHostFactory"
        CodeBehind="~/App_Code/BlogService.cs" %>

Se executarmos a aplicação já teremos o conteúdo publicado pelo serviço à nossa disposição. Para acessar e visualizar o seu conteúdo não é necessária a criação de uma aplicação cliente, basta apenas acessar a URL diretamente no navegador que já conseguiremos visualizar o conteúdo. Para o exemplo deste artigo, o endereço é: “http://localhost:50569/Host/BlogService.svc/GetPosts?formato=atom&#8221;, podendo variar o parâmetro formato para “rss”, permitindo o usuário escolher o formato desejado simplesmente trocando o valor do parâmetro. A imagem abaixo ilustra o conteúdo interpretado pelo navegador:

Figura 3 – Conteúdo sendo exibido pelo navegador.

Como se pode perceber, temos que informar no endereço a extensão do arquivo *.svc e, em alguns casos, isso não é muito interessante. Se você desejar remover a extensão do arquivo do endereço do feed, você tem duas opções, dependendo de qual versão do IIS está utilizando. Caso esteja utilizando a versão 7.0, então você pode recorrer ao uso do Url Rewrite Module onde, através de expressões regulares, podemos customizar a URL de acesso ao serviço. Abaixo consta uma regra que podemos utilizar para remover a extensão *.svc do endereço:

<?xml version="1.0"?>
<configuration>
  <!-- outras configurações -->
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="RemoverExtensaoSvc" stopProcessing="true">
          <match url="^BlogService/(.*)$" />
          <action type="Rewrite" url="BlogService.svc/{R:1}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Com a regra acima aplicada ao diretório virtual, podemos acessar o serviço da seguinte forma: “http://localhost/Feeds/BlogService/GetPosts?formato=rss&#8221;.

Para as versões anteriores do IIS, a saída para remover a extensão *.svc do endereço do serviço é escrever um módulo para manipular o endereço. Para isso, devemos criar uma classe e implementar a Interface IHttpModule, analisando no evento BeginRequest a URL solicitada e, caso seja a solicitação para um arquivo *.svc, devemos sobrescrever a requisição através do método RewritePath da classe HttpContext.

Conclusão: O artigo descreveu as principais classes que são fornecidas pelo WCF para a criação de syndication, exibindo as classes que são utilizadas para compor o feed, bem com as classes necessárias para a serialização do mesmo. Para finalizar, a idéia do syndication a partir do WCF é apenas uma alternativa para a criação dos feeds e não um substituto dos padrões ATOM ou RSS.

Syndication.zip (8.89 kb)

WCF – Segurança

Um dos grandes desafios de um software é a segurança do mesmo. Em qualquer software hoje em dia a segurança não consiste apenas em autenticar um usuário, mas também que direitos ele tem dentro do software. As coisas ficam mais complicadas quando falamos de um ambiente distribuído, tornando o processo de autenticação e autorização um pouco mais complexo e, como se não bastasse, temos que nos preocupar com a proteção das requisições que viajam entre o cliente e o servidor.

A finalidade deste artigo é exibir todas as possibilidades que temos para manipular a segurança em serviços WCF. Algumas técnicas serão comentadas mas não serão esgotadas, pois elas merecem um artigo exclusivo para o seu completo entendimento. Já outras técnicas (as mais comuns) de autenticação, autorização e proteção de mensagens serão abordadas neste artigo, exemplificando a sua utilização e configuração.

Modos de Segurança

Apesar de serviços WCF não fugirem muito do padrão de segurança de aplicações convencionais, há algumas exceções e uma delas é a forma de segurança que será aplicada à mensagem. Essa forma de segurança influencia em como a autenticação será realizada e como a mensagem será protegida durante a sua viagem. É importante dizer que se a transferência da mensagem entre o cliente e o serviço ou entre o serviço e o cliente não fosse protegida, a autenticação e autorização estariam completamente vulneráveis, permitindo vários tipos de ataques.

Assim como várias outras configurações, a segurança também é uma característica do binding, podendo efetuar a configuração de forma declarativa ou imperativa. O WCF fornece cinco formas diferentes para tornar segura a transferência da mensagem. Cada uma dessas formas tem suas particularidades e influenciam em como a mensagem será protegida e como a autenticação será realizada. A tabela abaixo exibe essas cinco formas de segurança, detalhando cada uma delas:

Tipo Descrição
None Como o próprio nome já indica, nenhuma espécie de segurança é fornecida e toda a mensagem será trafegada sem criptografia.
Transport Esta opção informa ao WCF que o transporte (TCP, IPC, MSMQ ou HTTPS) irá garantir a segurança da mensagem, criptografando toda a comunicação e, além disso, fornece integridade, privacidade e autenticação mútua. Um dos pontos negativos deste modo é que a segurança será apenas garantida ponto-a-ponto, ou seja, se houver intermediários entre o cliente e o serviço não teremos a garantia de que a mensagem chegará segura até o destino final.
Message Com esta opção toda a mensagem será criptografada, garantindo assim a autenticação mútua e a proteção da mensagem (confidencialidade e integridade). Ao contrário da segurança baseada no transporte, a segurança a nível de mensagem, garante a segurança end-to-end, independentemente de quantos intermediários houver entre o cliente e o serviço, permintindo inclusive que o serviço seja exposto sob um protocolo não seguro, como é o caso do HTTP. Outro grande benefício que existe é que a segurança é baseada em padrões existentes no mercado, o que garantirá a interoperabilidade. Já um ponto negativo desta opção é o overhead que existe, pois toda e qualquer mensagem será criptografada e assinada.
Both Como o próprio nome diz, esta opção utiliza a segurança a nível de transporte e a nível de mensagem, ou seja, a mensagem será protegida e, além disso, será transferida por um transporte seguro. Apesar de maximizar a segurança, isso pode causar uma grande perda de performance e, atualmente, é somente permitido em protocolos específicos, como é o caso do Message Queue, onde a latência não é sentida.
TransportWithMessageCredential Esta opção é um mix das duas anteriores, ou seja, a autenticação do cliente será fornecida a nível de mensagem enquanto a proteção da mensagem (confidencialidade e integridade) e a autenticação do serviço serão fornecidas pela segurança do transporte.
TransportCredentialOnly Apenas a autenticação mútua é fornecida a nível de transporte, não havendo a proteção da mensagem. Esta opção somente está disponível para o binding basicHttpBinding.

A listagem acima possui todas as opções disponíveis, mas isso não quer dizer que todos os bindings fornecidos pelo WCF suportam todas elas. Todos os bindings oferecem uma propriedade chamada Security e, a partir dela, temos uma segunda propriedade chamada Mode que, como já podemos notar, especificará um dos tipos listados na tabela acima através de um enumerador exclusivo.

Para ter uma visão mais detalhada de quais modos de segurança cada binding suporta e, principalmente, qual é a configuração padrão, a tabela abaixo sumariza essas informações, incluindo o tipo do enumerador utilizado por cada binding para determinar o modo desejado (sendo as opções em negrito a configuração padrão):

Binding None Transport Message Mixed Both Enumerador
BasicHttpBinding Sim Sim Sim Sim Não BasicHttpSecurityMode
WebHttpBinding Sim Sim Não Sim Não WebHttpSecurityMode
WSHttpBinding Sim Sim Sim Não Não SecurityMode
WSDualHttpBinding Sim Não Sim Sim Não WSDualHttpSecurityMode
WSFederationHttpBinding Sim Não Sim Sim Não WSFederationHttpSecurityMode
NetTcpBinding Sim Sim Sim Sim Não SecurityMode
NetPeerTcpBinding Sim Sim Sim Sim Não SecurityMode
NetNamedPipeBinding Sim Sim Não Não Não NetNamedPipeSecurityMode
NetMsmqBinding Sim Sim Sim Não Sim NetMsmqSecurityMode
MsmqIntegrationBinding Sim Sim Não Não Não MsmqIntegrationSecurityMode

Uma vez que conhecemos os modos de segurança que temos e que é o binding quem determinará qual deles poderá ser utilizado, chega o momento de saber como devemos proceder para efetuar essa configuração no serviço. Essa configuração poderá ser realizada de forma imperativa ou declarativa. Os bindings fornecem a possibilidade de configurar a segurança a nível de transporte e de mensagem através de propriedades diferentes e, obviamente, que devemos recorrer a uma delas, dependendo do modo escolhido. Os dois trechos de código a seguir ilustram como fazer para efetuar tal configuração de forma imperativa e, na sequência, de forma declarativa:

using System;
using System.ServiceModel;

WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="srvBinding">
          <security mode="Message">
            <!-- outras configurações -->
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Quando a segurança está habilitada, há uma classe chamada ServiceSecurityContext que representa o contexto de segurança. Se ela estiver sendo acessada no serviço essa classe representará o cliente, e se estiver sendo acessada no cliente representará o serviço. Essa classe fornece uma propriedade estática chamada Current que retorna o contexto atual. As configurações de segurança de um binding e as informações fornecidas pela classe ServiceSecurityContext não param por aí. Vamos ainda explorar vários outros detalhes durante o decorrer deste artigo.

Autenticação

O processo de autenticação consiste em certificarmos que o cliente é quem ele diz ser e, indo mais além, também temos que certificar o serviço. Para efetuar o processo de autenticação, o cliente precisará informar as suas credenciais para que o serviço consiga autenticá-lo. Para isso, o WCF disponibiliza várias possibilidades, e cada uma delas atenderá um cenário específico. A tabela abaixo lista todas essas possibilidades, incluindo uma breve descrição de cada uma delas:

Tipo Descrição
None Nenhuma espécie de autenticação é realizada.
Windows Utiliza a autenticação baseada no Windows, fazendo com que o cliente forneça um token, representando sua credencial. Quando houver um domínio o protocolo Kerberos será utilizado, caso contrário, será utilizado o NTLM.
UserName Sendo uma das formas mais comuns, esta opção possibilita ao WCF trabalhar com um usuário e senha a serem informados pelo cliente antes de invocar a operação, validando o mesmo no próprio Windows ou em alguma tabela do banco de dados.
Certificate Permite ao cliente apresentar um certificado que o identifica.
IssuedToken Geralmente utilizado em serviços federados (serão abordados mais abaixo), issued tokes são tokens gerados por um serviço específico e, futuramente, podemos encaminhar este token para as aplicações que exigem a autenticação. Como há uma relação de confiança entre o serviço que gera o token e a aplicação, então o usuário está autenticado.
Custom Possibilita a criação customizada de um autenticador.

Para utilizar uma das opções, primeiramente é necessário analisar qual dos modos de segurança (transporte ou mensagem) você irá utilizar, pois nem todas as opções acima estão disponíveis em ambos os modos. Antes de falarmos sobre a implementação, é importante entender a relação dos tipos de credenciais e o modo de segurança e, para isso, as tabelas abaixo exibem a relação entre os modos de segurança e quais meios de fornecimento de identidade são suportados por eles, lembrando que são os bindings que expõem as propriedades utilizadas para tal configuração (em negrito é a configuração padrão):

Relação entre os bindings e a segurança baseada no transporte


Binding None Windows UserName Certificate
BasicHttpBinding Sim Sim Sim Sim
WebHttpBinding Sim Sim Sim Sim
WSHttpBinding Sim Sim Sim Sim
WSDualHttpBinding
WSFederationHttpBinding
NetTcpBinding Sim Sim Não Sim
NetPeerTcpBinding Não Não Sim Sim
NetNamedPipeBinding Não Sim Não Não
NetMsmqBinding Sim Sim Não Sim
MsmqIntegrationBinding Sim Sim Não Sim

Relação entre os bindings e a segurança baseada na mensagem


Binding None Windows UserName Certificate IssuedToken
BasicHttpBinding Não Não Sim Sim Não
WebHttpBinding
WSHttpBinding Sim Sim Sim Sim Sim
WSDualHttpBinding Sim Sim Sim Sim Sim
WSFederationHttpBinding
NetTcpBinding Sim Sim Sim Sim Sim
NetPeerTcpBinding
NetNamedPipeBinding
NetMsmqBinding Sim Sim Sim Sim Sim
MsmqIntegrationBinding

Cada binding fornece uma propriedade chamada ClientCredencialType que está acessível a partir da propriedade Transport ou Message, onde utilizaremos uma das duas, de acordo com o modo (proteção) de transferência escolhido. Para ilustrar essa configuração, os trechos de códigos abaixo exibem a forma imperativa e declarativa:

using (ServiceHost host = new ServiceHost(typeof(Servico), 
    new Uri[] { new Uri("http://localhost:9922")}))
{
    WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);
    binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

    host.Open();
}

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="ServiceBinding">
          <security mode="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Para efetuar a configuração específica de um dos tipos de autenticação no serviço, devemos recorrer a uma classe chamada ServiceCredentials (namespace System.ServiceModel.Description). Essa classe fornece propriedades específicas para cada tipo de autenticação (como por exemplo: IssuedTokenAuthentication, PeerCredential, UserNameAuthentication, WindowsAuthentication), onde devemos utilizar uma delas, de acordo com o tipo de autenticação a ser realizado. Essa classe ainda implementa a Interface IServiceBehavior e, com isso, deve ser adicionada como um behavior do serviço. Abaixo é mostrado como efetuar a configuração de um serviço para que o mesmo utilize a autenticação baseada em UserName:

using (ServiceHost host = new ServiceHost(typeof(Servico), 
    new Uri[] { new Uri("http://localhost:2999/") }))
{
    WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);

    ServiceCredentials credenciais = new ServiceCredentials();
    credenciais.UserNameAuthentication.UserNamePasswordValidationMode = 
        UserNamePasswordValidationMode.Custom;
    credenciais.UserNameAuthentication.CustomUserNamePasswordValidator = new Autenticador();
    credenciais.ServiceCertificate.SetCertificate(
        StoreLocation.LocalMachine,
        StoreName.My,
        X509FindType.FindBySerialNumber,
        "3c f2 d0 c0 ef 8e 0a 96 42 36 e6 54 5f 67 50 e0");

    host.Description.Behaviors.Add(credenciais);

    host.AddServiceEndpoint(typeof(IContrato), binding, "srv");

    host.Open();
    Console.ReadLine();
}

Quando utilizamos a autenticação baseada em UserName, devemos especificar qual será o modo de autenticação e, para isso, utilizamos a propriedade UserNamePasswordValidationMode que recebe uma das três opções do enumerador UserNamePasswordValidationMode, listadas abaixo:

  • Custom: Permite especificar um validador customizado.

  • Membership: Efetuará a validação baseando-se no ASP.NET.

  • Windows: Os usernames serão mapeados para usuários Windows.

Quando utilizamos a opção Custom devemos especificar a classe que fará a validação. Para isso, devemos definir a propriedade CustomUserNamePasswordValidator com a instância da classe validadora que, obrigatoriamente, deverá herdar da classe UserNamePasswordValidator, sobrescrevendo o método Validate, assim como mostrado abaixo:

using System;
using System.IdentityModel.Tokens;
using System.IdentityModel.Selectors;

public class Autenticador : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        if (userName != "Israel" || password != "123")
            throw new SecurityTokenException("Credenciais Invalidas.");
    }
}

Observação: Para utilizar a autenticação baseada em UserName/Password que o WCF fornece sob o protocolo HTTP, será necessário utilizar um certificado. Sem a utilização deste, não seria possível garantir a integridade e confidencialidade da mensagem, comprometendo as informações e, principalmente, permitindo que alguém intercepte a mensagem e capture os dados sigilosos.

Quando o serviço é exposto utilizando algum meio de autenticação que vimos acima, ao referenciar o mesmo em um cliente, será necessário que este cliente forneça as credenciais para o serviço que, consequentemente, irá validar o cliente em algum repositório. Quando criamos o proxy, a classe que o representa herda diretamente da classe ClientBase<TChannel> que, por sua vez, disponibiliza uma propriedade chamada ClientCredentials. Essa propriedade retorna uma instância da classe ClientCredentials (namespace System.ServiceModel.Description) que possibilita ao cliente configurar as credenciais, fornecendo propriedades especificadas para cada tipo de autenticação, por exemplo: ClientCertificate, HttpDigest, IssuedToken, Peer, ServiceCertificate, UserName e Windows. Devemos apenas utilizar uma delas, de acordo com a especificação/exigência do serviço.

Como os exemplos de configuração que vimos acima foi utilizando o UserName, o código abaixo ilustra como proceder para efetuar a configuração do lado do cliente, fornecendo explicitamente o username e senha antes de invocar a operação.

using (ContratoClient proxy = new ContratoClient())
{
    proxy.ClientCredentials.UserName.UserName = "Israel";
    proxy.ClientCredentials.UserName.Password = "123";

    Console.WriteLine(proxy.Metodo());
}

A classe ServiceSecurityContext é responsável por disponibilizar informações a respeito do contexto de segurança da requisição atual. As principais propriedades que ela fornece para extrair informações a respeito do usuário autenticação são: PrimaryIdentity e WindowsIdentity. Ambas refletem a identidade do usuário autenticado, mas a segunda somente estará acessível caso a autenticação seja baseada no Windows. Caso o usuário não estiver autenticado, a propriedade PrimaryIdentity retornará uma identidade do tipo GenericIdentity em branco. Abaixo o exemplo de como acessar essas propriedades:

ServiceSecurityContext ctx = OperationContext.Current.ServiceSecurityContext;

Debug.WriteLine(string.Format("PrimaryIdentity: {0}", ctx.PrimaryIdentity));
Debug.WriteLine(string.Format("WindowsIdentity: {0}", ctx.WindowsIdentity));

Autorização

Acontecendo sempre depois da autenticação, a autorização verificará quais direitos um determinado indivíduo tem sobre o sistema. Uma vez que sabemos quem ele é, então é necessário conhecermos também quais privilégios ele tem no sistema e, a partir daí, conceder ou negar acesso à um determinado recurso. Para gerenciar isso, o WCF fornece três diferentes técnicas, a saber:

Tipo Descrição
Role-based Neste caso o acesso às operações são controlados por papéis, ou seja, o usuário (cliente) somente conseguirá ter acesso se o mesmo estiver dentro do respectivo papel. Como repositório podemos utilizar os grupos do Windows, o RoleProvider do ASP.NET ou até mesmo um repositório customizado, como é o caso de um banco de dados exclusivo.
Identity-based Conhecido como Identity Model, este modo suporta um novo modelo de autorização baseado em claims, onde você deverá analisar cada claim que estará dentro das credenciais do usuário autenticado e, assim, determinar se o usuário tem ou não acesso.
Resource-based Com esta opção os recursos são protegidos a partir das ACLs do Windows (Access Control Lists). Esta opção geralmente obriga o serviço a fazer o impersonation ou delegation (mais detalhes abaixo) do cliente antes de acessar o recurso, ficando sob responsabilidade do próprio sistema operacional verificar se o cliente tem ou não permissão de acesso.

Vimos no primeiro modo, role-based, que podemos fazer a utilização de vários repositórios, como é o caso do ASP.NET 2.0, dos grupos do Windows ou customizado. Neste caso, utilizamos as técnicas existentes dentro do .NET Framework desde sua primeira versão, onde basicamente recorremos às classes que implementam as Interfaces IIdentity e IPrincipal. A segunda gerencia a autorização expondo um método chamado IsInRole que, dado um papel, retorna um valor boleano indicando se o usuário corrente faz ou não parte do mesmo. Quando trabalhamos com credenciais Windows, então temos a classe WindowsPrincipal; já quando tratam-se de outros repositórios, como é o caso do ASP.NET, então utilizaremos a classe GenericPrincipal para o gerenciamento da autorização. Para mais detalhes sobre esses tipos, consulte o capítulo 9 deste artigo.

O primeiro passo é a configuração da autorização. Essa configuração será definida a partir de um behavior de serviço do tipo ServiceAuthorizationBehavior que irá criar e gerenciar o repositório dos papéis. Este behavior disponibiliza uma propriedade chamada PrincipalPermissionMode que aceita uma das quatro opções expostas pelo enumerador PrincipalPermissionMode e que especificará qual deles utilizar:

  • Custom: Opção que permite especificar um repositório de papéis customizado. Neste caso, a classe que representará este repositório deverá obrigatoriamente implementar a Interface IPrincipal.

  • None: A autorização baseada em papéis está desabilitada, populando a propriedade CurrentPrincipal com uma identidade em branco.

  • UseAspNetRoles: Nesta opção o repositório a ser utilizado será o RoleProvider do ASP.NET.

  • UseWindowsGroups: Utiliza os grupos do Windows para efetuar a autorização. Este é o valor padrão quando não especificado.

A configuração de qual dos tipos utilizar pode, como várias outras configurações, ser definida de forma imperativa ou declarativa, lembrando que ela deve ser configurada antes da abertura do host. Os códigos abaixo exibem como efetuar essa configuração:

using (ServiceHost host = new ServiceHost(typeof(Servico), 
    new Uri[] { new Uri("net.tcp://localhost:9922")}))
{
    host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.UseAspNetRoles;

    host.Open();
}

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="DefaultService" behaviorConfiguration="ServiceBehavior">
          <!-- Endpoints -->
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceAuthorization principalPermissionMode="UseAspNetRoles" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Uma vez que a configuração do repositório da autorização já está definida, chega o momento de especificarmos qual usuário ou qual papel tem acesso à uma determinada operação ou recurso. Mais uma vez, temos a possibilidade de aplicar a autorização de forma imperativa e declarativa mas, neste caso, os modos são utilizados para diferentes técnicas. O modo declarativo definirá a autorização para o método todo, enquanto o modo imperativo nos dará uma maior flexibilidade, já que podemos analisar se o usuário pertence ou não a algum papel e, consequentemente, conceder o acesso. No código abaixo primeiramente verificamos se o usuário é um administrador e, caso seja, permite o acesso a algum recurso protegido e, como dito anteriormente, podemos silenciosamente efetuar alguma operação sem disparar exceções caso ele não faça parte deste grupo.

using System;
using System.ServiceModel;

public class Servico : IContrato
{
    public string Metodo()
    {
        if (Thread.CurrentPrincipal.IsInRole("Admin"))
        {
            //faça algo
        }
    }
}

No modo declarativo utilizamos um velho conhecido: o atributo PrincipalPermissionAttribute que faz parte do namespace System.Security.Permissions. Este atributo fornece uma propriedade chamada Role onde podemos especificar o papel necessário para que o usuário tenha o direito de executar o método. Caso mais de um papel possa ter o direito de acesso ao método, então você poderá aplicar múltiplas vezes o mesmo atributo no método, variando apenas o nome do papel, assim como é mostrado abaixo:

using System;
using System.ServiceModel;

public class Servico : IContrato
{
    [PrincipalPermission(SecurityAction.Demand, Role = "Admin")]
    [PrincipalPermission(SecurityAction.Demand, Role = "Gerentes")]
    public string Metodo()
    {
        //faça algo
    }
}

Caso o usuário não faça parte de nenhum dos papéis especificados no atributo, então uma exceção do tipo SecurityException será disparada. Esse atributo funciona com qualquer modo de autorização, mas precisamos nos atentar com alguns detalhes. Quando utilizamos o ASP.NET, basicamente colocamos o nome do papel; já quando utilizamos o Windows, então é necessário especificar o domínio antes do papel, ficando algo como: “DomínioAdmin”.

A integração com o RoleProvider do ASP.NET permite aos serviços WCF fazerem uso de uma arquitetura existente e fornecida pelo ASP.NET. O recurso de Provider Model do ASP.NET foi disponibilizado a partir da versão 2.0, trazendo vários recursos que utilizam esta técnica, como é o caso do MembershipProvider (falado acima) e do RoleProvider. Para entender detalhadamente como funciona essa integração, consulte este artigo.

Para finalizar esta seção, o modo Identity-based fornece um conjunto de APIs que gerencia os claims e as policies e, baseado nestas claims, concedem ou negam o acesso. Este recurso é comumente utilizado em ambientes federados, que será comentado mais tarde, ainda neste artigo. Já no modo Resource-based, a autorização será analisada pelo próprio sistema operacional, baseando-se em ACLs previamente configuradas.

Impersonation/Delegation

O conceito de impersonation já existe há algum tempo e não é exclusividade do WCF. Esta técnica permite à aplicação assumir a identidade do usuário para atuar como ele durante o acesso a alguns recursos, como sistema de arquivos e banco de dados que estão disponíveis na mesma máquina onde está sendo executado o serviço. Já o conceito de delegation é usado para acessar recursos que estão remotos, ou melhor, fora da máquina onde está o serviço.

Podemos controlar o impersonation nas duas extremidades, ou seja, do lado do serviço e do lado do cliente. Do lado do serviço podemos recorrer à propriedade Impersonation do atributo OperationBehaviorAttribute, especificando uma das três opções expostas pelo enumerador ImpersonationOption:

  • NotAllowed: O impersonation não será executado em uma operação específica. Quando não informado, esta é a opção padrão.

  • Allowed: Caso um token Windows esteja disponível e a propriedade ImpersonateCallerForAllOperations da classe ServiceAuthorizationBehavior esteja definida como True (que por padrão é definida como False), o impersonation será executado.

  • Required: Obriga o cliente a fornecer um token Windows para efetuar o impersonation, disparando uma exceção caso o mesmo não seja informado.

O código a seguir exibe como podemos configurar uma determinada operação para requerer o impersonation. Note que por se tratar de um behavior, isso deverá ser aplicado na classe que implementa a Interface do contrato:

using System;
using System.ServiceModel;

public class Servico : IContrato
{
    [OperationBehavior(Impersonation = ImpersonationOption.Required)]
    public string Metodo()
    {
        //Implementação        
    }
}

Como vimos, a opção Allowed somente faz sentido quando você desejar “ligar” ou “desligar” o impersonation (apesar de que isso não seja uma boa prática). Quando definido como Allowed o WCF sempre irá respeitar o valor especificado na propriedade ImpersonateCallerForAllOperations, que pode ser definida de forma imperativa ou declarativa, tendo o cuidado de definir este valor antes da abertura do host. O código abaixo ilustra essas duas possíveis configurações:

using (ServiceHost host = new ServiceHost(typeof(Servico), 
    new Uri[] { new Uri("net.tcp://localhost:9922")}))
{
    host.Authorization.ImpersonateCallerForAllOperations = true;

    host.Open();
}

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Servico" behaviorConfiguration="behaviorConfig">
        <!-- outras configurações -->
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="behaviorConfig">
          <serviceAuthorization impersonateCallerForAllOperations="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Acima foi falado que também podemos controlar a impersonation do lado do cliente. A idéia aqui é proteger o cliente de serviços maliciosos, que pode capturar a sua credencial e executar uma operação ilegal ou que danifique algum recurso. Pode ser que em alguns casos você não queira possibilitar que o cliente forneça a sua identidade para serviço e, para customizar isso, podemos utilizar o enumerador TokenImpersonationLevel (contido no namespace System.Security.Principal), que fornece as seguintes opções:

  • None: O impersonation não será permitido.

  • Anonymous: O serviço não poderá obter a identificação do cliente e, consequentemente, não poderá efetuar o impersonation para o cliente corrente.

  • Identification: O serviço poderá obter a identificação do cliente, mas não poderá efetuar o impersonation e, se tentar efetuar, uma exceção será disparada.

  • Impersonation: Além de obter a identificação do cliente, poderá também efetuar o impersonation do cliente, mas podendo acessar somente os recursos que estão rodando na mesma máquina do serviço.

  • Delegation: Pode efetuar o impersonation do cliente e também acessar recursos que estão além da máquina onde está o serviço.

Como essa é uma configuração que diz respeito ao cliente, é do lado do mesmo que devemos efetuá-la, podendo ser de forma declarativa ou imperativa. Como isso é uma configuração exclusiva para quando a autenticação é baseada no Windows, é através desta opção que iremos acessar a propriedade AllowedImpersonationLevel que aceita uma das opções expostas pelo enumerador que vimos acima:

using System.Security.Principal;

using (ContratoClient proxy = new ContratoClient())
{
    proxy.ClientCredentials.Windows.AllowedImpersonationLevel = 
        TokenImpersonationLevel.Delegation;

    //chamada para as operações
}

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="srv" behaviorConfiguration="edpBehavior" ...>
        <!-- outras configurações -->
      </endpoint>
    </client>
    <behaviors>
      <endpointBehaviors>
        <behavior name="edpBehavior">
          <clientCredentials>
            <windows allowedImpersonationLevel="Delegation"/>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Observação: Quando optamos por habilitar o impersonation de forma declarativa, o método todo será executado sob as credenciais do cliente, revertendo para a identidade original quando o método é retornado. Se quisermos ter um controle mais refinado sobre isso, então podemos utilizar o método Impersonate da classe WindowsIdentity, que nos permite efetuar o impersonation de forma imperativa. Quando utilizamos esta técnica, devemos utilizar o método Undo desta mesma classe para reverter a identidade.

Integridade e Confidencialidade

Felizmente o WCF fornece aos desenvolvedores a possibilidade de ter integridade e confidencialidade para o envio de mensagens. A integridade consiste em ninguém conseguir mudar o conteúdo da mensagem durante a sua viagem e, caso haja, o WCF conseguirá detectar e descartar a mesma. Já com a confidencialidade temos a garantia de que ninguém, com exceção do destinatário, conseguirá ler o que está definido no interior da mensagem. É importante dizer que a integridade não garante a confidencialidade, ou seja, a integridade apenas determina que o conteúdo não será modificado entre o cliente e o serviço, mas ele continuará legível.

Para controlar essas funcionalidades o WCF fornece dois mecanismos, onde um deles pode ser aplicado a uma operação específica enquanto o outro pode ser aplicado ao contrato como um todo. Escolher uma das alternativas dependerá do que você precisa proteger, ou seja, não vale a pena proteger todas as operações do contrato se várias delas não expõem nenhuma informação confidencial. Para ambas as alternativas podemos utilizar uma das opções expostas pelo enumerador ProtectionLevel, que está contido no namespace System.Net.Security. Esse enumerador fornece três opções:

  • None: Desabilita qualquer tipo de proteção na mensagem.

  • Sign: Apenas assina a mensagem, não criptografando-a. Essa opção irá garantir a integridade, enquanto o seu conteúdo continua legível.

  • EncryptAndSign: Criptografa e em seguida assina a mensagem, garantindo a integridade e confidencialidade.

Qualquer uma das opções escolhidas apenas afetam os dados da aplicação, ou seja, informações da infraestrutura, headers, etc., não serão impactadas. Outro detalhe importante é que quando o modo de segurança é definido como transporte, a mensagem toda será protegida pelos mecanismos fornecidos pelo transportante, não tendo qualquer efeito o uso do ProtectionLevel.

Como falado, você pode aplicar essa técnica a uma operação ou ao contrato todo e, para isso, temos a nossa disposição uma propriedade chamada ProtectionLevel, acessível através dos atributos que são utilizados para compor a mensagem e que estão em diferentes escopos: OperationContractAttribute, ServiceContractAttribute, FaultContractAttribute, MessageContractAttribute, MessageBodyContractAttribute e MessageHeaderAttribute. Vejamos através do trecho de código abaixo como podemos proceder para proteger todo o contrato:

using System;
using System.ServiceModel;
using System.Net.Security;

[ServiceContract(ProtectionLevel = ProtectionLevel.Sign)]
public interface IContrato
{
    [OperationContract]        
    string Metodo();
}

O uso desta técnica também está relacionada com o binding. Se você define explicitamente esta configuração, o binding obrigatoriamente deverá estar com a segurança habilitada. Caso você não defina explicitamente o ProtectionLevel e tiver a segurança habilitada, o WCF sempre utilizará a configuração padrão que é EncryptAndSign (salvo os bindings que não suportam segurança a nível de mensagem, como é o caso do basicHttpBinding).

Outro detalhe importante quando estamos trabalhando com criptografia é o algoritmo a ser utilizado. O WCF pode fazer uso de vários algoritmos existentes mas é importante dizer que os algoritmos devem ser iguais no serviço e no cliente para que a criptografia funcione sem problemas. Esta também é uma configuração que está debaixo do binding e deve ser especificada de forma declarativa ou imperativa. No modo declarativo, o atributo algorithmSuite recebe uma string que especifica o algoritmo, enquanto que no modo imperativo, a propriedade AlgorithmSuite espera uma instância da classe SecurityAlgorithmSuite, representando o algoritmo escolhido. No modo imperativo, utilizamos uma das propriedades estáticas disponibilizadas por esta mesma classe que retorna a instância da mesma configurada com o algoritmo escolhido. Os trechos abaixo ilustram ambas as formas de configuração do algoritmo de criptografia:

WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);
binding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.TripleDes;

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="BindingConfig">
          <security mode="Message">
            <message algorithmSuite="TripleDes" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Negociação

A “negociação” é uma funcionalidade que permite ao cliente e ao serviço negociar uma chave para efetuar a autenticação mútua e a proteção da mensagem quando o serviço está definido com a segurança baseada na mensagem. Na segurança baseada no transporte essa negociação é feita de forma automática e não conseguimos interferir neste processo.

Quando ela estiver habilitada, que é o padrão, ela elimina a necessidade do cliente conhecer antecipadamente a chave necessária para efetuar a troca das mensagens, ao contrário de quando ela estiver desabilitada, que nos obriga a ter alguns cuidados, como por exemplo: se o serviço espera por credenciais Windows, então tanto o cliente quanto o serviço devem estar dentro de um mesmo domínio; já em outras formas de autenticação, como UserName (que exige um certificado), obrigará o cliente a extrair a chave pública do certificado de uma outra forma, já que não haverá a negociação.

Há possibilidade de configurar este recurso tanto de forma declarativa quanto imperativa. No modo imperativo, recorremos à propriedade boleana NegotiateServiceCredential da classe NonDualMessageSecurityOverHttp, que estará acessível através da propriedade Security do binding. Já no modo declarativo temos o atributo boleano negotiateServiceCredential exposto através do elemento security, como é mostrado no trecho de código abaixo:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="BindingConfig">
          <security mode="Message">
            <message clientCredentialType="UserName" negotiateServiceCredential="false" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Sessões Seguras

Quando uma determinada operação é invocada, o processo de autenticação sempre será realizado, verificando se o usuário é quem ele diz ser. Se, a partir de um mesmo proxy eu invoque N operações, a autenticação ocorrerá N vezes. Visando diminuir este overhead, o WCF disponibiliza um recurso chamado de sessões seguras. Em lugares onde você precisa invocar várias operações, a criação de uma sessão segura melhora consideravelmente a performance, evitando que a autenticação aconteça toda vez.

Ao invocar a primeira operação, automaticamente a sessão segura será estabelecida entre o cliente e o serviço e, para identificá-la é criado um security context token (SCT). Com isso, todas as requisições subsequentes irão utilizar este token ao invés de criar uma nova sessão e/ou refazer a autenticação. É importante dizer que, por padrão, esta sessão (token) tem 15 minutos de vida, sendo descartada se nenhuma operação for invocada neste intervalo de tempo.

Basicamente, todos os bindings que suportam a segurança baseada em mensagem (e que a utilizam) também podem fazer uso das sessões seguras. Há alguns bindings que não tem a segurança em nível de mensagem como padrão, sendo o binding NetTcpBinding um exemplo disso. Apenas o fato de definí-lo com segurança em nível de mensagem, automaticamente ele criará a sessão segura, não permitindo desabilitar este recurso. Já os outros bindings, como o WSHttpBinding, nos permitem desabilitar este recurso, através do atributo boleano establishSecurityContext, do elemento security. O trecho de código abaixo ilustra como podemos proceder para desabilitá-lo:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="BindingConfig">
          <security mode="Message">
            <message establishSecurityContext="false" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Há também a possibilidade de configurar esta funcionalidade via modo imperativo. Para isso, basta recorrermos à propriedade (também boleana) EstablishSecurityContext da classe NonDualMessageSecurityOverHttp, que estará acessível através da propriedade Security do binding.

Como falado acima, o serviço cria um SCT para o cliente e, se no intervalo entre o retorno do serviço com o SCT criado e a próxima requisição o host for reinicializado (muito comum quando o serviço é exposto através do IIS), uma exceção do tipo MessageSecurityException será lançada. Isso ocorre porque do lado do serviço há uma chave que é armazenada na memória do processo e que está associada com o SCT do cliente e, uma vez perdido, ele não será mais válido.

Esse comportamento, que é o padrão, é conhecido como Stateless SCT e pode ser substituído por um Stateful SCT e, neste caso, a chave que antes ficava na memória do serviço, passa a ficar embutida no próprio SCT que é enviado para o cliente e, consequentemente, sobrevivendo às possíveis reinicializações do serviço.

Federation

Todas as configurações da autenticação e da autorização que vimos no decorrer deste artigo são realizadas em cima do serviço que estamos criando para atender uma determinada regra de negócio. Muitas vezes, ao criar um novo serviço para a mesma empresa, cria-se e configura-se novamente toda a parte de autenticação e autorização, e as vezes se tem duplicação de usuários, senhas, etc., dificultando o gerenciamento por parte da aplicação e também do usuário que a acessa.

O conceito de federação torna esse gerenciamento e segurança muito mais simples, seguro e eficaz. Os serviços apenas devem focar no desenvolvimento e execução da regra do negócio e a parte de autenticação e autorização será desacoplada do mesmo. Em um ambiente “federado”, o cliente (conhecido como Subject) deverá ser autenticado em um provedor de identidades (Security Token Service (STS)) e, finalmente, o token gerado será encaminhado para a aplicação (Relying Party (RP)).

Um outro grande benefício que temos ao utilizar os serviços “federados” é a possibilidade de estabelecer uma relação de confiança entre todos os participantes (RP), criando assim uma espécie de single sign-on (SSO). Isso quer dizer que precisamos nos autenticar uma única vez e ter acesso a todas as aplicações (RPs) que fazem parte, ou melhor, confiam em um provedor (STS) específico. Como há vários mecanismos de autenticação/autorização, o STS irá abstrair isso e as aplicações participantes passam a lidar apenas com os claims, sem a necessidade de conhecer a tecnologia utilizada para autenticação/autorização. A imagem abaixo ilustra supercialmente como este processo acontece:

Figura 1 – Participantes de serviços “federados”.
  • Fase 1: A fase 1 consiste em ir até o provedor de identidades e requerer pelo token que identifica o respectivo cliente no ambiente federado.

  • Fase 2: Se o cliente for encontrado, um token é gerado e enviado ao cliente.

  • Fase 3: A partir deste momento todas as requisições para a aplicação (ou serviço) serão realizadas de forma segura, contendo o token gerado pelo provedor que todos confiam.

  • Fase 4: Depois do token validado o serviço permite a execução da requisição e retorna a resposta para o cliente que a solicitou.

“Geneva”

Atualmente conhecido como “Geneva”, é o codename para um novo framework que a Microsoft está criando para facilitar a criação de aplicações e serviços baseados em claims e para implementação de serviço “federados”. Os recursos fornecidos por esta nova API vai desde a possibilidade de que aplicações (ASP.NET, Windows, etc.) ou serviços possam utilizar o modelo baseado em claims até a construção de nossos próprios STS.

Este framework está dividido em três grandes partes: “Geneva” Server, CardSpace “Geneva” e “Geneva” Framework. Infelizmente não há espaço para falar sobre essa API neste momento, pois ela precisa de um artigo exclusivo para abordar os problemas conhecidos e como ela pode ser utilizada para sanar cada um deles.

Conclusão: Apesar de um pouco grande, este artigo tentou cobrir as principais funcionalidades fornecidas pelo WCF para o gerenciamento de autenticação e autorização de serviços, abordando as vantagens e desvantagens de cada modo. Analisamos outras características ligadas diretamente à autenticação, como foi o caso do impersonation e o delegation e, além disso, vimos como proceder para proteger as mensagens, garantindo a integridade e confidencialidade. Para finalizar, foi falado superficialmente sobre Federation e o “Geneva” que são temas importantes e que serão esgotados em futuros artigos.

Seguranca.zip (59.55 kb)