Skip to main content

Deux exemples de mise en œuvre de contrôles de sécurité dans nos services WCF

Par Pascal Belaud, Microsoft France
http://blogs.msdn.com/Pascal
17 Mars 2008 - Version 1.0

Lors que l’on développe et que l’on expose des services WCF, il faut bien évidemment mettre en place un service d’authentification. Ceci étant fait, on réalise assez vite qu’il nous faudra un service d’autorisation pour vérifier que l’appelant, bien qu’ayant été correctement authentifié par notre système, a toutefois le droit d’appeler le service demandé.

Une première solution consisterait à coder cette autorisation directement dans le service lui-même. Ainsi, on peut y vérifier, avant tout appel au service proprement dit, que l’appelant est bien autorisé à appeler le service.

L’inconvénient majeur d’une telle solution est que l’on mélange du code servant à rendre un service avec du code servant à autoriser l’appel. Or, généralement, le cycle d’évolution de ces deux types de code n’est pas le même. Cela complexifie toute tentative d’évolution des règles d’autorisation car toute modification de celles-ci implique un redéploiement de l’ensemble du service.

Nous allons voir dans cet article comment utiliser l’infrastructure totalement extensible de WCF. Cette extensibilité va nous permettre d’écrire du code d’autorisation de manière totalement indépendante du code du service lui-même exposé. Ainsi, deux équipes totalement différentes pourraient prendre en charge pour l’une l’aspect service proprement dit, et pour l’autre l’aspect autorisation.

L’ensemble du code source de cet article est téléchargeable à l’adresse suivante :

http://www.msfrancedev.net/Articles/DemosWCFSecurite.zip

Dans un premier temps, nous allons mettre en place la solution non sécurisée. Nous verrons ensuite comme mettre en œuvre un premier mécanisme d’autorisation. Nous essayerons enfin de voir comment aller plus loin dans le mécanisme d’autorisation en inspectant, non seulement les méthodes qui sont appelées, mais également les valeurs des paramètres fournis en entrée à ces méthodes.

Présentation du projet WCF initial

Pour démontrer tout ceci, nous allons partir d’un projet WCF classique exposant deux services. Ces services ne bénéficient pour le moment d’aucun service d’autorisation.

Notre solution Visual Studio 2008 est présentée à la Figure 1. Le langage de développement qui a été utilisé ici est Visual Basic 9.0 Un code équivalent pourrait être tout aussi bien développé en utilisant C# 3.0, ou tout autre langage .NET disponible.

Figure 1
Figure 1

Le contrat WCF exposé se trouve dans un projet de type Class Library appelé IServices et est décrit dans la Figure 2.

Figure 2
Figure 2

L’implémentation de ces deux services est effectuée dans le projet de type Class Library et appelé Services. Le code de cette implémentation se trouve à la Figure 3.

Figure 3
Figure 3

Afin d’exposer nos services, nous avons créé un projet de type Console Application appelé Host. Le code d’hébergement se trouve dans la Figure 4.

Figure 4
Figure 4

Comme vous le savez, cette application va nécessiter une configuration XML spécifique pour comprendre comment exposer les services. Cette configuration est décrite dans la Figure 5.

 

A noter que vous pouvez également exposer vos services WCF via un service Windows développé par Pascal Belaud [Microsoft France, http://blogs.msdn.com/Pascal ] appelé DynamicWCFServicesHoster et disponible gratuitement à l’adresse suivante :

http://blogs.msdn.com/pascal/archive/tags/DynamicWCFServicesHoster/default.aspx

Une fois notre hébergeur exécuté, nous obtenons ce qui est décrit dans la Figure 6.

Figure 6
Figure 6

Côté client maintenant, nous avons créé un projet de type Windows Application appelé Client. Le formulaire Windows se trouve à la Figure 7.

Figure 7
Figure 7

De la même manière que côté serveur, la partie cliente a également besoin d’un fichier de configuration XML.

Au lieu de l’écrire manuellement, nous allons utiliser la possibilité offerte par Visual Studio 2008 de générer pour nous l’ensemble du code et de la configuration pour appeler notre service. Vu que nos services exposent un contrat WSDL (voirFigure 5), il suffit d’ajouter une nouvelle référence à nos services comme démontré dans les Figure 8et Figure 9.

Figure 8
Figure 8

Figure 9
Figure 9

Attention pour pouvoir effectuer cette prise de référence, notre hébergeur doit être lancé afin de pouvoir effectivement exposer le contrat WSDL  nécessaire.

Si vous souhaitez mieux comprendre comment exposer le contrat WSDL correspondant  à vos services WCF, je ne peux que vous recommander la lecture de cet article :

Publication des meta-data des services WCF

http://blogs.msdn.com/pascal/archive/2007/10/26/article-publication-des-meta-data-des-services-wcf.aspx

Le code d’appel aux services WCF est exposé à la Figure 10.

Figure 10
Figure 10

Une exécution de l’ensemble de la solution nécessite de lancer et le client, et la partie hébergeur. Aussi, nous allons modifier les paramètres de notre solution pour lancer les deux projets (Client et Host) ensemble systématiquement. Cette configuration est décrite dans les Figure 11 et Figure 12.

Figure 11
Figure 11

Figure 12
Figure 12

L’exécution de cette solution produit ce qui est démontré dans la Figure 13. (Vous noterez l’adéquation complète entre le numéro de cette figure et le message renvoyé par l’application cliente)

Figure 13
Figure 13

Vous avez certainement noté que tous ces projets sont signés en utilisant un fichier contenant une clé publique et clé privée, PublicPrivateKeys.snk, directement généré par Visual Studio 2008 (voirFigure 14).

Figure 14
Figure 14

Mise en place d’un ServiceAuthorizationManager

A ce stade, nos services sont disponibles et sécurisés, vu que l’on utilise le binding netTcpBinding qui est sécurisé par défaut. Cependant, toute personne correctement authentifiée peut appeler ces deux services, et ceci commence à nous poser des problèmes car les choses sont en réalité plus complexes.

En effet, nous souhaitons que seul l’administrateur puisse appeler le service « MaFunction » tandis que ce même administrateur ne doit pas avoir le droit d’appeler le service « MaProcédure ». Nous voici donc avec un besoin de mise en place d’un service d’autorisation clairement identifié.

Dans cette section, nous allons utiliser la classe ServiceAuthorizationManager, se trouvant l’espace de nom System.ServiceModel :

http://msdn2.microsoft.com/library/system.servicemodel.serviceauthorizationmanager.aspx

Pour tirer partie de cette infrastructure, nous devons créer une classe héritant de la classe ServiceAuthorizationManager, puis de déclarer celle-ci au bon endroit afin que l’infrastructure WCF en tienne compte.

Nous allons donc créer un nouveau projet de type Class Library appelé Sécurité (voir Figure 15).

Figure 15
Figure 15

Nous allons y ajouter une classe appelée Vérificateur1 dont le code trouve dans la Figure 16.

Figure 16
Figure 16

Comme vous pouvez le voir, notre classe hérite de la classe ServiceAuthorizationManager et effectue une surcharge de la fonction CheckAccessCore. Une fois correctement déclarée, cette méthode va être appelée par l’infrastructure WCF  à chaque tentative d’appel d’un des services exposés.

A nous désormais de vérifier, en fonction l’appelant et de la méthode appelée, si l’autorisation peut être donnée en renvoyant True ou False selon le cas. Comme vous le constatez, cette méthode nous passe un paramètre de type OperationContext (System.ServiceModel) qui va nous permettre d’obtenir tous les informations nécessaires :

  • operationContext.ServiceSecurityContext.PrimaryIdentity.Namenous renvoie le logon de l’appelant
  • operationContext.IncomingMessageHeaders.Actionnous renvoie le nom de la méthode invoquée (à noter que le nom de la méthode renvoyée est en fait une URI au format défini dans la spécification WS-Addressing disponible ici : http://msdn2.microsoft.com/library/ms951225.aspx)

A noter que les URI de nos services peuvent être changées à tout moment en modifiant le contrat WCF comme indiqué dans la Figure 17.

Figure 17
Figure 17

Du coup, le code de vérification devra également être modifié en conséquence (Figure 18).

Figure 18
Figure 18

Donc comme on le voit dans le code de la classe Vérificateur1, décidez si l’appel est autorisé est un jeu d’enfants vu que l‘on sait qui appelle et quelle méthode est appelée.

Il ne reste plus qu’à déclarer ce service d’autorisation dans l’infrastructure WCF. Il y’a deux manières de réaliser cela :

  • Par programmation
  • Par configuration XML

Déclaration par programmation

Tout se passe dans la classe qui instancie le ServiceHost, c’est-à-dire dans notre hébergeur. La Figure 19 nous montre comment réaliser cela.

Figure 19
Figure 19

Déclaration par configuration XML

Dans ce cas, on n’a pas à toucher à notre code original, toute la configuration devant être réalisée dans le fichier app.config de notre hébergeur. La Figure 20 vous montre comme réaliser ceci.

Figure 20
Figure 20

Pour rappel, le code du client n’a pas changé et est proposé dans la Figure 21.

Figure 21
Figure 21

Lorsque l’on exécute la solution, nous obtenons le résultat de la Figure 22.

Figure 22
Figure 22

On voit que le premier appel a correctement eu lieu, tandis que le second appel a été refusé par l’infrastructure WCF. Il est à noter que c’est l’infrastructure qui s’est chargée de transmettre l’exception, SecurityAccessDeniedException, jusqu’au client.

Voilà, nous avons vu dans cette première partie comment mettre en place un système d’autorisation qui soit capable de tenir compte, et de l’appelant, et de la méthode appelée, pour pouvoir donner ou non les droits d’exécution.

Néanmoins, dans certains cas, cette approche peut ne pas être suffisante. En effet, dans certaines situations, tout le monde a le droit d’appeler une méthode mais pas avec n’importe quel paramètre en entrée.

La mise en œuvre d’une telle infrastructure fait tout l’objet du chapitre suivant.

Mise en place d’un IParameterInspector

Dans certaines situations, tout le monde a le droit d’appeler une méthode mais pas avec n’importe quel paramètre en entrée. Un exemple est la mise à jour de son adresse de messagerie. Un exemple d’implémentation d’un tel service pourrait ressembler à cela :

Public Sub MAJAdresseMessagerie(logonPersonne As String, adresseMessagerie As String)

N’importe qui doit avoir le droit d’appeler cette méthode, mais seulement pour modifier sa propre adresse de messagerie. Du coup, on est bien obligé de donner accès à cette méthode à tout le monde.

Le problème désormais est que quiconque peut appeler cette méthode en passant n’importe quel logon de personne, ce qui permettrait à n’importe qui de modifier n’importe quelle adresse de messagerie. Cette situation est parfaitement inacceptable.

Heureusement, l’infrastructure WCF a été conçue de manière suffisamment robuste et extensible pour nous permettre de pouvoir très facilement mettre en place la couche de vérification nécessaire dans ce type de scénarios.

Pour ce chapitre, nous allons repartir de la solution initiale, c'est-à-dire celle n’implémentant pas la classe ServiceAuthorizationManager,  vue au chapitre précédent.

Pour réaliser cette nouvelle implémentation, nous allons utiliser l’interface IParameterInspector (System.ServiceModel.Dispatcher) :

http://msdn2.microsoft.com/library/system.servicemodel.dispatcher.iparameterinspector.aspx

Cette interface va nous permettre d’être appelé avant et après tout appel. De plus, nous saurons également quels paramètres sont passés avant l’appel et quels sont les valeurs de retour renvoyées après que l’appel ait été effectué.

Nous allons donc créer un nouveau projet de type Class Library appelé Sécurité (voir Figure 23).

Figure 23
Figure 23

L’implémentation de ces deux services est effectuée dans le projet de type Class Library et appelé Services. Le code de cette implémentation se trouve à la Figure 3.

Figure 24
Figure 24

Comme on le voit, tout l’art va consister à implémenter l’interface IParameterInspector, ce qui revient à implémenter deux méthodes en réalité. Voyons désormais ce code d’un peu plus près.

La méthode BeforeCall (Figure 25) nous permet de savoir qu’on essaie d’appeler telle ou telle méthode, avant que cet appel ne soit effectivement réalisé.

Figure 25
Figure 25

Cette fois-ci, le nom de la méthode étant invoquée nous est directement passé via le paramètre operationName. Il est à noter que, cette fois-ci, le nom de la méthode n’est pas une URI mais bel bien le nom de la méthode appelée, telle qu’exposée dans le WSDL.

Nous avons également, sous forme d’un tableau d’objets, inputs, accès à tous les paramètres qui ont été passés à cette méthode.

Enfin, grâce à la propriété statique Current de l’objet OperationContext, il est aisé de récupérer le logon de la personne effectuant réellement l’appel.

Du coup, nous avons toutes les informations requises pour pouvoir prendre une décision.

A noter que le comportement de cette méthode est quelque peu différent de la classe ServiceAuthorizationManager. En effet, dans le cas présent, ne rien faire, c'est-à-dire laisser la méthode se dérouler normalement jusqu’au bout, est synonyme d’autorisation donnée à l’exécution.

Pour signifier que l’appel est interdit, il nous faut lever une exception pour interrompre l’exécution normale du code. Il est d’usage de lever à ce moment là une exception de type FaultException(Of SecurityAccessDeniedException). Il est à noter que notre service WCF doit autoriser la propagation d’une telle exception pour que le client puisse être correctement notifié de l’exception.

Ceci est réalisé en positionnant l’attribut adéquat, FaultContract, au niveau de notre interface comme l’indique la Figure 26.

Figure 26
Figure 26

Une fois que l’appel a été autorisé et qu’il a été effectué, nous allons être rappelés par la méthode AfterCall.

Dans cette méthode, l’infrastructure va nous communiquer cette fois-ci, l’ensemble des paramètres de retour, c'est-à-dire ceux qui ont été transmis lors de l’appel par référence (ByRef) ainsi que le résultat final de l’appel, si la dite-méthode renvoie quelque chose évidemment.

On peut encore à ce niveau là intervenir et interdire le retour de la méthode pour telle ou telle raison.

De plus, il peut être utile de transmettre une sorte d’ « état » entre le moment où on est appelé dans BeforeCall et le moment où l’on est appelé dans AfterCall. C’est tout l’objet du CorrelationState.

On a la possibilité, dans BeforeCall, de renvoyer n’importe quel objet. L’infrastructure WCF va se charger de « garder au chaud » pour nous cet objet jusqu’à l’appel de la méthode AfterCall. Et c’est à ce moment précis que l’infrastructure va retransmettre cet objet via le paramètre correlationState.

LaFigure 27 montre un exemple d’implémentation qui va récupérer tout simplement les paramètres d’entrée (transmis grâce au paramètre CorrelationState) et effectuer un refus pour une certaine valeur d’un des paramètres. Evidemment, on aurait pu faire ce test dans le BeforeCall mais c’était juste une manière ici de montrer comment opérer une interdiction d’exécution depuis la méthode AfterCall.

Figure 27
Figure 27

Il ne reste plus qu’à déclarer à l’infrastructure WCF la présence de cet Inspector.

Pour ce faire, nous allons positionner un attribut spécifique devant l’implémentation de nos méthodes. La Figure 28  montre l’utilisation d’un attribut appelé OpérationSécurisée devant chacune des méthodes.

Figure 28
Figure 28

Bien que cela serait très plaisant, les développeurs de Redmond ne se sont malheureusement pas mis à fournir des noms de classes ou de méthode en françaisJ. Non, cet attribut est un attribut personnalisé qui a été développé pour l’occasion.

LaFigure 29 montre l’implémentation de cet attribut.

Figure 29
Figure 29

Il est à noter que cette classe hérite de la classe Attribute (pour pouvoir être considérée comme un attribut) et implémente une interface spécifique : IOperationBehavior (System.ServiceModel.Description) :

http://msdn.microsoft.com/library/system.servicemodel.description.ioperationbehavior.aspx

Cette dernière interface va justement nous permettre d’insérer notre « inspector » dans la liste (qui peut être longue !) des « inspectors » à invoquer à chaque appel de méthode.

On peut également effectuer la même opération en passant par le fichier de configuration XML, chose que je n’ai pas faite dans cet article. Je vous renvoie donc à la documentation si vous êtes intéressés par cet aspect là.

Voilà, tout est place désormais pour tester cette « stack » d’autorisation ainsi développée.

Le code client de la Figure 30 va effectuer un premier appel à la fonction MaFunction qui va être autorisé car le paramètre entier fourni est 1.

Figure 30
Figure 30

Par contre, une nouvelle tentative en suivant, avec la valeur 0, va être tentée, appel qui devrait être interdit par la méthode BeforeCall. La Figure 31 montre le résultat de l’exécution.

Figure 31
Figure 31

Maintenant, nous allons effectuer un nouveau test mais en passant la valeur 2 au lieu de la valeur 0 lors du deuxième appel. Ce deuxième appel devrait donc être autorisé dans BeforeCall mais interdit cette fois-ci dans la méthode AfterCall. La Figure 32 montre le code client tandis que la Figure 33 montre le résultat.

Figure 32
Figure 32

Figure 33
Figure 33

Conclusion

J’espère que, par l’intermédiaire de cet article détaillé, vous appréhendez désormais un peu mieux la plateforme WCF qui contient un nombre incalculable de points d’entrée pour l’étendre. J’en découvre moi-même tous les jours

Un grand merci à l’ami David Rousset, Partner Technical Consultant chez Microsoft France, pour la relecture et les corrections très précieuses qu’il a bien voulu apporter à cet article. Vous pouvez retrouver son blog à l’adresse suivante :
http://blogs.msdn.com/dev

 

Haut de pageHaut de page

Microsoft réalise une enquête en ligne pour comprendre votre opinion sur le site Web de. Si vous choisissez de participer, l’enquête en ligne vous sera présentée lorsque vous quitterez le site Web de.

Souhaitez-vous y participer ?