IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Robusta Web Library : Simplifiez REST et Ajax


précédentsommairesuivant

IV. La Sécurité

IV-A. Rappels sur la sécurité à travers le Web

IV-A-1. HTTPS et le certificat

Le protocole HTTPS a pour fonction de crypter les données entre le client et le serveur. Si vous vous connectez sur https://www.mi5.co.uk/home, aucun employé de votre FAI ne pourra lire vos données.

Sauf si cet employé a intercalé un serveur s'appelant également mi5.co.uk, à l'insu du site officiel. Le certificat a donc pour objet de s'assurer que le site mi5.co.uk est bien le site officiel, et non un site fourni par un escroc.

Image non disponible
Une authentification (presque) parfaitement certifiée

Ces deux outils sont indispensables à un site sécurisé, mais ne seront cependant pas le sujet de ce chapitre : ce n'est pas à Java de s'occuper de cela. Je signalerais quand il sera simulé une requête cryptée.

IV-A-2. L'authentification BASIC

Nous avons déjà abordé la notion d'authentification, en remplissant le champ Authorization par 'James Bond' et en l'envoyant à travers une requête non cryptée. Bien que très peu de gens savent lire ou créer eux-mêmes une requête HTTP, il y a peu de chance que cette méthode satisfasse les normes du MI5.

Le protocole dit BASIC sur Internet envoie une autorisation contenant l'identifiant (le terme anglais username est moins ambigu) et le mot de passe (password est plus pratique). Si l'on envoie à travers une requête cryptée :

 
Sélectionnez
HTTPS 1.1
POST /mi5/login
Authorization: BASIC "jamesbond:howisyourblanquette"
<root>It's good</root>

Alors seuls le serveur et son administrateur sont capables de connaître le couple username:password. Vous avez surement déjà rencontré une telle demande d'identification reconnaissable par un pop-up :

Image non disponible

Il est recommandé d'utiliser des caractères spéciaux comme ' ' ou '%' dans un password, malheureusement le protocole HTTP(s) n'aime pas ces caractères. On utilise donc une fonction particulière nommée Base64 afin d'envoyer le couple ; cette fonction se trouve dans la classe robusta.utils.Codec. Codec.encodeBase64(« jamesbond:howisyourblanquette »)=amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=

On utilisera donc :

 
Sélectionnez
HTTPS 1.1
POST /mi5/login
Authorization:BASIC amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
<root>It's good</root>

Base64 se décode aussi facilement par Codec.decode64(). De plus une chaîne Base64 se remarque très facilement puisqu'elle finit souvent par un ou deux caractères '='. Il est donc EXTRÊMEMENT important d'utiliser HTTPS pour ce protocole, sinon n'importe quel hacker de votre FAI ou dans votre entreprise trouverait le mot de passe.

Le navigateur mémorise le champ Authorization, et chaque future requête vers le site web incorporera cette autorisation. En général, votre navigateur arrêtera d'envoyer ce champ à son prochain redémarrage. Il est possible que votre navigateur vous demande d'enregistrer le mot de passe, et il n'existe pas de bouton logout.

IV-A-3. Avantages et défauts de la BASIC Authentication pour une application web

Il s'agit ici de s'intéresser aux contraintes d'un site qui veut faire de l'argent en attirant des clients : design, ergonomie et sécurité sont les critères. On exclut cependant les applications hautement sensibles comme les banques, voire les emails en Saas.

Défauts

  • Le « pop-up style » est ringard, et non personnalisable.
  • Pas de log-out : si plusieurs personnes utilisent le même PC, il y a danger.
  • Il faut être sûr que HTTPS est bien utilisé, sur toutes les pages du site (ou du « realm »).
  • HTTPS consomme des ressources : faut-il l'utiliser partout ?
  • Contrairement aux cookies, le temps de sauvegarde du mot de passe par les Managers de sécurité du navigateur est illimité.

Avantages

  • C'est techniquement parfaitement sécurisé, tant qu'il y a HTTPS et qu'une seule personne utilise le PC.
  • C'est simple à implémenter.
  • Tous les navigateurs gèrent un minimum l'authentification BASIC.

Pour pallier ce défaut de la nécessité de garantir HTTPS, il existe le mode d'authentification DIGEST, mais les autres défauts perdurent.

Ces deux paragraphes montrent la nécessité de créer son propre système de login, comme le font tous les sites web. Robusta propose le mode ArmyLazy, proche du mode BASIC.

IV-B. Les contraintes ergonomiques et sécuritaires d'un site web

