Validando a Identidade do Serviço

O WCF fornece várias possibilidades para autenticar o cliente. Isso permitirá ao serviço somente possibilitar o acesso ao mesmo, se a autenticação do cliente for realizada com sucesso, independente do tipo de credencial e de como isso é avaliado. É muito comum quando falamos de autenticação, em pensar que somente isso acontecerá do lado do serviço.

Da mesma forma que o serviço precisa validar o cliente para conceder o acesso, o cliente também precisa validar o serviço, para saber se ele está enviando a mensagem para o local correto. Felizmente o WCF possibilita a autenticação mútua, ou seja, antes de efetivamente enviar a mensagem para o serviço, o cliente primeiramente fará algumas validações para determinar se o serviço para qual ele enviará a mensagem, é realmente quem ele diz ser.

Depois que o cliente inicia a comunicação com um endpoint e depois que o serviço o autentica, ele irá comparar a identidade do endpoint remoto com a identidade fornecida para o cliente durante a referência do mesmo, através do documento WSDL. Se os valores forem iguais, então o cliente assume que é o serviço correto. Isso irá proteger o cliente de possíveis ataques de phishing, evitando que o mesmo seja redirecionado para endpoints maliciosos.

Essa validação ocorre de forma transparente e dependerá de qual tipo de identidade é fornecida pelo serviço. Atualmente temos cinco possibilidades: Domain Name System (DNS), Certificate, Certificate Reference, RSA, UPN (User Principal Name) e SPN (Service Principal Name). O uso de cada um deles dependerá do cenário e dos requerimentos de segurança exigidos. Para saber mais detalhes sobre cada um deles, consulte este artigo.

Se você quiser interceptar esse processo, você poderá criar um “autenticador” customizado. Isso nos permitirá customizar como essa validação deverá ser realizada, ficando sob nossa responsabilidade determinar se o serviço para qual o cliente está tentando enviar a mensagem é válido ou não.

Para que isso seja possível, é necessário criarmos uma classe que herde da classe abstrata IdentityVerifier (namespace System.ServiceModel.Security) e implementar os métodos TryGetIdentity e CheckAccess. O primeiro deles, recebe um parâmetro de output do tipo EnpointIdentity que será populado, com um objeto que representa a identidade remota do serviço, retornando um valor boleano indicando se a identidade foi ou não criada. O segundo método, CheckAccess, é onde devemos efetivamente validar se a identidade do serviço é realmente válida. Esse método também retorna um valor boleano, só que neste caso, indicando se a identidade é válida ou não, de acordo com a regra que vamos customizar. O código abaixo ilustra a implementação desta classe:

public class ValidadorDeIdentidadeDoServico : IdentityVerifier
{
    public override bool CheckAccess(EndpointIdentity identity, AuthorizationContext authContext)
    {
        foreach (var set in authContext.ClaimSets)
        {
            foreach (var claim in set)
            {
                Console.WriteLine(claim.ClaimType);
                Console.WriteLine(“t{0}”, claim.Resource);
            }
        }

        Console.WriteLine();
        return true;
    }

    public override bool TryGetIdentity(EndpointAddress reference,
        out EndpointIdentity identity)
    {
        return IdentityVerifier.CreateDefault().TryGetIdentity(reference, out identity);
    }
}

Depois dessa classe criada, precisamos acoplá-la na execução do proxy. Mas para isso, iremos precisar de um binding customizado. Através do método abaixo vamos efetuar essa configuração. Note que ele atribui a instância da classe ValidadorDeIdentidadeDoServico na propriedade IdentityVerifier, que será responsável pela validação do serviço.

public static Binding ConfigurarBinding()
{
    WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);
    binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
    binding.Security.Message.EstablishSecurityContext = true;

    BindingElementCollection elements = binding.CreateBindingElements();
    SymmetricSecurityBindingElement ssbe =
        (SymmetricSecurityBindingElement)elements.Find<SecurityBindingElement>();

    ssbe.LocalClientSettings.IdentityVerifier = new ValidadorDeIdentidadeDoServico();
    return new CustomBinding(elements);
}

Para finalizar, temos que configurar o proxy para, ao invés de utilizar a configuração padrão do arquivo de configuração, usar o binding customizado, onde definimos a classe que será responsável por efetuar a validação do serviço. O código abaixo ilustra como fazemos a vinculação do binding ao proxy:

