Sécurisez vos applications Angular avec Spring Security et HMAC

Dans cet article nous allons voir comment implémenter HMAC sur une communication REST. L’architecture applicative proposée est constituée d’un frontend développé en Angular 2 et d’un backend Spring et Spring Security.

L’environnement technique

Stack

Les composants techniques sont les suivants :

Partie serveur:

  • Spring 4.3.9
  • Spring Boot 1.5.4
  • Spring Security 4.2.3
  • Base de données H2 (in-memory)

Partie cliente:

  • Angularv 4.1.2
  • HTML 5 et CSS 3
  • Librairie JavaScript CryptoJS pour l’encodage.

La communication est faite via des webservices REST entre la partie front office (AngularJS) et la partie serveur (Spring) notamment grâce à Spring MVC.  

Sources

Vous pouvez récuperer les sources du projet d’exemple sur notre github : Angular Spring HMAC

Installation

  • Pour lancer le serveur : mvn spring-boot:run  puis ouvrir un navigateur à l’adresse : http://localhost:8080. La commande npm install est faite automatiquement au lancement du serveur.
  • Les différents comptes utilisateurs sont dans le fichier README.md

Définition

MAC

MAC, de l’anglais  message authentication code, est un code accompagnant les données permettant de vérifier que ces dernières n’ont pas été modifiées pendant la transmission. Le principe repose sur la génération d’un bloc authentificateur à l’aide d’un algorithme prédéfini et d’une clé secrète.

HMAC

HMAC, de l’anglais keyed-hash message authentication code, repose sur le principe d’un MAC mais ajoute le chiffrement des données à partir d’un algorithme de hashage de type SHA, MD5, etc… toujours en combinaison avec une clé secrète.

Intérêt

HMAC permet d’éviter les attaques de type « man in the middle » où un intrus se place entre l’émetteur et le récepteur. Il se fait alors passer pour le récepteur auprès de l’émetteur tout en continuant à transmettre les données de manière transparente. Il peut alors potentiellement lire et/ou modifier les données et tromper alors l’emetteur. La vérification des données par le récepteur assure alors que ces données n’ont pas été altérées. En effet, l’intrus ne connaissant pas la clé secrète ni l’algorithme utilisé pour produire la clé MAC il lui est impossible d’altérer les données. 

HMAC permet donc de s’assurer de l’intégrité des données et de l’authenticité de l’emetteur. Le niveau de sécurité et d’efficacité d’HMAC est exponentielle avec la longeur de la clé secrète et la force de l’algorithme de hashage utilisé. Par exemple, une clé secrète « redfroggy » et un chiffrement MD5 sera moins efficace qu’une clé comme « 03742b9c204cf3b31b3f2a20f55e5ca9ddd7ef1f15e3297c56d5b9d8a3c6d0d3 » et d’un chiffrement SHA-256.

Une fois le chiffrement effectué, on parle alors d’algorithmes HMAC-SHA256, HMAC-SHA1, HMAC-MD5, etc …

Il est important de rappeler que la mise en place de HMAC n’a de sens que si la communication entre le client et le serveur est chiffrée notamment via https.

Principe

diagram-hmac

Comme on peut le voir sur le schéma ci dessus, le principe est simple : une requête est envoyée du client au serveur et l’on souhaite vérifier que son contenu n’a pas été altéré.

Coté client

  • Le client va constituer un message (basé sur un algorithme partagé entre le client et le serveur) puis l’encoder en utilisant un algorithme de hashage (SHA-256, MD5, etc..) et une clé secrète partagée.
  • La donné finale est envoyée au serveur (par exemple dans un header HTTP) : C’est la clé A sur notre schéma.

Coté serveur

  • Le serveur applique le même algorithme que le client pour constituer son message, puis applique la même fonction de hashage en utilisant la clé secrète partagée. Il en ressort la clé B.
  • Le serveur va alors comparer la clé B avec la clé A : Si elles sont identiques alors la requête est valide et le message n’a pas été altéré.

Json Web Token

L’implémentation de HMAC que nous avons mis en place et qui est proposée dans cet article fait appel à une autre notion : Le Json Web Token (JWT).

Ils permettent de mettre facilement et de manière securisée une authentification sur des applications de type stateless c’est à dire qui ne stocke aucune session coté serveur. C’est ce qu’on appelle une authentification basée sur un token (Token based authentication)

Le web token est généralement un hash transmis au moment de l’authentification et qui va transiter dans toutes les requêtes entre le client et le serveur. Il est préférable de générer un JWT à chaque requête pour éviter les attaques de type CSRF, c’est à dire ou un intrus utilise le token client pour dialoguer avec le serveur. Si le token change à chaque requête cela rend sa tâche beaucoup plus difficile.

Comme exemple on peu cité le SSO (Single Sign On) qui repose sur le principe du JWT. La grande force d’un JWT est qu’il n’est stocké nulle part sur le serveur mais simplement échangé avec le client (seul le secret doit être stocké ou défini en fichier de configuration coté serveur). Il convient donc particulièrement bien pour les applications de type stateless.

 

Dans l’exemple que nous verrons par la suite, l’authentification repose sur un JWT, et le processus peut être schématisé de la façon suivante :

spring_hmac_auth

Le processus est le suivant:

  • Le client fait une requête d’authentification
  • Le serveur authentifie l’utilisateur sur un tiers (base de données ou ldap par exemple)
  • En cas de succès, le serveur génère un secret (basé sur un UUID) qu’il stocke en cache ou utilise un secret qui peut être défini en fichier de configuration ce qui est préférable car persister un secret coté serveur reviendrait à définir une session.
  • Le serveur génère ensuite un JWT qui sera envoyé au client en réponse. Il contient notamment l’identifiant de l’utilisateur fraîchement authentifié ce qui nous permettra à chaque requête de pouvoir authentifié auprès de Spring Security l’utilisateur.
  • Le JWT est renvoyé au client sous forme d’un cookie avec la propriété httpOnly = true  (seul le navigateur à accès au cookie) ce qui empêche la lecture du cookie notamment via JavaScript.
  • A aucun moment le secret qui permet de signer le JWT est envoyé au client, seul celui utilisé pour la signature des requêtes HMAC est stocké et utilisé par le client.

Les informations sont échangées grâce aux headers HTTP car c’est un moyen efficace de transmettre de la donnée sans altérer le contenu du message de la requête HTTP. Les headers HTTP utilisés sont les suivants:

  • WWW-Authenticate : Contient la méthode de chiffrement à utiliser pour chiffre les prochaines requêtes (Ex: HMACSHA256, HMACSHA512, etc)
  • X-Secret : Contient la clé secrète à utiliser pour signer les prochaines requêtes HMAC.

En plus de ces headers, un cookie est renvoyé au client à l’authentification et contient le JWT généré permettant d’identifier un utilisateur pour une requête HTTP.

Dans notre stack d’exemple, l’authentification est réalisée grâce à Spring Security. Pour plus d’informations voir l’article sur l’architecture Spring Security.

 

JWT, cookies et attaques CSRF

L’avantage de stocker le JWT en cookie réside dans le fait que sa transmission est transparente (tant que les requêtes se font bien sur le même domaine) et qu’il est possible de limiter l’exposition du cookie (avec les propriétés httpOnly ou secured) et donc de rendre l’information (dans notre cas le JWT) moins vulnérable:

  • Un cookie marqué comme httpOnly n’est accessible que par le navigateur et pas par JavaScript
  • Un cookie marqué comme secured sera stocké par le navigateur uniquement si le client utilise le protocole https.

Le revers de la médaille est qu’un cookie est vulnérable à une attaque de type CSRF où un script malicieux réutiliserait le cookie pour modifier de la donnée coté serveur. La documentation Spring explique assez bien ce principe.

Avec Spring Security, il est possible de générer un token pour chaque client qui sera stocké en cookie et transmis en header http. Si le header ne correspond pas au cookie une erreur est retournée par le serveur. Voir SecurityConfiguration.

 

Code Time !

Pour récuperer les sources du projet, faite un git clone  sur notre projet Angular Spring HMAC. Il s’agit d’une simple application Angular/Spring qui se contente d’afficher une liste d’utilisateurs après une authentification obligatoire. La liste des utilisateurs est crée au lancement du serveur dans une base de données H2.

L’authentification

Coté serveur

On déclare tout d’abord une api rest pour l’authentification grâce à Spring MVC :

Le webservice est de type POST et les credentials sont représentés par l’objet LoginDTO qui contient un identifiant et un mot de passe.

La logique de l’authentification est encapsulée dans un service AuthenticationService :

Dans un premier temps on réalise l’authentification via Spring Security:

  • L’instance de l’authenticationManager est récupérée grâce à un @Autowired et permet d’appeler la méthode authenticate qui déclence les mécanismes d’authentification de Spring Security.
  • On place ensuite le résultat dans le context Spring Security et on charge les données de l’utilisateur par son identifiant grâce a la classe UserDetailsService.
  • Pour bien comprendre les mécanismes d’authenfication de Spring Security, je vous renvoie vers cet article.
  • On génère ensuite un secret grâce à la classe utilitaire HmacSigner :

  • On génère un JWT toujours grâce à la classe HmacSigner :

