terça-feira, outubro 08, 2013

Utilizando métodos seguros no WCF

Recentemente estive trabalhando com o WCF (Windows Communication Foundation) da Microsoft. E depois de fazer algumas experiências básicas lá fui eu me certificar da forma de transmissão segura e criptografadas.
Não vou me alongar na explicação porque isso tem no MSDN e em n blogs melhores escritor do que o meu :-), mas, como de praxe vou registrar um exemplo aqui (cabe ressaltar que daria para refatorar um pouco esse meu exemplo, que eu tive que fazer as pressas, principalmente a classe Greetting.cs que ficou bem porca mesmo. Admito, sem orgulho :-| #ficaAdica).

Para quem não sabe o que é o WCF, aqui vai a definição da Microsoft:

Windows Communication Foundation (WCF) is Microsoft's unified programming model for building service-oriented applications. It enables developers to build secure, reliable, transacted solutions that integrate across platforms and interoperate with existing investments.

Sacou?

Então vamos ao que interessa: código!
Em meu exemplo eu criei uma classe chamada Credential.cs que irá armazenar um token (que no meu caso é uma senha) e a informação de que tipo de device está consumindo meu serviço.
Além disso, há uma classe onde a lógica do meu programa está implementada e outra classe que é o serviço em sí (a lógica do tratamento das chamadas ao serviço).
Simplão, o funcionamento é: conferir se a senha passada está correta e se estiver chamar a classe que vai gerar a saudação e retorná-la ao client, caso contrário é lançar uma exceção.

Para começar essa é a classe da credencial (Credential.cs):

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

namespace com.blogspot.jeanjmichel.services
{
    /// <summary>
    /// This class represents the credential.
    /// </summary>
    [MessageContract]
    public class Credential
    {
        /// <summary>
        /// The token is a password that have be valid to authorize the 
        /// client to consume the service.
        /// </summary>
        [MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public String Token;
        
        /// <summary>
        /// The DeviceCategory is an inrelevant information that is not 
        /// encrypted and is just used to show the kind of device is 
        /// consuming the service (Whindows Phone, iOS, Android, web page, 
        /// etc).
        /// </summary>
        [MessageBodyMember(Order = 1, ProtectionLevel = ProtectionLevel.None)]
        public String DeviceCategory;

        /// <summary>
        /// Empty constructor.
        /// </summary>
        public Credential()
        {
        }
    }
}


Notem que a informação do token irá trafegar criptografada, no header da mensagem. Já a informação do device do client não irá trafegar criptografada.
Greeting.cs, este é o core, ou kernel, do programa, dependendo do quanto nojento você queira parecer falando isso :-) (roubei essa do Elemar Jr):

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

namespace com.blogspot.jeanjmichel.model
{
    /// <summary>
    /// This class represents a greeting.
    /// </summary>
    [MessageContract]
    public class Greeting
    {
        private String userGreeting;

        /// <summary>
        /// This method will set an appropriate greeting based on the server's 
        /// local time.
        /// </summary>
        private void SetGreeting()
        {
            DateTime now = DateTime.Now;

            if (now.Hour >= 7 && now.Hour <= 11)
            {
                this.userGreeting = "Good morning";
            }
            else if (now.Hour >= 12 && now.Hour <= 17)
            {
                if (now.Hour == 12 || now.Hour == 13)
                {
                    this.userGreeting = "Good afternoon, it's lunch time!";
                }
                else
                {
                    this.userGreeting = "Good afternoon";
                }
            }
            else if (now.Hour >= 18 && now.Hour <= 20)
            {
                this.userGreeting = "Good evening";
            }
            else
            {
                this.userGreeting = "Good night";
            }
        }

        /// <summary>
        /// This property returns the greeting in the message body,
        /// and this will move across the network ever encrypted.
        /// </summary>
        [MessageBodyMember(Order = 1, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public String UserGreeting
        {
            get { return this.userGreeting; }
        }


        /// <summary>
        /// Empty constructor.
        /// </summary>
        public Greeting()
        {
            this.SetGreeting(); //Call the method on construction time.
        }
    }
}


Fica a dica para quem quiser refatorar essa classe: usar internacionalização para retornar a saudação em n idiomas.

E por fim, mas não menos importante :-), a interface (contrato) do serviço e a implementação dele:

using System.ServiceModel;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel.services.contract
{
    /// <summary>
    /// This interface defines the service methods.
    /// </summary>
    [ServiceContract(Namespace = "http://jeanjmichel.blogspot.com/services/v0.0.1")]
    public interface IGetGreeting
    {
        /// <summary>
        /// This method will returns a greeting based based on the server's 
        /// local time.
        /// </summary>
        /// <param name="credential">An access credential that will be validated
        ///                          in order to authorize the access to the 
        ///                          method.</param>
        /// <returns>A Greeting object.</returns>
        [OperationContract]
        Greeting GetGreeting(Credential credential);
    }
}


using System;
using System.ServiceModel;
using com.blogspot.jeanjmichel.services.contract;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel.services
{
    /// <summary>
    /// This class is the service. Is where the logic behind the services' 
    /// calls occurs (validade credencial, invoke the business layer, etc).
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
                     Namespace = "http://jeanjmichel.blogspot.com/services/v0.0.1")]
    public class GetGreetingService: IGetGreeting
    {
        /// <summary>
        /// This method will returns a greeting based based on the server's 
        /// local time.
        /// </summary>
        /// <param name="credential">An access credential that will be validated
        ///                          in order to authorize the access to the 
        ///                          method.</param>
        /// <returns>A Greeting object.</returns>
        public Greeting GetGreeting(Credential credential)
        {
            if (String.IsNullOrEmpty(credential.Token))
            {
                throw new FaultException("Inform the security phrase," +
                                         " and try again.");
            }
            else
            {
                if (credential.Token.Equals("mySeCuriTyP@ss"))
                {
                    Greeting g = new Greeting();
                    return g;
                }
                else
                {
                    throw new FaultException("Wrong password.");
                }
            }
        }
    }
}