using (ContratoClient p =
    new ContratoClient(ConfigurarBinding(),
    new EndpointAddress(“http://localhost:2334/srv&#8221;)))
{
    //…
}

Anúncios

MultiMethods

É muito comum criarmos métodos em nossas classes que possuam “versões” diferentes. Por “versão”, entenda como overload ou sobrecarga do método. Isso quer dizer que podemos ter um método com o mesmo nome, mas que possuem quantidade ou tipos diferentes.

Em linguagens como o C# e o VB.NET, a resolução de qual dos overloads será invocado é feita em tempo de compilação, baseando no tipo que está sendo passado para o método, o compilador determinará qual das versões invocar. Suponhamos que temos a seguinte estrutura de classes:

public class Pessoa
{
    public string Nome;
}

public class PessoaJuridica : Pessoa
{
    public string CNPJ;
}

public class PessoaFisica : Pessoa
{
    public string CPF;
}

Agora, temos uma classe que tem a finalidade de gerenciar instâncias da classe Pessoa. Essa classe possui dois overloads, onde cada um deles espera uma instância concreta da classe Pessoa:

public class GestorDePessoas
{
    public void AdicionarPessoa(PessoaFisica pf)
    {
        Console.WriteLine("PF");
    }

    public void AdicionarPessoa(PessoaJuridica pj)
    {
        Console.WriteLine("PJ");
    }
}

Nossa aplicação trabalhará de forma independente de qual tipo de Pessoa esteja utilizando. Haverá um método chamado ConstruirPessoa que, dado o nome e o número do documento, retornará uma instância da classe PessoaFisica ou PessoaJuridica que, no nosso caso, deveremos passá-la para a classe GestorDePessoas. Se traduzirmos este parágrafo para código, então teríamos algo como:

Pessoa p = ConstruirPessoa(“Israel Aece”, “123456789”);
GestorDePessoas gp = new GestorDePessoas();
gp.AdicionarPessoa(p);

Se tentarmos compilar, receberemos um erro avisando que não foi possível encontrar um método com essa assinatura. As duas versões do método AdicionarPessoa esperam instâncias das classes concretas (PessoaFisica e PessoaJuridica). Mesmo que “p” armazene internamente a instância de uma classe concreta, ele não conseguirá determinar qual dos métodos invocar, pois somente saberemos o que “p” representa durante a execução da aplicação.

MultiMethods é um conceito que existe para escolher qual dos métodos invocar em tempo de execução, ao contrário do que acontece com o overloading, que fará essa escolha estaticamente, ainda em tempo de compilação. Há várias implementações de MultiMethods para .NET, e uma delas é o MultiMethods.NET. Com essa library, podemos criar apenas uma versão pública do método, que por sua vez, receberá a versão mais básica do tipo, que no nosso caso é a classe Pessoa. Internamente, a library determinará qual dos overloads invocar de acordo com o tipo que está sendo passado. O código abaixo ilustra a classe GestorDePessoas ligeiramente alterada, utilizando a library em questão:

public class GestorDePessoas
{
    private MultiMethod.Action<Pessoa> _adicionar;

    public GestorDePessoas()
    {
        this._adicionar = Dispatcher.Action<Pessoa>(this.AdicionarPessoa);
    }

    public void AdicionarPessoa(Pessoa p)
    {
        this._adicionar(p);
    }

    protected virtual void AdicionarPessoa(PessoaFisica pf)
    {
        Console.WriteLine("PF");
    }

    protected virtual void AdicionarPessoa(PessoaJuridica pj)
    {
        Console.WriteLine("PJ");
    }
}

O código de consumo desta classe que antes não funcionava, passa a funcionar, e em tempo de execução, o método AdicionarPessoa será invocado de acordo com o tipo retornado pelo método ConstruirPessoa. Publicamente temos apenas o método AdicionarPessoa que aceita o tipo base, e dentro dele, utiliza o delegate fornecido pela library para efetuar a escolha do método.

É importante dizer que estas libraries de MultiMethods recorrem a Reflection para efetuar todo o trabalho, e como sabemos, isso possui um overhead extra. Se de um lado ganhamos em flexibilidade, do outro perdemos em performance. Já com o .NET 4.0, o “tipo” dynamic evitará o uso dos MultiMethods. Esta palavra chave informará ao compilador que o método somente deverá ser conhecido em tempo de compilação, também se baseando nos tipos que forem passados à ele.

Ao contrário de grande parte dos tipos do .NET, a keyword dynamic não esta mapeada para algo como System.Dynamic; ao detectar uma variável definida com esta keyword, o compilador efetivamente criará uma variável do tipo System.Object mas, internamente, deverá recorrer à alguma API de DLR para invocar o membro, que talvez possa ser mais performático que Reflection.