Extensibilité de WCF (3/3)

1) Introduction

Après ces deux articles de base sur l'authentification UserName/Password et l'extensibilité de base via les interfaces WCF (IParameterInspector,...), nous allons aborder ici le changement de "policy" de base. Les classes que nous aborderont ici seront celles que bon nombre de personnes utiliseront/modifieront lorsqu'ils souhaiteront ajuster la sécurité de leur applications.

2) Implémentation de notre classe de base "Principal"

La première partie consiste à implémenter un custom Identity et un custom Principal comme il en existe déjà dans le framework .NET (Ex: WindowsIdentity, GenericIdentity ou WindowsPrincipal, GenericPrincipal). Nous avons également inséré un petit helper "con" permettant de convertir un token vers une instance d'identité. 

public class MyIdentity : IIdentity
{
    public string AuthenticationType
    {
        get { return ""; }
    }
    public bool IsAuthenticated
    {
        get { return true; }
    }
    public string Name
    {
        get { return "ChocolatUser"; }
    }
}
public class MyUser : IPrincipal
{
    public bool IsInRole(string role)
    {
        return true;
    }
    public IIdentity Identity
    {
        get { return new MyIdentity(); }
    }
    public static IPrincipal GetUser(Guid guid)
    {
        return new MyUser();
    }
}
3) Implémentation de notre classe client insérant un token
Comme expliqué dans le premier article sur les interfaces "bas niveau" de WCF, nous allons donc le mettre en pratique pour ajouter sur le client un token à chaque appel vers le service WCF. Pour faire simple, nous avons retiré toute la logique "complexe" et nous générons simplement un nouveau GUID. Celui-ci sera intégré dans le header du message.
public class ClientAddHeaderToken : IClientMessageInspector
{
    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
    }
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        MessageHeader header = MessageHeader.CreateHeader("MyToken""ns"Guid.NewGuid());
        request.Headers.Add(header);
        return null;
    }
}
public class ClientAddHeader : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, 
                                        BindingParameterCollection bindingParameters)
    {
    }
    public void ApplyClientBehavior(ServiceEndpoint endpoint, 
                                        ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new ClientAddHeaderToken());
    }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
                                        EndpointDispatcher endpointDispatcher)
    {
    }
    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

4) Implémentation de notre classe "Service" d'authentification

La première étape "sécurité serveur" consiste à implémenter une "Custom" classe d'authentification. Cette classe authentifier le message en interceptant le GUID passé dans le header par le client. Nous ajoutons donc l'utilisateur dans les "Properties" de l'appel WCF. La classe d'authorisation s'occupera de la suite... Cependant, il est possible dans notre classe d'authentification d'ajouter certaines nouvelles classes de "policies" d'autorisation.
public class ServerAuthenticationManager : ServiceAuthenticationManager
{
    public override ReadOnlyCollection<IAuthorizationPolicy> Authenticate(
                                                ReadOnlyCollection<IAuthorizationPolicy> authPolicy, 
                                                Uri listenUri, 
                                                ref Message message)
    {
        message.Properties["Principal"] = MyUser.GetUser(message.Headers.GetHeader<Guid>("MyToken""ns"));
        return base.Authenticate(authPolicy, listenUri, ref message);
    }
}

5) Implémentation de notre classe "Server" d'autorisation

Un point "central" d'une gestion personnalisée des autorisations est l'implémentation de IAuthorizationPolicy. Cette implémentation ajoutera/définira des règles/Claims pour chaque appel WCF mais en aucun cas ne lancera la gestion d'autorisation. La classe ServiceAuthorizationManager s'occupera de la validation des autorisations ! Nous retournons "true" à l'appel de la méthode Evaluate pour déclarer qu'aucune nouvelle évaluation de notre méthode de classe n'est requise en cas d'ajouts de nouvelles stratégies d'autorisation ... 
public class AuthorizationPolicyManager : IAuthorizationPolicy
{
    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        IPrincipal user = OperationContext.Current.IncomingMessageProperties["Principal"as IPrincipal;
        evaluationContext.Properties["Principal"] = user;
        evaluationContext.Properties["Identities"] = new List<IIdentity> { user.Identity };
        return true;
    }
    public ClaimSet Issuer
    {
        get { return ClaimSet.System; }
    }
    public string Id
    {
        get { return "MyCustomAuthPolicyManager"; }
    }
}

6) Ajout de la validation sur le service WCF

Après avoir implémenter nos classe d'authentification et d'autorisation, nous pouvons ajouter par exemple une restriction de permission sur une méthode. Ci-dessous par exemple, nous souhaitons ne permettre l'appel "GetCountry" uniquement aux utilisateurs possédant le role "Admin".
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] 
public class MyCountryService : ICountryService
{
    [PrincipalPermission(SecurityAction.Demand, Role = "Admin")]
    public Country GetCountry(string code)
    {
        return CountryDAL.GetCountry(code);
    }
}

7) Utilisation des classes générées ci-dessus dans le fichier de configuration

Il ne nous reste plus qu'à ajouter nos nouvelles classes dans notre fichier XML de définition du service WCF côté serveur. Nous définissons donc un "custom serviceAuthorization" et un "custom authentificationManager".
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="MyWCFServer.MyCountryService">
        <endpoint address="http://localhost:8080/CountryService.svc"
                  binding="basicHttpBinding"
                  contract="MyWCFServer.ICountryService"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceAuthorization principalPermissionMode="Custom" >
            <authorizationPolicies>
              <add policyType="WCFImprovements.Server.AuthorizationPolicyManager, WCFImprovements" />
            </authorizationPolicies>
          </serviceAuthorization>
          <serviceAuthenticationManager 
            serviceAuthenticationManagerType="WCFImprovements.Server.ServerAuthenticationManager, WCFImprovements"/>
          <serviceMetadata httpGetEnabled="true"
                           httpGetUrl="http://localhost:8080/MetaData/CountryService.svc"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

8) Implémentation de notre code "client" pour avoir le support "Token"

Nous avons tout mis en place... Nous pouvons donc reprendre le code créé dans notre étape 3 et l'insérer aux behaviors du clients.
CountryServiceClient client = new CountryServiceClient();
client.Endpoint.Behaviors.Add(new ClientAddHeader());
Console.WriteLine(client.GetCountry("BE").Label);

9) Conclusion

Et voici ... il ne reste plus rien à faire ... Tout fonctionne .. Comme vous pouvez le voir, il est très facile d'ajouter une custom sécurity ou d'étendre WCF selon nos besoins. Cette méthode est très pratique pour bon nombre de cas ... Ce sont des classes que l'on manipule parfois quand on "joue" avec des Claims etc ... 
Ce post termine cette petite série sur l'extensibilité WCF. Nous aborderons bientôt comment étendre WCF REST pour ajouter de la sécurité ...

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

Fil des commentaires de ce billet

Page top