La classe HmacToken regroupe un ensemble d’élément nécessaire à la composition du JsonWebToken mais surtout le JWT lui même.

  • On crée le cookie qui contient le JWT généré:

  • Enfin, on retourne les informations nécessaires dans les headers HTTP:

 

 

Coté client

Le service LoginService se charge de l’appel au backend

 

C’est le service LoginService, au retour de l’authentification qui va lire les headers HTTP. Les données sont stockées en local storage.

 

Coté http, voici un exemple de headers retournés lors se l’authentification:

 

 

Signature des requêtes api (hors authentification)

flux_hmac_request

  • Le client génère un message en concaténant l’url appelée, la méthode HTTP et la date de la requête. message = url + method + date
  • Il génère ensuite un digest avec un encodage HMAC à partir du secret (récupéré du fichier de configuration) et du message. digest = HMACSHA256(secret,message)
  • Le JWT est automatiquement transmis car stocké en cookie.
  • Le digest est placé dans un header HTTP X-Digest
  • La date utilisée pour générer le message est placée dans un header HTTP X-Once
  • Le serveur vérifie ensuite la signature du JWT  à l’aide du secret présent dans le fichier de configuration application.yml.
  • Si le JWT est conforme, le serveur génère un message basé sur l’url, la méthode http et la date stockée dans le header X-Once
  • Il génère ensuite un digest HMAC à partir du message et du secret.
  • Si le digest serveur est strictement égal au digest client stocké dans le header X-Digest alors la requête est valide

Coté client

Angular nous permet, grâce aux intercepteurs http, d’appliquer une logique de manière globale pour toutes les requêtes http qui seront appelées. Les intercepteurs angular sont déclenchés pour tout types de requêtes HTTP : url api, ressource html, etc… Il nous faut donc filter les requêtes pour ne prendre que celle qui correspondent aux apis de notre backend.

 On utilise une surcharge de la classe de base Http: HmacHttpClient 

 La classe HmacHttpClient fait appel à la classe UrlMatcher pour déterminer quels apis sont valables.

Ainsi, seules les requêtes comprenant « /api/** » et qui ne contiennent pas « /api/authenticate » vont déclencher notre logique présente dans l’intercepteur.

 

 

  • Si les urls correspondent, on génère tout d’abord un message
  • On récupère le secret que l’on décode de la base 64
  • On génère le message

 

  • On génère le digest à partir du secret et du message. L’algorithme de hashage utilisé va dépendre du mode d’encodage renvoyé par le serveur. 
    • La librairie JavaScript CryptoJS est utilisé pour l’encodage HMAC.

 

 

  • On place les informations pertinentes dans les différents headers HTTP

 

Coté http, voici un exemple de headers envoyés par le client: 

Côté serveur

Pour le backend, l’idée est de vérifier la validité de la requête HMAC avant que Spring Security effectue l’authentification.

Pour cela les filtres Spring Security vont nous permettre de répondre au besoin. Ce sont les classes XAuthTokenFilterHmacSecurityFilter et SecurityService qui vont jouer ce rôle de vérification dans notre cas. Les filtres XAuthTokenFilter et HmacSecurityFilter sont exécutés dans cet ordre par Spring Security.

Le filtre HmacSecurityFilter  est générique et peut donc être repris tel quel avec tout le package hmac security

Vérification de l’authentification

Dans le filtre XAuthTokenFilter on vérifie que la requête courant possède bien un cookie contenant un JWT et que ce dernier est valide. Si ce n’est pas le cas une erreur 401 est retournée.

  • On vérifié la présence du cookie, la validité du JWT et en cas de succès au authentifie l’utilisateur courant auprès de Spring Security :

 

Dans le filtre  HmacSecurityFilter on vérifie que la requête est signée correctement et qu’elle n’a pas été altérée:

  • On applique la vérification uniquement pour les requêtes http dont l’url contiennent « /api/** » et ne contiennent pas « /api/authenticate ». C’est le même comportement que notre intercepteur angular. 

  • La classe  SecurityService est appelée pour faire les vérifications  où on vérifie que le JWT (envoyé par cookie) est valide :

  • On génère le message en fonction du type de requête

  • On génère le digest

  • Les digest sont comparés et s’il sont différents, la requête est interrompue et une erreur 403 est retournée.

  • En cas d’erreur, un code HTTP 403 « Unauthorized » est renvoyée au client.