Développer son application via des API c’est extremement pratique et évolutif, certes, mais cela rend plus complexe la sécurisation des applications sous-jacentes. Le Framework JOY est maintenant opérationel pour faire du CRUD … il me reste maintenant à mettre en place les mécanismes de sécurité adéquates. Alors évidemment, je me suis renseigné sur les mécanismes oAuth 1 et 2 qui sont les plus utilisés, même si très honnetement ils ont plus été conçus (surtout la v2) pour des applications B2C (via l’utilisation de réseaux sociaux).
Insipiré de tout celà j’ai décidé d’implémenter mon propre mécanique directement dansle framework serveur mais aussi de fournir le framework client (Javascript) qui fonctionne avec.
Index
Les principes d’authentification
Quant on développait des application web à la mode JSP on utilisait des sessions qui en fait utilisaient la plupart du temps des token (transmis bien souvent via des cookies). Ces token étaient gérés par J2EE ce qui fait qu’on ne s’en occupait guère … on se contentait de récupérer l’objet session et de travailler avec. Ce temps là est bien révolu et nous alllons en quelque sorte devoir re produire ce type de mécanisme dans notre API.
Tout d’abord nous utiliserons l’entête HTTP « Authentication », c’est à cet endroit en effet qu’il est prévu d’y envoyer le token de session:
String headerAuth = _request.getHeader("Authorization");
A chaque appel d’API le framework récupérera ce token pour le contrôler.
Mais celà veut dire que l’application doit maintenir ce token pendant son utilisation. Et bien sur, la solution la plus simple pour maintenir une donnée coté client est l’utilisation de cookies. Nous créerons donc coté framework client la possibilité de naviguer à travers les écrans en affectant et récupérant les cookies.
Le token
voici à quoi ressemble le token de session dans le framework Joy :
Ce dernier est composé de deux parties :
- La première contient la clé publique (non cryptée)
- La seconde contient la clé publique + une date & heure. Ces deux données étant cryptées via une clé privée que seule l’application connait.
On utilise le carractère | comme délimiteur.
Pourquoi une date en plus me direz-vous ?
Pour deux raisons en fait. La première est pour éviter que le cryptage de la clé publique soit toujours le même et ainsi complexifier un piratage de type « man in the middle ». La seconde raison est de rajouter un autre controle (autre que celui de vérification des clés publiques) de type vérification timeout. Selon la configuration de l’application appelante on pourra considérer facilement que si le token n’a pas été utilisé pendant 10 minutes, un login est de nouveau nécéssaire.
Attention, car rajouter ce controle de timeout implique aussi de recalculer le token à chaque appel !
Coté framework serveur
2 cas de figures sont à envisager :
- La phase de login elle même qui permet la création du token
- Les phases d’appels d’API dans lesquelles on vérifiera la validité du token
Le login
Le login sera donc effectué dans Joy via un nouveau filtre java http : FilterAuthenticate.
Pendant cette phase de login, le controle login/mot de passe sera bien entendu effectué et un token sera calculé et renvoyé automatiquement. Afin de prendre en compte les spécificités de l’application le filtre doit être hérité de la sorte :
public class dgmAuthFilter extends FilterAuthenticate { @Override protected String getPublicKey(JoyApiRequest request) { return request.getParameter(C.REQ_PARAM_USER_TAG).getValue(); } @Override protected boolean checkLogin(JoyApiRequest request) { return (request.getParameter(C.REQ_PARAM_USER_TAG).getValue() != null && request.getParameter(C.REQ_PARAM_PWD_TAG).getValue() != null); } }
Les appels d’API
Le framework Joy contrôle ensuite automatiquement les appels d’API dans le filtre http : FilterCommon.
La méthode checkToken peut d’ailleurs être surchargée (facultatif) pour changer le contrôle :
protected boolean checkToken(JoyState state) { try { JoyAuthToken myCookie = new JoyAuthToken(); myCookie.setPrivateKey(state.getAppParameters().getAuthPrivateKey()); myCookie.setTimeoutInMinutes(state.getAppParameters().getSessionItemout()); return myCookie.checkAuthCookie(state.getHttpAuthToken()); } catch (Exception ex) { this.getLog().log(Level.WARNING, "checkToken|Exception> {0}", ex.toString()); return false; } }
Pour faire simple le framework fournit une classe JoyAuthToken qui gère le token entièrement (avec l’encryption).
L’encryption
La classe JoyAuthToken fournit 3 méthodes d’encryptage/décryptage que j’ai d’ailleurs récupéré sur internet :
private Key generateKey() throws Exception { byte[] keyValue = this.privateKey.getBytes("UTF-8"); MessageDigest sha = MessageDigest.getInstance("SHA-1"); keyValue = sha.digest(keyValue); keyValue = Arrays.copyOf(keyValue, 16); // use only first 128 bit Key key = new SecretKeySpec(keyValue, C.AUTH_ALGO); return key; } protected String encrypt(String Data) throws Exception { Key key = generateKey(); Cipher c = Cipher.getInstance(C.AUTH_ALGO); c.init(Cipher.ENCRYPT_MODE, key); byte[] encVal = c.doFinal(Data.getBytes()); String encryptedValue = DatatypeConverter.printBase64Binary(encVal); return encryptedValue; } protected String decrypt(String encryptedData) throws Exception { Key key = generateKey(); Cipher c = Cipher.getInstance(C.AUTH_ALGO); c.init(Cipher.DECRYPT_MODE, key); byte[] decordedValue = DatatypeConverter.parseBase64Binary(encryptedData); byte[] decValue = c.doFinal(decordedValue); String decryptedValue = new String(decValue); return decryptedValue; }
Cette classe se base sur deux parametres qu’il faut modifier dans la configuration du framework :
- La clé privée : fichier joy-parameters.xml (tag <auth-private-key>)
- Le timeout : fichier joy-parameters.xml (tag <joy-session-timeout>)
Coté framework client
Coté client il faut que le framework puisse gérer :
- La création/suppression de cookies
- L’ajout du token lors d’appels Ajax
Pour le login
Voici un exemple de login avec appel de la classe d’authentification et retour et affectation du token à un cookie :
function cb_login_return(content) { if(content.status == 1) { $("#errormessages").hide(); $$.setToken(content); $$.navigate("home"); } else { $("#errormessages").show(); $("#errormessages").html("Login failed, please retry"); } } function login() { var postParams = { "user" : $("#joyuser").val(), "password" : $("#joypassword").val() }; $$.ajax("POST", cb_login_return, $$.getLOGINCall(), postParams); }
Dans cet exemple la page de login a deux INPUT (joyuser et joypassword). L’appel de la fonction setToken() permet elle de créer le cookie avec le token dedans.
Pour la navigation
Pour les appels Ajax
J’ai du modifier quelque peu la méthode d’appel de fonction ajax :
this.ajax = function(m, cb, url, valObj) { // Get the session cookie var myCookie = this.getToken(); if (myCookie == null) { // NO SESSION ... } ... myxhr.onreadystatechange = function() { if(myxhr.readyState==4){ switch(myxhr.status) { case 200: cb(eval("(" + myxhr.responseText + ")")); break; case 403, 404, 503 : cb(null); break; case 401: JOY.navigate("login"); break; // Redirect login page default: cb(null); } } } };
Il fallait en effet rajouter deux choses :
- La récupération du token (cookie) et le placer automatiquement dans l’entête HTTP (Authentication)
- L’erreur de vérification du token et la redirection automatique (erreur 401).
Les cookies
J’ai aussi récupéré sur internet deux petites fonctions javascript pour gérer simplement les cookies :
this.setCookie = function(nom, valeur, jours) { if (jours) { var date = new Date(); date.setTime(date.getTime()+(jours*24*60*60*1000)); var expire = "; expire="+date.toGMTString(); } // Aucune valeur de jours spécifiée else var expire = ""; document.cookie = nom+"="+valeur+expire+"; path=/"; } this.getCookie = function(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } this.delCookie = function(nom) { this.setCookie(nom, "", -1); }
Et voilà le framework (client + server) est totalement opérationnel 🙂
Récupérer le framework ici
Ingénieur en informatique avec plus de 20 ans d’expérience dans la gestion et l’utilisation de données, Benoit CAYLA a mis son expertise au profit de projets très variés tels que l’intégration, la gouvernance, l’analyse, l’IA, la mise en place de MDM ou de solution PIM pour le compte de diverses entreprises spécialisées dans la donnée (dont IBM, Informatica et Tableau). Ces riches expériences l’ont naturellement conduit à intervenir dans des projets de plus grande envergure autour de la gestion et de la valorisation des données, et ce principalement dans des secteurs d’activités tels que l’industrie, la grande distribution, l’assurance et la finance. Également, passionné d’IA (Machine Learning, NLP et Deep Learning), l’auteur a rejoint Blue Prism en 2019 et travaille aujourd’hui en tant qu’expert data/IA et processus. Son sens pédagogique ainsi que son expertise l’ont aussi amené à animer un blog en français (datacorner.fr) ayant pour but de montrer comment comprendre, analyser et utiliser ses données le plus simplement possible.