martedì 27 dicembre 2011

Autenticazione di Web Service tramite Soap Header

 

In questi giorni sto lavorando ad un progetto per la creazione di una serie di Web Services che saranno consumati in prima battuta da una applicazione Windows Form e da altre applicazioni nel futuro.

Vista i tempi stretti e la necessità di “compatibilità interna” al team di sviluppo è stato scelto di non usare WCF ma di restare con i cari vecchi web methods. Per quanto riguarda l’autenticazione è stato scelto di usare un token, che tradotto nel caso specifico si tratta di un byte array costante, da passare del client al server e da validare lato server.

Nel mio caso i web methods da implementare saranno molti e quindi sono subito nate due esigenze: non tempestare i web methods con ripetitivi parametri e non duplicare il codice di validazione.

La soluzione scelta per la prima esigenza è stata quella di sfruttare la classe SoapHeader. Il soap header è un elemento opzionale del protocollo SOAP e contiene normalmente informazioni riguardanti autenticazione, pagamenti, …. perfetto! Nel mio caso quindi il token di autenticazione (sostituibile con le classiche proprietà utente e password) viene veicolato nell’intestazione soap.

Ma come fare per non duplicare il codice che effettua l’autenticazione vera e propria? La soluzione adottata è stata quella di utilizzare le classi SoapExtension e SoapExtensionAttribute. Il risultato finale è che in questo modo è possibile decorare un web method con il proprio attributo custom ed centralizzare il codice per l’autenticazione.

Ma adesso un po’ di codice.

Innanzitutto la classe MyCustomSoapHeader per la gestione del soap header che eredita da SoapHeader e contiene la proprietà byte array che rappresenta il token:

public class MyCustomSoapHeader : SoapHeader
{
    public byte[] Token;
    public MyCustomSoapHeader()
    {
    }
}
 

Nel web service basterà esporre una proprietà pubblica di tipo MyCustomSoapHeader e decorare il web method con l’attributo SoapHeaderAttribute:



public MyCustomSoapHeader MySoapHeader;
 
[WebMethod]
[SoapHeader("MySoapHeader", Direction = SoapHeaderDirection.InOut)]
public bool MyWebMethod(string parm1, int parm2)
{
    ....
}

In questo modo si dice al web service di usare la proprietà MySoapHeader per parcheggiare il soap header. Nel mio caso è stato aggiunto Direction = SoapHeaderDirection.InOut perchè si è voluto veicolare nel soap header anche altre informazione da rimandare al client chiamante.


Per la parte di autenticazione è stata implementata la classe TokenValidatorSoapExtension facendola ereditare da SoapExtension:



public class TokenValidatorSoapExtension : SoapExtension
{
    public override void ProcessMessage(SoapMessage message)
    {
        if (message.Stage == SoapMessageStage.AfterDeserialize)
        {
            MyCustomSoapHeader header = message.Headers[0] as MyCustomSoapHeader;
            if (header != null)
            {
                if (header.Token == new byte[] { 0x53, 0x64, 0x50, 0x32, 0x26, 0x21 };)
                {
                    return;
                }
            }
            throw new SoapHeaderException("Richiesta non autorizzata", SoapException.ClientFaultCode);
        }
    }
 
    public override object GetInitializer(Type serviceType)
    {
        return null;
    }
 
    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }
 
    public override void Initialize(object initializer)
    {
        return;
    }
}

Da notare che la classe SoapExtension ha vari metodi da sovrascrivere, ma solo ProcessMessage è quello che interessa per lo scopo. In particolare ProcessMessage ha 4 specifiche fasi SoapMessageStage in cui processare un messaggio: BeforeSerialize, AfterSerialize, BeforeDeserialize e AfterDeserialize. Ovviamente è stato usato AfterSerialize in quanto è l’unico momento in cui il messaggio soap o stato deserializzato in oggetti .NET.


Per poter applicare la SoapExtension ad un web method rimane solo da implementare una classe che erediti da SoapExtensionAttribute:



[AttributeUsage(AttributeTargets.Method)]
public class TokenValidatorAttribute : SoapExtensionAttribute
{
    private int _priority;
 
    public override Type ExtensionType
    {
        get { return typeof(TokenValidatorSoapExtension); }
    }
 
    public override int Priority
    {
        get { return _priority; }
        set { _priority = value; }
    }
}

A questo punto è possibile decorare il web method con l’attributo appena implementato e il gioco è fatto:



public MyCustomSoapHeader MySoapHeader;
 
[WebMethod]
[SoapHeader("MySoapHeader", Direction = SoapHeaderDirection.InOut)]
[TokenValidator]
public bool MyWebMethod(string parm1, int parm2)
{
    ....
}

Da notare l’attributo TokenValidator, che dice al web method di usare l’estensione TokenValidatorSoapExtension. A questo punto ogni volta che il web method viene invocato, viene recuperato il token dalla soap header custom MyCustomSoapHeader e avviene la validazione del token nell’override del metodo ProcessMessage.


Infine al client basterà creare il soap header e impostare il token:



RemoteService.RemoteService service = new RemoteService.RemoteService();
RemoteService.MySoapHeader myHeader = new RemoteService.MySoapHeader();
myHeader.Token = new byte[] { 0x53, 0x64, 0x50, 0x32, 0x26, 0x21 };
service.MyHeaderValue = myHeader;

Il meccanismo messo a disposizione dalla SoapExtension ovviamente può essere utile anche in scenari diversi da quello esposto.