Le protocole ArmyLazy a été inventé pour répondre aux contraintes du site Edupassion.com. Il s'agit d'un logiciel de bulletin de notes et de QCM en ligne et doit se plier aux contraintes suivantes :

  • les élèves ont accès aux mêmes ordinateurs que les professeurs ;
  • certains élèves sont fils de professeurs dans la même école, et ont accès à la maison aux mêmes ordinateurs ;
  • il est illusoire de penser qu'un professeur cliquera toujours sur log-out ;
  • il est complètement inacceptable qu'un élève modifie les notes ;
  • il est extrêmement gênant qu'un élève voie les notes des autres élèves ;
  • il est gênant qu'un élève voie le sujet du prochain QCM ;
  • si le professeur doit rentrer son mot de passe à chaque page, il n'utilisera pas le logiciel ;
  • le professeur ne va pas laisser le logiciel de notes ouvert en pleine classe, mais peut laisser une page de présentation ou de QCM.

Il apparait logique de créer des zones ultrasécurisées, et d'autres moins sécurisées, mais moins contraignantes. Voici les zones :

  • NONE : accès direct pour les personnes non inscrites : présentation, tutoriels, etc. ;
  • LAZY-INIT : cette connexion cryptée, sous HTTPS, permet de se logguer et de créer le cookie ;
  • LAZY : on se log, puis on navigue normalement. Si on quitte le site web, on revient sans se relogguer (style forum, facebook, email) ;
  • ARMY : on doit taper le mot de passe, même si on s'est déjà loggué. Army est entièrement sous HTTPS.

La plupart des sites peuvent se passer du mode ARMY !

Par conséquent, le mot de passe ne peut être enregistré ad vitam aeternam, et doit être retapé pour passer à certaines pages. Voici quelques exemples en images.

Image non disponible
Présentation (None) vers Dashboard (Lazy) : un cookie suffit
Image non disponible
Dashboard (Lazy) vers vue des notes (Army ou Lazy ?): à priori taper le password, dépendra des retours d'utilisation
Image non disponible
Dashboard (Lazy) vers bulletin de notes (Army) : No way ! Il faut rentrer le Password
Image non disponible
Quizz (Army) vers Modification des notes (autre zone Army) : il faut rentrer Password

Le dernier exemple montre que deux zones Army se comportent comme deux « realm » différents de la Basic Authentication.

IV-C. Cryptage != Sécurité

Si HTTPS n'existait pas, les escrocs n'auraient aucun mal à pirater vos mots de passe et leur business serait juteux. Puisque HTTPS existe, le crime ne paie pas et peu d'escrocs s'amusent à renifler vos requêtes donc paradoxalement crypter ou pas un site web a finalement peu d'influence sur la sécurité.

Le crime est dans la tentation ! Comptez davantage sur votre conjoint et surtout votre collègue pour vous piéger - éventuellement des élèves dans une école. C'est donc l'accès aux mots de passe qu'il faut protéger, et on en est bien loin. Faites un tour dans l'onglet Outils -> Options -> Mots De passe -> Mots de passe enregistrés du Firefox à votre travail.

Image non disponible

Ces mots de passe sont tout autant lisibles dans les autres navigateurs s’ils ont été sauvés, et si l'on cherche un peu. Il ne faut donc jamais sauver son mot de passe d'email ou de banque sur son lieu de travail, et ne pas utiliser un mot de passe générique (c.-à-d. celui utilisé pour developpez.com, hattrick, labrute, etc. ) pour l'email, la banque ou d'autres applications web sensibles, comme Facebook.

Plus subtile est la récupération du cookie. Supposons que vous vous connectiez à Facebook au boulot, sans explicitement sauver le mot de passe. Un intrus peut copier le cookie (en 20 secondes avec une clé USB, ou par réseau) puis se connecter tranquillement sur son PC avec votre compte. Heureusement, le cookie contient rarement le mot de passe en clair.

Supposons maintenant que tous les sites web gèrent leurs cookies de la même façon, avec des utilisateurs utilisant le même mot de passe sur la plupart des sites. En récupérant un cookie de Facebook, vous pourriez vous connecter sur le compte Gmail. Le plus sûr est donc de faire intervenir une partie aléatoire dans la génération du cookie.

Dernier point, encore plus pernicieux. Vous faîtes un site web innocent jaimelesoranges.com, sauvez les mots de passe en clair, et un collègue mal intentionné a accès à la base de données. On peut coder le password en MD5, ou encore rajouter un grain de sel, ou « salt » dans la terminologie. À partir du mot de passe, vous sauvez dans la base de données MD5 (salt+password). Pour le décoder, le collègue aura besoin de l'accès à la base de données ET au code, ce qui diminue un peu les risques. Par contre, si un hacker extérieur accède à la basse de données, il n'aura pas accès au salt, et ne pourra pas décoder le mot de passe.

