Sicurezza in WCF
In una tecnologia che permette lo scambio di messaggi SOAP tra clients e servizi risulta essere di estrema importanza la sicurezza legata all’intero processo cui sono sottoposte queste informazioni.
In questo senso WCF integra nella sua piattaforma tecnologie ormai più che consolidate, già esistenti.

Possiamo racchiudere il principio di WCF security in quattro concetti: integrity, confidentiality, authentication e authorization.

Sebbene il WCF security sia un argomento vasto e fondamentale, in questa parte del tutorial si è deciso di affrontare più gli aspetti pratici con il solito progetto di esempio “passo-passo”, piuttosto che dilungarsi troppo sulla parte teorica.

Lo scopo della soluzione che andremo a creare sarà quello di permettere la comunicazione tra client e server esclusivamente previa autenticazione e garantiremo inoltre la sicurezza inerente lo scambio dei messaggi durante il loro intero processo.
L’aspetto più interessante di tutto ciò sarà l’integrazione di un authentication process security completamente personalizzato rendendo il progetto certamente più corposo, ma in grado di offrire al lettore tutti gli strumenti, o quasi, per potersi destreggiare con una certa agilità su questo argomento.

Il Server
Avviamo la prima istanza di VS e creiamo un progetto Web, quindi aggiungiamo subito un servizio WCF e ridefiniamo il metodo DoWork in modo che restituisca una stringa qualunque.
Come detto precedentemente, per poter accedere al servizio ed usufruire del metodo offerto sarà necessario fornire delle credenziali personalizzate, username e password di nostra scelta.
Per rendere questo possibile dobbiamo innanzitutto creare una classe, nel nostro esempio implementata in una dll, che eredita da UserNamePasswordValidator e ridefinirne il metodo Validate.

Avviamo quindi un’altra istanza di VS e scegliamo come progetto una nuova libreria di classi, inserendo questo codice al suo interno:

using System.IdentityModel;
using System.IdentityModel.Selectors;
namespace ClassLibrary1
{
    public class MyValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName != "user" || password != "pwd")
                throw new Exception("Username o Password non validi.");
        }
    }
}

Compiliamo la libreria e aggiungiamola come riferimento al progetto web.

A questo punto dobbiamo apportare una serie di modifiche al file web.config in modo da istruire correttamente il servizio su come dovrà comportarsi.
In particolare, dovremo specificare che tipo di autenticazione vogliamo utilizzare, quale certificato sfruttare per la crittografia dei dati di accesso e la modalità di sicurezza per i messaggi:

<behaviors>
   <serviceBehaviors>
    <behavior name="WebApplication1.Service1Behavior">
     <serviceMetadata httpGetEnabled="true"/>
     <serviceDebug includeExceptionDetailInFaults="true"/>  
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
                                    customUserNamePasswordValidatorType="ClassLibrary1.MyValidator, ClassLibrary1" />
            <serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
          </serviceCredentials>
        </behavior>
   </serviceBehaviors>
  </behaviors>
    <bindings>
      <wsHttpBinding>
        <binding name="myBinding">
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  <services>
   <service behaviorConfiguration="WebApplication1.Service1Behavior" name="WebApplication1.Service1">
    <endpoint address="" binding="wsHttpBinding" contract="WebApplication1.IService1" bindingConfiguration="myBinding">
     <identity>
      <dns value="localhost"/>
     </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
   </service>
  </services>

Soffermiamoci sui punti di maggior interesse.
In Behaviors abbiamo aggiunto un nuovo elemento, ServiceCredentials in cui abbiamo specificato il tipo di autenticazione (Custom) e il tipo di convalida per tali credenziali (cioè la classe creata e situata nella dll aggiunta al progetto).
All’interno di ServiceCredentials troviamo poi l’elemento ServiceCertificate che specifica quale certificato utilizzare. Nel nostro progetto il certificato specificato è un certificato di esempio creato da noi e installato sulla macchina su cui stiamo costruendo la soluzione; poco più avanti verrà spiegato in che modo crearlo e installarlo.
L’altro punto di interesse è la sezione relativa al binding, in cui è stato inserito l’elemento security, impostandone la modalità a message e per questi messaggi che tipo di credenziali il client dovrà usare.
Il nostro server è pronto, prima di premdere F5 però, come detto pocanzi dobbiamo installare sulla nostra macchina un certificato di esempio come specificato nell’elemento ServiceCertificate. Per farlo avviamo il command prompt di VS e digitiamo la seguente riga seguita dal tasto invio:

makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyServerCert -sky exchange –pe

Il certificato è ora installato, possiamo quindi avviare il nostro server premendo F5.

Il Client
Avviamo ancora una volta Vs e optiamo per la creazione di una Windows Form, quindi inseriamo un Button.
Aggiungiamo il riferimento al nostro servizio in esecuzione e iniziamo a configurare correttamente il client.
Prima di tutto è necessario verificare l’esistenza e la validità del certificato sul server e per farlo, anche in questo caso scriveremo una classe contenuta in un assembly che eredita da X509CertificateValidator.
Creiamo quindi un nuovo ed ultimo progetto di tipo libreria di classi e inseriamo il seguente codice:

using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;

namespace ClassLibrary2
{
    public class MyX509Validator : X509CertificateValidator
    {
        public override void Validate(X509Certificate2 certificate)
        {
            if (certificate == null)
                throw new ArgumentNullException("Nessun certificato rilevato");

            if (certificate.SubjectName.Name != "CN=MyServerCert")
                throw new SecurityTokenValidationException("Certificato non valido");
        }
    }
}

Le istruzioni presenti non fanno altro che verificare l’esistenza di un certificato e la sua validità, cioè la sua corrispondenza al certificato da noi desiderato.
Aggiungiamo un riferimento alla libreria appena creata sulla nostra applicazione e prepariamoci ad esaminare e modificare il solito app.config:

<bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
                    allowCookies="false">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <reliableSession ordered="true" inactivityTimeout="00:10:00"
                        enabled="false" />
                    <security mode="Message">
                      <message clientCredentialType="UserName" />                   
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
      <behaviors>
        <endpointBehaviors>
          <behavior name="myClientBehavior">
            <clientCredentials>
              <serviceCertificate>
                <authentication certificateValidationMode="Custom" customCertificateValidatorType="ClassLibrary2.MyX509Validator, ClassLibrary2" />
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
      </behaviors>
      <client>
            <endpoint address="http://localhost:50384/Service1.svc" binding="wsHttpBinding"
                bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1"
                name="WSHttpBinding_IService1" behaviorConfiguration="myClientBehavior">
                <identity>
                    <dns value="MyServerCert" />
                </identity>
            </endpoint>
        </client>

Un primo sguardo va dato sull’elemento security all’interno del’elemento binding, dove anche qui, come per il server è stata impostata la modalità di sicurezza (message) e la tipologia di credenziali da fornire (userName).
Ancor più interessante è la sezione behaviors, in cui viene specificato il comportamento per un singolo endpoint. E’ stato impostato il nome del behavior ed il tipo di autenticazione attraverso il riferimento alla libreria creata precedentemente.
Nell’ elemento endpoint poi è stato ovviamente inserito l’attributo behaviorConfiguration, specificando così a quale endpointBehaviors fare riferimento ed infine è stato modificato a MyServerCert il valore di value per l’elemento dns.

Non ci resta che gestire l’evento click del Button in questo modo:

ServiceReference1.Service1Client s = new WindowsFormsApplication1.ServiceReference1.Service1Client();
s.ClientCredentials.UserName.UserName = "user";    s.ClientCredentials.UserName.Password = "pwd";
s.Open();
this.Text = s.DoWork();
s.Close();

Anche il client è terminato.
Non ci sono molte considerazioni da fare per questo code snippet: è stata semplicemente creata un’istanza di Service1Client e prima di aprire la connessione e chiamare il metodo DoWork sono state impostate le credenziali di accesso al servizio.
Si noti però che le impostazioni di UserName e Password non sono possibili attraverso il file config, ma solo via codice.
Premiamo F5 e clicchiamo sul Button.
Il risultato dovrebbe essere la visualizzazione della stringa restituita dal metodo esposto nel servizio nella barra del titolo della Windows Form.

Qualcosa in più da sapere…
La piattaforma WCF implementa sistemi di sicurezza in modo efficiente, ma senza introdurre nulla di nuovo, usufruendo così di tecnologie già ben consolidate e conosciute dalla gran parte degli sviluppatori.
Ad esempio, nel nostro progetto avremmo potuto optare per un tipo di autenticazione nativa, come quella di Windows e certamente non sarebbe stata una scelta meno azzeccata o meno sicura di quella custom.
Il fatto di aver voluto utilizzare delle credenziali personalizzate, come Userame e Password trova riscontro in più ragioni: prima fra tutte, si ha avuto modo, nonché necessità di inserire nuovi elementi nel file config sino ad ora mai introdotti nei precedenti tutorial.
Ancora, in tanti e tanti tipi di progetti reali, la volontà di permettere un accesso sicuro client/server attraverso classiche credenziali “nome utente/password” è quasi una costante.
In rete è possibile trovare diverse guide, MSDN è una fonte eccezionale ed inesauribile, libri di testo ne esistono a volontà, eppure non sembra essere così semplice riuscire a trovare un esempio completo in ogni minimo dettaglio sull’utilizzo di credenziali custom in WCF.

Sempre a proposito di security, WCF integra nella sua piattaforma il concetto di Authorization e con esso, è possibile impostare dei criteri di autorizzazione/accesso a determinate risorse del servizio.
Tutto ciò è reso possibile attraverso l’attributo PrincipalPermission applicato a metodi del servizio, ASP.NET Membership e Role Provider, Authorization Manager e Identity Model.

Conclusioni
In questo articolo si è appreso come integrare differenti livelli di sicurezza lato client/server in WCF e se si considerano le sei precedenti pubblicazioni possiamo ritenere di conoscere gran parte degli elementi essenziali per un corretto utilizzo di questa tecnologia.


If you enjoyed this post, then make sure you subscribe to our RSS Feed.

Comments

Leave a Reply