Gérer les autorisations sur une application WCF

Gérer les autorisations sur une application WCF

WCF pour Windows Communication Foundation est un composant qui permet de créer des services qui utilisent le protocole HTTP pour exposer et consommer des données sur le Web. Les données sont exposées sous forme de ressources adressables par des URI, elles sont accessibles et modifiables à l'aide des verbes HTTP standard. L'interface entre l'application et le web est réalisée au travers d'une classe. Ses méthodes publiques sont exposées en HTTP et peuvent être annotés pour définir le verbe ou le chemin de l'URI.

L'appel à ces méthodes ne gèrent ni l'authentification ni les autorisations, mais il est possible de l'implémenter de plusieurs manières :

  • Créer un service de sécurité et l'appeler dans chaque méthode,
  • Ajouter une passerelle d'API frontale qui gère les autorisations,
  • Annoter les méthodes avec le système de droits,
  • ...

Il existe en réalité une multitude d'implémentation, mais ici nous allons parler d'un système à base d'annotations.

Objectifs

Nous allons partir d'une application WCF existante. Cette application gère en REST un CRUD(Create Read Update DELETE) sur les entités de type User. La partie front de l'application est déjà développée et gère déjà la partie authentification. Elle envoie donc avec chaque appel une clé permettant d'authentifier la source de la requête, ainsi que de valider droits de cet utilisateur.

Nous allons voir ici comment, en utilisant des annotations, nous allons pouvoir gérer les droits utilisateurs.

Présentation

Afin de suivre/appliquer cette manipulation vous aurez besoin :

  • Des notions de base de programmation,
  • D'avoir une compréhension même basique du langage de programmation C#,
  • Une machine Windows avec .Net 4.6 et un serveur IIS.

Nous allons partir d'une application simple et existante puis nous allons y ajouter les classes nécessaires à notre transformation pour finalement l'implémenter puis le tester. Vous pouvez le retrouver sur le dépôt suivant github.com/neoPix/simpleWCFApp.

JWT

Il existe plusieurs techniques d'authentifications avec HTTP. Nous allons en utiliser une à base de Token JWT passé au travers du header Authorization. JWT pour JSON Web Token est un standard (rfc 7519) de l'industrie offrant une méthode pour représenter des revendications de droits de manière sécurisée entre deux parties. Le site jwt.io offre une très belle explication du système. Une fois que vous aurez compris la procédure, vous serez libre d'implémenter votre propre technique.

Installation

Commencez par installer la version 4.6 du framework .net https://www.microsoft.com/fr-fr/download/details.aspx?id=48130.

Pour l'installation en local de l'application vous aurez besoin d'un PowerShell à jour et des autorisations pour exécuter un script. Si vous ne savez pas comment faire, vous trouverez des solutions ici : http://bit.ly/2FaubNK. Quand vous avez tout ça, lancez :

> install.ps1

Ce script, lancé en tant qu'administrateur, va activer IIS sur votre machine, le configurer avec notre application, enregistrer les services nécessaires et redémarrer IIS avec la nouvelle configuration (testé et approuvé sur windows 7 et 10).

Si vous lancez un navigateur sur http://localhost

Parcour du code

Je vous épargne la partie app qui est une simple application Vue.js qui fait du CRUD sur l'API REST en utilisant la librairie Axios. De même pour l'assembly Model dans laquelle sont définies les modèles de l'application ainsi que leurs annotations de sérialization.

La partie Service est elle aussi relativement classique, en dehors du store, qui nous permet de simuler une base de données dans le cadre de cet exemple. On note aussi la méthode authenticate qui génère un token JWT.

Finalement, la partie WebService qui, en utilisant un contexte, retourne les résultats du service en les formatants correctement et les adaptans au protocole HTTP.

Opérations

Passez sur la branche phase/02 afin de mieux suivre les opérations réalisées.

Nous commençons par ajouter la notion de niveau d'accès sur les utilisateurs, avec deux niveaux, ADMIN et USER(default).

Puis nous créons deux attributs :

  • OperationAccessAttribute qui permet de définir le niveau d'accès requis pour exécuter une méthode.
  • ServiceContractSecurityAttribute qui permet de désérialiser l'utilisateur actuel à partir du token JWT, puis, par introspection, de définir si ce dernier est capable d'exécuter la méthode demandée.

Le premier attribut est décrit comme suit :

using simpleWCFApp.Models;
using System;

namespace simpleWCFApp.WebService.Attributes
{
  public class OperationAccessAttribute : Attribute
  {
    public OperationAccessAttribute()
    {
      this.RequiredAccessLevel = ACCESS_LEVEL.PUBLIC;
    }

    public ACCESS_LEVEL RequiredAccessLevel { get; set; }
  }
}

Et s'utilise sur les opérations du web service de la manière suivante :

/* ... */
  [OperationContract]
  [OperationAccess(RequiredAccessLevel = ACCESS_LEVEL.ADMIN)]
  [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, UriTemplate = "/users")]
  public Models.User Add(Models.User user)
  {
/* ... */

Avec cette annotation j'ai défini les niveaux d'exécution nécessaires pour chaque méthode. Si le niveau de l'utilisateur n'est pas supérieur ou égale à cette valeur, je veux retourner une erreur HTTP.

Le second est décris comme suit:

/* ... */

namespace simpleWCFApp.WebService.Attributes
{
  [AttributeUsage(AttributeTargets.Class)]
  public class ServiceContractSecurityAttribute : Attribute, IParameterInspector, IServiceBehavior {
    /* ... */
  }
}

L'implémentation de IServiceBehavior, IParameterInspector nous permet de nous brancher, avant l'instanciation du webservice et de réaliser quelques pré traitements.

On commence par lire le token de la requête, on le désérialize, puis on charge l'utilisateur associé. Puis nous implémentons donc le BeforeCall dans lequel, par introspection nous récupérons le webservice concerné, ainsi que la méthode appelée. Dans cette méthode nous récupérons les attributs OperationAccessAttribute est comparons les niveaux d'authorization requis.

On implémente aussi la méthode ApplyDispatchBehavior dans laquelle on référence l'instance de notre attribut afin que le before et after soient bien appelés.

Pour le diff entre la phase 01 et 02, vous pouvez le visualiser sur cette pull request.

Recapitulatif

Nous venons en quelques lignes :

  • De créer un système de droits d'API,
    • Le système est modulaire et évolutif,
    • Son intégration est simple sur un web service existant.
  • Créer un système d'annotation simple à comprendre et utiliser
  • Un code front, gérant déjà les autorizations, n'est pas impacté.

Le code est disponible à titre informatif sur GitHub sous licence GPL.