La seule réelle protection des utilisateurs est d'avoir un mot de passe différent pour chaque application sensible.

IV-D. Le protocole ArmyLazy

ArmyLazy est un protocole de sécurité inventé de toute pièce pour Robusta, mais fortement inspirée de BASIC Authentication. Il n'y a rien de révolutionnaire - et c'est une bonne chose. Comme c'est un protocole, ArmyLazy est indépendant de Java et donc de Robusta, bien que Robusta propose une interface et un Enum pour en faciliter l'implémentation.

Comme dit précédemment, il y a quatre zones à définir : NONE, LAZY-INIT, LAZY et ARMY. Sachant que la plupart des sites pourront se passer d'ARMY.

IV-D-1. Un peu de vocabulaire

Dans le header Authorization : LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=

  • L'ensemble « LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU= » est appelé AuthorizationValue
  • La partie codée « amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU= » est appelé Credential
  • Le prefix « LAZY-INIT » est appelé mode

IV-D-2. NONE

Il n'y a rien à faire, si ce n'est le préciser. Ça y est, c'est fait !

IV-D-3. LAZY-INIT

Par contre la partie LAZY-INIT est de loin la plus compliquée :

  • le navigateur envoie une requête HTTPS ;
  • le serveur décode le header Authorization ;
  • le serveur valide le couple username/password ;
  • le serveur crée ou update dans la base de données le lazyToken ;
  • le serveur renvoie des données ;
  • le navigateur analyse les données ;
  • le navigateur crée les cookies ;
  • le navigateur affiche la suite.
IV-D-3-a. Le navigateur envoie la requête
Image non disponible
La bonne vieille boite Login

Le formulaire peut-être un FORM avec un bouton SUBMIT, ou une série de champs : le bouton envoie une requête Ajax. Quoi qu'il en soit, la page web montrant la boite doit être sous HTTPS !

Pour une requête Ajax, un username « jamesbond » et un password « howisyourblanquette », le header Autorization sera :
Authorization : LAZY-INIT Base64(« jamesbond: »+MD5(« howisyourblanquette »))
Ce qui se code en utilisant Prototype.js et robusta.js :

 
Sélectionnez
var username=$F("username"), password=$F("password"); //$F() est une fonction de Prototype.js lisant les champs.
var credential = robusta.Codec.encodeB64( username+":"+robusta.Codec.md5(password) );
var authorizationValue = "LAZY-INIT " + credential;

new Ajax.Request("/mywebapp/myjax/login", {
            method: 'POST', //eventuellement GET, c'est pareil
            contentType :'application/xml;charset=UTF-8',
            requestHeaders:$H({Authorization:authorizationValue}),
            onSuccess: function(transport) {              
                //analyse de la requête                 
                //Création des cookies
                //affichage de la suite sur la même page ou redirection
            }
        });

Pour des raisons de licence (GPL vs BSD), robusta.js n'implémente pas encore directement de fonction MD5. Vous devez joindre ce fichier md5.js source créé par Paul Andrew Johnston : http://pajhome.org.uk/crypt/md5/

IV-D-3-b. Le travail du Serveur

Il va de soi que tout ce travail peut-être fait en PHP, ruby ou autre. L'implémentation est assez longue, donc très partiellement écrite ici, et je fournis le code source de la démo. Nous allons nous concentrer sur les bénéfices apportés par Robusta. Voici la table DbUser utilisée pour stocker les comptes.

id

username

passwordMD5

email

lazytoken

Nous allons créer un ResourceController JAX-RS généraliste, pour tout le Web Service, implémentant l'interface ArmyLazyController. Le LoginController n'aura plus qu'à étendre ce SecuredController. Nous avons aussi besoin d'un objet implémentant robusta.security.User, qui est simple, mais suffisant. Robusta propose un objet UserImpl.

Le code Java ci-dessous récupère les credentials et extrait le username et password grâce aux fonctions Codec.getUsername(b64string) et son équivalent getPassord().

 
Sélectionnez
import robusta.commons.codec.Codec;
import robusta.security.User;
import robusta.security.UserImpl;
import robusta.security.ArmyLazyController;
import robusta.security.ArmyLazy;//Plus quelques imports

class SecuredController extends JaxRsResourceController implements ArmyLazyController{

User validateLazyInitAuthorization() throws UnknownUserException, ServerException, AuthenticationException{
        ArmyLazy mode = ArmyLazy.getArmyLazyMode(authorizationValue);

        if (mode != ArmyLazy.LAZY_INIT) {
            throw new AuthenticationException("Illegal Authentication mode :" + mode);
        }

        String credentials, username, passwordMD5;
        try {
            credentials = mode.retrieveCredentials(authorizationValue);
            username = Codec.getUsername(credentials);
            passwordMD5 = Codec.getPassword(credentials); /* password should be in MD5 ; javascript is able to do it */
        } catch (Exception ex) {
            throw new AuthenticationException("Authorization Value :" + authorizationValue + " - is probably not correct");
        }

        this.user = fetchUserWithPassword(username, passwordMD5);// recherche dans la database 
        updateLazyToken(user.getId());// insertion du lazytoken  (MD5(randomString)) dans la database.        
        return user;
}

User validateLazyAuthorization() throws UnknownUserException, ServerException, AuthenticationException{}
User validateArmyAuthorization() throws UnknownUserException, ServerException, AuthenticationException{}
public String getStoredLazyToken(){}

}

Ce controller pourra être utilisé dans tous les controllers de votre application. Vous validerez la sécurité et récupérerez votre User en une ligne. Voici le LoginController que tapera la requête avec le path /mywebapp/myjax/login

 
Sélectionnez
@Path("/login")
public class LoginController extends SecuredController {

    @POST
    @Produces("application/xml")
    /**
     * The user must send something like :
     * POST /xseditor/login
     * Authorization:LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
     * @return xml with idUser, username, lazyToken and email nodes and values
     */
    public Response loginUser() {

        try {
            System.out.println("login authorization :"+getAuthorizationValue());
            User user = validateLazyInitAuthorization();
            String lazyToken = getStoredLazyToken();
            if (!StringUtilities.validateMD5(lazyToken))//check lazyToken ressemble à du MD5
            throw new ServerException("LazyToken is not correctly created");
            // magic Xml function :)
        String xml =  new VsxSax().buildVerySimpleXml("idUser", user.getId(),
            "username", user.getUsername(), "email", user.getEmail(), "lazyToken",lazyToken ); 
            return getResponse(xml);

        } catch (Exception ex) {
            MyLog.sendLog(ex, "");
            return classicFailure(ex);
        }

    }

}
IV-D-3-c. Le navigateur reçoit la requête

Le navigateur reçoit le LazyToken sous forme de MD5, comme '9386f6cde6a557f23eeafa70c6e8e9d3'. Il y a très peu de chances que cela arrive, mais théoriquement, ce token peut être en double pour deux utilisateurs différents. Le cookie envoyé pour l'identification doit donc incorporer le username, car il est marqué Unique dans la database.

Autre problème, un cookie ne peut contenir d'espace : 'LAZY xyz=' n'est pas valable. Le cookie aura pour nom : lazy et valeur : xyz=. Je conseille fortement le nom lazy, mais la classe robusta.MyRobusta permet de renommer le nom du cookie qui sera détecté par les tags. Reprenons la fonction onSuccess () de la requête Ajax :

 
Sélectionnez
         onSuccess: function(transport) {              
                //analyse de la requête : username est déjà connu
                var lazyToken = robusta.VerySimpleXml.getValue(transport, "lazyToken");
                var idUser = robusta.VerySimpleXml.getValue(transport, "idUser");
                var email = robusta.VerySimpleXml.getValue(transport, "email");
                //Création des cookies
                document.cookie="idUser="+idUser+";path=/";
                document.cookie="email="+email+";path=/";
                document.cookie="username="+username+";path=/";
                var lazyCredentials = Base64.encode(username+":"+lazyToken);
                document.cookie="lazy="+lazyCredentials+";path=/";
                    
                //affichage de la suite : dans l'exemple on affiche idUser
            }
Image non disponible

Ceci conclut le travail sur LAZY-INIT. Heureusement, le travail sur LAZY et ARMY sera quasiment identique.

IV-E. LAZY

Le mode LAZY permet de se connecter aux zones nécessitant une identification, mais en général non cryptées et non stratégiques. L'authentification se fait en principe par cookie, mais on peut également utiliser le header Authorization ce qui est pratique pour les tests unitaires.

IV-E-1. Tag dans une page JSP

Robusta fournit un tag <robusta:authentication/> qui permet de valider le cookie (ou le header Authorization), et fournit ensuite à la page l'attribut ${user} implémentant l'interface robusta.security.User. Selon votre implémentation, vous aurez accès à ce que vous voudrez de cet utilisateur. Vous avez également accès à ${authorizationValue}.

Voici un exemple :

 
Sélectionnez
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.robustaweb.com" prefix="robusta" %>

<jsp:useBean id="controller" class="demo.myjax.SecuredController" scope="request" />
<robusta:authentication controller="${controller}" />

<html><body>
User Id : ${user.id}<br/>
Email : ${user.email}
</body></html>

Notre page JSP utilise comme bean un JaxRSResourceController, sans utiliser les capacités liées à @Context. Cela montre la versatilité de la spécification JAX-RS. Notez aussi que JspResourceContoller aurait très bien pu être utilisé, en y implémentant l'interface ArmyLazyController.

IV-E-2. Fonctionnement interne

Le fonctionnement est simple :

  • la requête arrive sur la page JSP avec un cookie lazy ;
  • un javabean SecuredController est créé ;
  • le tag <robusta:authentication/> signale l'authorizationValue (la valeur du cookie) au controller ;
  • le tag appelle la fonction validateLazyAuthentication() du controller ;
  • le tag crée l'attribut ${user}.

Notons d'une part que l'implémentation de validateLazyAuthentication() est très proche de celle du mode LAZY-INIT, et d'autre part que rien dans la page jsp ne gère automatiquement une exception, notamment en cas d'absence de cookie.

IV-E-3. Résultat

Voici le résultat lors de l'appel d'une page nécessitant une identification par cookie.

Image non disponible

IV-E-4. Détection dans un Service Web

Nous avons utilisé le tag <robusta:authentication/>, mais JAX-RS a accès aux données de la requête (HttpServletRequest) grâce à l'utilisation de l'annotation @Context. JaxRsResourceController utilise ce système pour détecter le cookie et le header de la requête. Si votre ResourceController hérite de JaxRsResourceController, vous n'avez plus qu'à utiliser getAuthorizationValue().

IV-F. ARMY

Après avoir sécurisé votre domaine par HTTPS, le mode ARMY est extrêmement simple à implémenter une fois les autres modes assimilés. Le problème est de récupérer les valeurs du username et du password. En effet, une page web ne gère aucun état, excepté le cookie qui n'est pas sécurisé.

La solution consiste donc à passer par une page de Login, avec la fameuse boite demandant le couple username/pasword. Puis on fait la suite par Ajax. Ajax est en effet ici la solution de tous les problèmes d'état, car l'application peut se comporter comme une application Desktop.

Autrement, passer d'une page (ou URI) à une autre n'est pas aisé. Est-ce vraiment nécessaire ? Posez-vous bien la question, car cela augmente aussi les risques de faille de sécurité. Si votre réponse est affirmative, et si les exigences en ergonomie ne permettent pas de redemander le password, il existe heureusement des solutions. Vous pouvez faire passer la forme MD5 du password en paramètre de requête dans l'URL - c'est crypté ! Plus discret, il est possible d'utiliser le header « WWW-Authenticate » de la réponse pour faire passer ce password ; c'est ce qu'utilise l'authentification DIGEST.

IV-G. Conclusions

Il existe bien d'autres solutions de sécurité : JAAS, Acegi (Spring Security), etc. Le protocole ArmyLazy ne repose pas sur Java, et peut être aussi bien utilisé en PHP qu'en Ruby. Par extension, il n'y a pas non plus de dépendance à un serveur particulier. Cependant il vous faudra implémenter l'ensemble, car Robusta ne fournit qu'une interface et un Enum.

ArmyLazy ne fonctionne pas sur le principe d'Inversion de Controle, ni d'Interceptor. Côté serveur, cela se passe dans le code, et l'interface ArmyLazyController fournit à chaque fois un objet User, tout de suite réutilisable. Vous pouvez cependant utiliser le protocole ArmyLazy et vous passer sans difficulté de ce Controller, ou écrire le vôtre.

De plus, ArmyLazy ne protège pas votre application contre tout type d'injection de code. La classe robusta.utils.StringUtilities propose quelques fonctions efficaces, mais vous devrez pointer à chaque requête quelles sont les dangers potentiels, ainsi que définir le type de données acceptables.

Enfin, ne vous reposez pas sur la technologie. Si votre utilisateur ne voit pas de données sensibles à l'écran ou s'il ne s'est pas loggué le matin, il sera moins vigilant et un intrus pourra le surprendre. Ergonomie = Sécurité.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2009 Nicolas Zozol. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.