Só falta configurar a aplicação no app.config, na tag <endpoint> o atributo binding de basicHttpBinding por wsHttpBinding:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="GetGreetingService">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8081/soa" />
          </baseAddresses>
        </host>
        <endpoint address ="" 
                  binding="wsHttpBinding"
                  contract="com.blogspot.jeanjmichel.services.contract.IGetGreeting"
                  bindingNamespace="http://jeanjmichel.blogspot.com/services/v0.0.1" >
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <useRequestHeadersForMetadataAddress>
            <defaultPorts>
              <add scheme="http" port="8010" />
            </defaultPorts>
          </useRequestHeadersForMetadataAddress>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>


Agora o client para consumir o serviço:

using ConsoleClient.GetGreetingService; using System; using System.ServiceModel;   namespace ConsoleClient {     public class Program     {         static void Main(string[] args)         {             Console.WriteLine("Connecting to the endpoint...");             ChannelFactory<IGetGreeting> factory =                  new ChannelFactory<IGetGreeting>("WSHttpBinding_IGetGreeting",                      new EndpointAddress("http://192.168.0.173:8081/soa/GetGreetingService.svc"));               Console.WriteLine("Creating the proxy...");             IGetGreeting proxy = factory.CreateChannel();               Console.WriteLine("Creating the credential...");             Credential credential = new Credential();             credential.Token = "mySeCuriTyP@ss";             credential.DeviceCategory = "Windows Phone 7.8";               try             {                 Console.WriteLine("Call the service...");                 Greeting greeting = proxy.GetGreeting(credential);                   Console.WriteLine("Greeting: " + greeting.UserGreeting);             }             catch (Exception e)             {                 Console.WriteLine("An error has occurred. Message: " + e.Message);             }                          Console.ReadKey();         }     } }

Rodando esse client assim como está aqui a saída seria:

Connecting to the endpoint... Creating the proxy... Creating the credential... Call the service... Greeting: Good evening

Se você errar a senha:

Connecting to the endpoint... Creating the proxy... Creating the credential... Call the service... An error has occurred. Message: Wrong password.

E se você anular a senha:

Connecting to the endpoint... Creating the proxy... Creating the credential... Call the service... An error has occurred. Message: Inform the security phrase, and try again.

Vale ressaltar que eu estou hospedando a aplicação em um IIS local para testes.

Era isso. Qualquer dúvida é só comentar.

Nenhum comentário: