Formulaires : le b.a.-ba

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Dans cette partie, nous allons littéralement faire table rase. Laissons tomber nos précédents exemples, et attaquons l'étude des formulaires par quelque chose de plus concret : un formulaire d'inscription. Création, mise en place, récupération des données, affichage et vérifications nous attendent !

Bien entendu, nous n'allons pas pouvoir réaliser un vrai système d'inscription de A à Z : il nous manque encore pour cela la gestion des données, que nous n'allons découvrir que dans la prochaine partie de ce cours. Toutefois, nous pouvons d'ores et déjà réaliser proprement tout ce qui concerne les aspects vue, contrôle et traitement d'un tel système. Allons-y !

Mise en place

Je vous propose de mettre en place une base sérieuse qui nous servira d'exemple tout au long de cette partie du cours, ainsi que dans la partie suivante. Plutôt que de travailler une énième fois sur un embryon de page sans intérêt, je vais tenter ici de vous placer dans un contexte plus proche du monde professionnel : nous allons travailler de manière propre et organisée, et à la fin de ce cours nous aurons produit un exemple utilisable dans une application réelle.

Pour commencer, je vous demande de créer un nouveau projet dynamique sous Eclipse. Laissez tomber le bac à sable que nous avions nommé test, et repartez de zéro : cela aura le double avantage de vous permettre de construire quelque chose de propre, et de vous faire pratiquer l'étape de mise en place d'un projet (création, build-path, bibliothèques, etc.). Je vous propose de nommer ce nouveau projet pro. N'oubliez pas les changements à effectuer sur le build-path et le serveur de déploiement, et pensez à ajouter le .jar de la JSTL à notre projet, ainsi que de créer deux packages vides qui accueilleront par la suite nos servlets et beans.

Revenons maintenant à notre base. En apparence, elle consistera en une simple page web contenant un formulaire destiné à l'inscription du visiteur sur le site. Ce formulaire proposera :

  • un champ texte recueillant l'adresse mail de l'utilisateur ;
  • un champ texte recueillant son mot de passe ;
  • un champ texte recueillant la confirmation de son mot de passe ;
  • un champ texte recueillant son nom d'utilisateur (optionnel).

Voici à la figure suivante un aperçu du design que je vous propose de mettre en place.

Formulaire d'inscription

Si vous avez été assidus lors de vos premiers pas, vous devez vous souvenir que, dorénavant, nous placerons toujours nos pages JSP sous le répertoire /WEB-INF de l'application, et qu'à chaque JSP créée nous associerons une servlet. Je vous ai ainsi préparé une page JSP chargée de l'affichage du formulaire d'inscription, une feuille CSS pour sa mise en forme et une servlet pour l'accompagner.

JSP & CSS

Voici le code HTML de base du formulaire d'inscription que nous allons utiliser tout au long de cette partie :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Inscription</title>
        <link type="text/css" rel="stylesheet" href="form.css" />
    </head>
    <body>
        <form method="post" action="inscription">
            <fieldset>
                <legend>Inscription</legend>
                <p>Vous pouvez vous inscrire via ce formulaire.</p>

                <label for="email">Adresse email <span class="requis">*</span></label>
                <input type="text" id="email" name="email" value="" size="20" maxlength="60" />
                <br />

                <label for="motdepasse">Mot de passe <span class="requis">*</span></label>
                <input type="password" id="motdepasse" name="motdepasse" value="" size="20" maxlength="20" />
                <br />

                <label for="confirmation">Confirmation du mot de passe <span class="requis">*</span></label>
                <input type="password" id="confirmation" name="confirmation" value="" size="20" maxlength="20" />
                <br />

                <label for="nom">Nom d'utilisateur</label>
                <input type="text" id="nom" name="nom" value="" size="20" maxlength="20" />
                <br />

                <input type="submit" value="Inscription" class="sansLabel" />
                <br />
            </fieldset>
        </form>
    </body>
</html>

/WEB-INF/inscription.jsp

Et voici le code de la feuille de style CSS accompagnant ce formulaire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* Général ------------------------------------------------------------------------------------- */

body, p, legend, label, input {
    font: normal 8pt verdana, helvetica, sans-serif;
}

fieldset {
    padding: 10px;
    border: 1px #0568CD solid;
}

legend {
    font-weight: bold;
    color: #0568CD;
}

/* Forms --------------------------------------------------------------------------------------- */

form label {
    float: left;
    width: 200px;
    margin: 3px 0px 0px 0px;
}

form input {
    margin: 3px 3px 0px 0px;
    border: 1px #999 solid;
}

form input.sansLabel {
    margin-left: 200px;
}

form .requis {
    color: #c00;
}

/form.css

Contrairement à la page JSP, la feuille de style ne doit pas être placée sous le répertoire /WEB-INF ! Eh oui, vous devez vous souvenir que ce répertoire a la particularité de rendre invisible ce qu'il contient pour l'extérieur :

  • dans le cas d'une page JSP c'est pratique, cela rend les pages inaccessibles directement depuis leur URL et nous permet de forcer le passage par une servlet ;
  • dans le cas de notre feuille CSS par contre, c'est une autre histoire ! Car ce que vous ne savez peut-être pas encore, c'est qu'en réalité lorsque vous accédez à une page web sur laquelle est attachée une feuille de style, votre navigateur va, dans les coulisses, envoyer une requête GET au serveur pour récupérer silencieusement cette feuille, en se basant sur l'URL précisée dans la balise <link href="..." />. Et donc fatalement, si vous placez le fichier sous /WEB-INF, la requête va échouer, puisque le fichier sera caché du public et ne sera pas accessible par une URL.

De toute manière, dans la très grande majorité des cas, le contenu d'une feuille CSS est fixe ; il ne dépend pas de codes dynamiques et ne nécessite pas de prétraitements depuis une servlet comme nous le faisons jusqu'à présent pour nos pages JSP. Nous pouvons donc rendre les fichiers CSS accessibles directement aux navigateurs en les plaçant dans un répertoire public de l'application.

En l'occurrence, ici j'ai placé cette feuille directement à la racine de notre application, désignée par le répertoire WebContent dans Eclipse.

Retenez donc bien que tous les éléments fixes utilisés par vos pages JSP, comme les feuilles de style CSS, les feuilles de scripts Javascript ou encore les images, doivent être placés dans un répertoire public, et pas sous /WEB-INF.

Avant de mettre en place la servlet, penchons-nous un instant sur les deux attributs de la balise <form>.

La méthode

Il est possible d'envoyer les données d'un formulaire par deux méthodes différentes :

  • get : les données transiteront par l'URL via des paramètres dans une requête HTTP GET. Je vous l'ai déjà expliqué, en raison des limitations de la taille d'une URL, cette méthode est peu utilisée pour l'envoi de données.
  • post : les données ne transiteront pas par l'URL mais dans le corps d'une requête HTTP POST, l'utilisateur ne les verra donc pas dans la barre d'adresses de son navigateur.

Malgré leur invisibilité apparente, les données envoyées via la méthode POST restent aisément accessibles, et ne sont donc pas plus sécurisées qu'avec la méthode GET : nous devrons donc toujours vérifier la présence et la validité des paramètres avant de les utiliser. La règle d'or à suivre lorsqu'on développe une application web, c'est de ne jamais faire confiance à l'utilisateur.

Voilà pourquoi nous utiliserons la plupart du temps la méthode POST pour envoyer les données de nos formulaires. En l'occurrence, nous avons bien précisé <form method="post" ... > dans le code de notre formulaire.

La cible

L'attribut action de la balise <form> permet de définir la page à laquelle seront envoyées les données du formulaire. Puisque nous suivons le modèle MVC, vous devez savoir que l'étape suivant l'envoi de données par l'utilisateur est le contrôle. Autrement dit, direction la servlet ! C'est l'URL permettant de joindre cette servlet, c'est-à-dire l'URL que vous allez spécifier dans le fichier web.xml, qui doit être précisée dans le champ action du formulaire. En l'occurrence, nous avons précisé <form ... action="inscription"> dans le code du formulaire, nous devrons donc associer l'URL /inscription à notre servlet dans le mapping du fichier web.xml.

Lançons-nous maintenant, et créons une servlet qui s'occupera de récupérer les données envoyées et de les valider.

La servlet

Voici le code de la servlet accompagnant la JSP qui affiche le formulaire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.sdzee.servlets;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Inscription extends HttpServlet {
    public static final String VUE = "/WEB-INF/inscription.jsp";

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException{
        /* Affichage de la page d'inscription */
        this.getServletContext().getRequestDispatcher( VUE ).forward( request, response );
    }
}

com.sdzee.servlets.Inscription

Pour le moment, elle se contente d'afficher notre page JSP à l'utilisateur lorsqu'elle reçoit une requête GET de sa part. Bientôt, elle sera également capable de gérer la réception d'une requête POST, lorsque l'utilisateur enverra les données de son formulaire !

Avant d'attaquer le traitement des données, voici enfin la configuration de notre servlet dans le fichier web.xml :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>Inscription</servlet-name>
        <servlet-class>com.sdzee.servlets.Inscription</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Inscription</servlet-name>
        <url-pattern>/inscription</url-pattern>
    </servlet-mapping>
</web-app>

/WEB-INF/web.xml

Souvenez-vous : l'adresse contenue dans le champ <url-pattern> est relative au contexte de l'application. Puisque nous avons nommé le contexte de notre projet pro, pour accéder à la JSP affichant le formulaire d'inscription il faut appeler l'URL suivante :

1
http://localhost:8080/pro/inscription

Vous devez, si tout se passe bien, visualiser le formulaire indiqué à la figure suivante dans votre navigateur.

Formulaire d'inscription

Et voici sous forme d'un schéma ce que nous venons de réaliser (voir la figure suivante).

L'envoi des données

Maintenant que nous avons accès à notre page d'inscription, nous pouvons saisir des données dans le formulaire et les envoyer au serveur. Remplissez les champs du formulaire avec un nom d'utilisateur, un mot de passe et une adresse mail de votre choix, puis cliquez sur le bouton d'inscription. Voici à la figure suivante la page que vous obtenez.

Code d'erreur HTTP 405

Eh oui, nous avons demandé un envoi des données du formulaire par la méthode POST, mais nous n'avons pas surchargé la méthode doPost() dans notre servlet, nous avons uniquement écrit une méthode doGet(). Par conséquent, notre servlet n'est pas encore capable de traiter une requête POST !

Nous savons donc ce qu'il nous reste à faire : il faut implémenter la méthode doPost(). Voici le code modifié de notre servlet :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.sdzee.servlets;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Inscription extends HttpServlet {
    public static final String VUE = "/WEB-INF/inscription.jsp";

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException{
        /* Affichage de la page d'inscription */
        this.getServletContext().getRequestDispatcher( VUE ).forward( request, response );
    }

    public void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException{
        /* Traitement des données du formulaire */
    }
}

com.sdzee.servlets.Inscription

Maintenant que nous avons ajouté une méthode doPost(), nous pouvons envoyer les données du formulaire, il n'y aura plus d'erreur HTTP ! Par contre, la méthode doPost() étant vide, nous obtenons bien évidemment une page blanche en retour…

Contrôle : côté servlet

Maintenant que notre formulaire est accessible à l'utilisateur et que la servlet en charge de son contrôle est en place, nous pouvons nous attaquer à la vérification des données envoyées par le client.

Que souhaitons-nous vérifier ?

Nous travaillons sur un formulaire d'inscription qui contient quatre champs de type <input>, cela ne va pas être bien compliqué. Voici ce que je vous propose de vérifier :

  • que le champ obligatoire email n'est pas vide et qu'il contient une adresse mail valide ;
  • que les champs obligatoires mot de passe et confirmation ne sont pas vides, qu'ils contiennent au moins 3 caractères, et qu'ils sont égaux ;
  • que le champ facultatif nom, s'il est rempli, contient au moins 3 caractères.

Nous allons confier ces tâches à trois méthodes distinctes :

  • une méthode validationEmail(), chargée de valider l'adresse mail saisie ;
  • une méthode validationMotsDePasse(), chargée de valider les mots de passe saisis ;
  • une méthode validationNom(), chargée de valider le nom d'utilisateur saisi.

Voici donc le code modifié de notre servlet, impliquant la méthode doPost(), des nouvelles constantes et les méthodes de validation créées pour l'occasion, en charge de récupérer le contenu des champs du formulaire et de les faire valider :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.sdzee.servlets;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Inscription extends HttpServlet {
    public static final String VUE = "/WEB-INF/inscription.jsp";
    public static final String CHAMP_EMAIL = "email";
    public static final String CHAMP_PASS = "motdepasse";
    public static final String CHAMP_CONF = "confirmation";
    public static final String CHAMP_NOM = "nom";

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException{
        /* Affichage de la page d'inscription */
        this.getServletContext().getRequestDispatcher( VUE ).forward( request, response );
    }

    public void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException{
        /* Récupération des champs du formulaire. */
        String email = request.getParameter( CHAMP_EMAIL );
        String motDePasse = request.getParameter( CHAMP_PASS );
        String confirmation = request.getParameter( CHAMP_CONF );
        String nom = request.getParameter( CHAMP_NOM );

        try {
            validationEmail( email );
            validationMotsDePasse( motDePasse, confirmation );
            validationNom( nom );
        } catch (Exception e) {
            /* Gérer les erreurs de validation ici. */
        }
    }

    private void validationEmail( String email ) throws Exception{}
    private void validationMotsDePasse( String motDePasse, String confirmation ) throws Exception{}
    private void validationNom( String nom ) throws Exception{}
}

com.sdzee.servlets.Inscription

La partie en charge de la récupération des champs du formulaire se situe aux lignes 24 à 27 : il s'agit tout simplement d'appels à la méthode request.getParameter(). Il nous reste maintenant à implémenter nos trois dernières méthodes de validation, qui sont vides pour le moment. Voilà un premier jet de ce que cela pourrait donner :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
...

/**
 * Valide l'adresse mail saisie.
 */
private void validationEmail( String email ) throws Exception {
    if ( email != null && email.trim().length() != 0 ) {
        if ( !email.matches( "([^.@]+)(\\.[^.@]+)*@([^.@]+\\.)+([^.@]+)" ) ) {
            throw new Exception( "Merci de saisir une adresse mail valide." );
        }
    } else {
        throw new Exception( "Merci de saisir une adresse mail." );
    }
}

/**
 * Valide les mots de passe saisis.
 */
private void validationMotsDePasse( String motDePasse, String confirmation ) throws Exception{
    if (motDePasse != null && motDePasse.trim().length() != 0 && confirmation != null && confirmation.trim().length() != 0) {
        if (!motDePasse.equals(confirmation)) {
            throw new Exception("Les mots de passe entrés sont différents, merci de les saisir à nouveau.");
        } else if (motDePasse.trim().length() < 3) {
            throw new Exception("Les mots de passe doivent contenir au moins 3 caractères.");
        }
    } else {
        throw new Exception("Merci de saisir et confirmer votre mot de passe.");
    }
}

/**
 * Valide le nom d'utilisateur saisi.
 */
private void validationNom( String nom ) throws Exception {
    if ( nom != null && nom.trim().length() < 3 ) {
        throw new Exception( "Le nom d'utilisateur doit contenir au moins 3 caractères." );
    }
}

com.sdzee.servlets.Inscription

Je ne détaille pas le code de ces trois courtes méthodes. Si vous ne comprenez pas leur fonctionnement, vous devez impérativement revenir par vous-mêmes sur ces notions basiques du langage Java avant de continuer ce tutoriel.

J'ai ici fait en sorte que dans chaque méthode, lorsqu'une erreur de validation se produit, le code envoie une exception contenant un message explicitant l'erreur. Ce n'est pas la seule solution envisageable, mais c'est une solution qui a le mérite de tirer parti de la gestion des exceptions en Java. À ce niveau, un peu de réflexion sur la conception de notre système de validation s'impose :

Que faire de ces exceptions envoyées ?

En d'autres termes, quelles informations souhaitons-nous renvoyer à l'utilisateur en cas d'erreur ? Pour un formulaire d'inscription, a priori nous aimerions bien que l'utilisateur soit au courant du succès ou de l'échec de l'inscription, et en cas d'échec qu'il soit informé des erreurs commises sur les champs posant problème.

Comment procéder ? Là encore, il y a bien des manières de faire. Je vous propose ici le mode de fonctionnement suivant :

  • une chaîne resultat contenant le statut final de la validation des champs ;
  • une Map erreurs contenant les éventuels messages d'erreur renvoyés par nos différentes méthodes se chargeant de la validation des champs. Une HashMap convient très bien dans ce cas d'utilisation : en l'occurrence, la clé sera le nom du champ et la valeur sera le message d'erreur correspondant.

Mettons tout cela en musique, toujours dans la méthode doPost() de notre servlet :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.sdzee.servlets;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Inscription extends HttpServlet {
    public static final String VUE          = "/WEB-INF/inscription.jsp";
    public static final String CHAMP_EMAIL  = "email";
    public static final String CHAMP_PASS   = "motdepasse";
    public static final String CHAMP_CONF   = "confirmation";
    public static final String CHAMP_NOM    = "nom";
    public static final String ATT_ERREURS  = "erreurs";
    public static final String ATT_RESULTAT = "resultat";

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /* Affichage de la page d'inscription */
        this.getServletContext().getRequestDispatcher( VUE ).forward( request, response );
    }

    public void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        String resultat;
        Map<String, String> erreurs = new HashMap<String, String>();

        /* Récupération des champs du formulaire. */
        String email = request.getParameter( CHAMP_EMAIL );
        String motDePasse = request.getParameter( CHAMP_PASS );
        String confirmation = request.getParameter( CHAMP_CONF );
        String nom = request.getParameter( CHAMP_NOM );

        /* Validation du champ email. */
        try {
            validationEmail( email );
        } catch ( Exception e ) {
            erreurs.put( CHAMP_EMAIL, e.getMessage() );
        }

        /* Validation des champs mot de passe et confirmation. */
        try {
            validationMotsDePasse( motDePasse, confirmation );
        } catch ( Exception e ) {
            erreurs.put( CHAMP_PASS, e.getMessage() );
        }

        /* Validation du champ nom. */
        try {
            validationNom( nom );
        } catch ( Exception e ) {
            erreurs.put( CHAMP_NOM, e.getMessage() );
        }

        /* Initialisation du résultat global de la validation. */
        if ( erreurs.isEmpty() ) {
            resultat = "Succès de l'inscription.";
        } else {
            resultat = "Échec de l'inscription.";
        }

        /* Stockage du résultat et des messages d'erreur dans l'objet request */
        request.setAttribute( ATT_ERREURS, erreurs );
        request.setAttribute( ATT_RESULTAT, resultat );

        /* Transmission de la paire d'objets request/response à notre JSP */
        this.getServletContext().getRequestDispatcher( VUE ).forward( request, response );
    }

    ...
}

com.sdzee.servlets.Inscription

Analysez bien les modifications importantes du code, afin de bien comprendre ce qui intervient dans ce processus de gestion des exceptions :

  • chaque appel à une méthode de validation d'un champ est entouré d'un bloc try/catch ;
  • à chaque entrée dans un catch, c'est-à-dire dès lors qu'une méthode de validation envoie une exception, on ajoute à la Map erreurs le message de description inclus dans l'exception courante, avec pour clé l'intitulé du champ du formulaire concerné ;
  • le message resultat contenant le résultat global de la validation est initialisé selon que la Map erreurs contient des messages d'erreurs ou non ;
  • les deux objets erreurs et resultat sont enfin inclus en tant qu'attributs à la requête avant l'appel final à la vue.

Le contrôle des données dans notre servlet est maintenant fonctionnel : avec les données que nous transmettons à notre JSP, nous pouvons y déterminer si des erreurs de validation ont eu lieu et sur quels champs.

Affichage : côté JSP

Ce qu'il nous reste maintenant à réaliser, c'est l'affichage de nos différents messages au sein de la page JSP, après que l'utilisateur a saisi et envoyé ses données. Voici ce que je vous propose :

  1. en cas d'erreur, affichage du message d'erreur à côté de chacun des champs concernés ;
  2. ré-affichage dans les champs <input> des données auparavant saisies par l'utilisateur ;
  3. affichage du résultat de l'inscription en bas du formulaire.

1. Afficher les messages d'erreurs

L'attribut erreurs que nous recevons de la servlet ne contient des messages concernant les différents champs de notre formulaire que si des erreurs ont été rencontrées lors de la validation de leur contenu, c'est-à-dire uniquement si des exceptions ont été envoyées. Ainsi, il nous suffit d'afficher les entrées de la Map correspondant à chacun des champs email, motdepasse, confirmation et nom :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<%@ page pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Inscription</title>
        <link type="text/css" rel="stylesheet" href="form.css" />
    </head>
    <body>
        <form method="post" action="inscription">
        <fieldset>
                <legend>Inscription</legend>
                <p>Vous pouvez vous inscrire via ce formulaire.</p>

                <label for="email">Adresse email <span class="requis">*</span></label>
                <input type="email" id="email" name="email" value="" size="20" maxlength="60" />
                <span class="erreur">${erreurs['email']}</span>
                <br />

                <label for="motdepasse">Mot de passe <span class="requis">*</span></label>
                <input type="password" id="motdepasse" name="motdepasse" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['motdepasse']}</span>
                <br />

                <label for="confirmation">Confirmation du mot de passe <span class="requis">*</span></label>
                <input type="password" id="confirmation" name="confirmation" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['confirmation']}</span>
                <br />

                <label for="nom">Nom d'utilisateur</label>
                <input type="text" id="nom" name="nom" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['nom']}</span>
                <br />

                <input type="submit" value="Inscription" class="sansLabel" />
                <br />
            </fieldset>
        </form>
    </body>
</html>

/WEB-INF/inscription.jsp

Vous retrouvez aux lignes 17, 22, 27 et 32 l'utilisation des crochets pour accéder aux entrées de la Map, comme nous l'avions déjà fait lors de notre apprentissage de la JSTL. De cette manière, si aucun message ne correspond dans la Map à un champ du formulaire donné, c'est qu'il n'y a pas eu d'erreur lors de sa validation côté serveur. Dans ce cas, la balise <span> sera vide et aucun message ne sera affiché à l'utilisateur. Comme vous pouvez le voir, j'en ai profité pour ajouter un style à notre feuille form.css, afin de mettre en avant les erreurs :

1
2
3
form .erreur {
    color: #900;
}

Vous pouvez maintenant faire le test : remplissez votre formulaire avec des données erronées (une adresse mail invalide, un nom d'utilisateur trop court ou des mots de passe différents, par exemple) et contemplez le résultat ! À la figure suivante, le rendu attendu lorsque vous entrez un nom d'utilisateur trop court.

Erreur de validation du formulaire

2. Réafficher les données saisies par l'utilisateur

Comme vous le constatez sur cette dernière image, les données saisies par l'utilisateur avant validation du formulaire disparaissent des champs après validation. En ce qui concerne les champs mot de passe et confirmation, c'est très bien ainsi : après une erreur de validation, il est courant de demander à l'utilisateur de saisir à nouveau cette information sensible. Dans le cas du nom et de l'adresse mail par contre, ce n'est vraiment pas ergonomique et nous allons tâcher de les faire réapparaître. Pour cette étape, nous pourrions être tentés de simplement réafficher directement ce qu'a saisi l'utilisateur dans chacun des champs "value" des <input> du formulaire. En effet, nous savons que ces données sont directement accessibles via l'objet implicite param, qui donne accès aux paramètres de la requête HTTP. Le problème, et c'est un problème de taille, c'est qu'en procédant ainsi nous nous exposons aux failles XSS. Souvenez-vous : je vous en ai déjà parlé lorsque nous avons découvert la balise <c:out> de la JSTL !

Quel est le problème exactement ?

Bien… puisque vous semblez amnésiques et sceptiques, faisons comme si de rien n'était, et réaffichons le contenu des paramètres de la requête HTTP (c'est-à-dire le contenu saisi par l'utilisateur dans les champs <input> du formulaire) en y accédant directement via l'objet implicite param, aux lignes 16 et 31 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<%@ page pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Inscription</title>
        <link type="text/css" rel="stylesheet" href="form.css" />
    </head>
    <body>
        <form method="post" action="inscription">
            <fieldset>
                <legend>Inscription</legend>
                <p>Vous pouvez vous inscrire via ce formulaire.</p>

                <label for="email">Adresse email <span class="requis">*</span></label>
                <input type="email" id="email" name="email" value="${param.email}" size="20" maxlength="60" />
                <span class="erreur">${erreurs['email']}</span>
                <br />

                <label for="motdepasse">Mot de passe <span class="requis">*</span></label>
                <input type="password" id="motdepasse" name="motdepasse" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['motdepasse']}</span>
                <br />

                <label for="confirmation">Confirmation du mot de passe <span class="requis">*</span></label>
                <input type="password" id="confirmation" name="confirmation" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['confirmation']}</span>
                <br />

                <label for="nom">Nom d'utilisateur</label>
                <input type="text" id="nom" name="nom" value="${param.nom}" size="20" maxlength="20" />
                <span class="erreur">${erreurs['nom']}</span>
                <br />

                <input type="submit" value="Inscription" class="sansLabel" />
                <br />
            </fieldset>
        </form>
    </body>
</html>

/WEB-INF/inscription.jsp

Faites alors à nouveau le test en remplissant et validant votre formulaire. Dorénavant, les données que vous avez entrées sont bien présentes dans les champs du formulaire après validation, ainsi que vous pouvez le constater à la figure suivante.

Erreur de validation du formulaire avec affichage des données saisies

En apparence ça tient la route, mais je vous ai lourdement avertis : en procédant ainsi, votre code est vulnérable aux failles XSS. Vous voulez un exemple ? Remplissez le champ nom d'utilisateur par le contenu suivant : ">Bip bip ! . Validez ensuite votre formulaire, et contemplez alors ce triste et désagréable résultat (voir la figure suivante).

Erreur de validation du formulaire avec faille XSS

Que s'est-il passé ?

Une faille XSS, pardi ! Eh oui, côté serveur, le contenu que vous avez saisi dans le champ du formulaire a été copié tel quel dans le code généré par notre JSP. Il a ensuite été interprété par le navigateur côté client, qui a alors naturellement considéré que le guillemet " et le chevron > contenus en début de saisie correspondaient à la fermeture de la balise <input> ! Si vous êtes encore dans le flou, voyez plutôt le code HTML produit sur la ligne posant problème :

1
<input type="text" id="nom" name="nom" value="">Bip bip !" size="20" maxlength="20" />

Vous devez maintenant comprendre le problème : le contenu de notre champ a été copié puis collé tel quel dans la source de notre fichier HTML final, lors de l'interprétation par le serveur de l'expression EL que nous avons mise en place (c'est-à-dire ${param.nom}). Et logiquement, puisque le navigateur ferme la balise <input> prématurément, notre joli formulaire s'en retrouve défiguré. Certes, ici ce n'est pas bien grave, je n'ai fait que casser l'affichage de la page. Mais vous devez savoir qu'en utilisant ce type de failles, il est possible de causer bien plus de dommages, notamment en injectant du code Javascript dans la page à l'insu du client.

Je vous le répète : la règle d'or, c'est de ne jamais faire confiance à l'utilisateur.

Pour pallier ce problème, il suffit d'utiliser la balise <c:out> de la JSTL Core pour procéder à l'affichage des données. Voici ce que donne alors le code modifié de notre JSP, observez bien les lignes 17 et 32 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<%@ page pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Inscription</title>
        <link type="text/css" rel="stylesheet" href="form.css" />
    </head>
    <body>
        <form method="post" action="inscription">
            <fieldset>
                <legend>Inscription</legend>
                <p>Vous pouvez vous inscrire via ce formulaire.</p>

                <label for="email">Adresse email <span class="requis">*</span></label>
                <input type="email" id="email" name="email" value="<c:out value="${param.email}"/>" size="20" maxlength="60" />
                <span class="erreur">${erreurs['email']}</span>
                <br />

                <label for="motdepasse">Mot de passe <span class="requis">*</span></label>
                <input type="password" id="motdepasse" name="motdepasse" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['motdepasse']}</span>
                <br />

                <label for="confirmation">Confirmation du mot de passe <span class="requis">*</span></label>
                <input type="password" id="confirmation" name="confirmation" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['confirmation']}</span>
                <br />

                <label for="nom">Nom d'utilisateur</label>
                <input type="text" id="nom" name="nom" value="<c:out value="${param.nom}"/>" size="20" maxlength="20" />
                <span class="erreur">${erreurs['nom']}</span>
                <br />

                <input type="submit" value="Inscription" class="sansLabel" />
                <br />
            </fieldset>
        </form>
    </body>
</html>

/WEB-INF/inscription.jsp

La balise <c:out> se chargeant par défaut d'échapper les caractères spéciaux, le problème est réglé. Notez l'ajout de la directive taglib en haut de page, pour que la JSP puisse utiliser les balises de la JSTL Core. Faites à nouveau le test avec le nom d'utilisateur précédent, et vous obtiendrez bien cette fois le résultat affiché à la figure suivante.

Erreur de validation du formulaire sans faille XSS

Dorénavant, l'affichage n'est plus cassé, et si nous regardons le code HTML généré, nous observons bien la transformation du " et du > en leurs codes HTML respectifs par la balise <c:out> :

1
<input type="text" id="nom" name="nom" value="&#034;&gt;Bip bip !" size="20" maxlength="20" />

Ainsi, le navigateur de l'utilisateur reconnaît que les caractères " et > font bien partie du contenu du champ, et qu'ils ne doivent pas être interprétés en tant qu'éléments de fermeture de la balise <input> !

À l'avenir, n'oubliez jamais ceci : protégez toujours les données que vous affichez à l'utilisateur !

3. Afficher le résultat final de l'inscription

Il ne nous reste maintenant qu'à confirmer le statut de l'inscription. Pour ce faire, il suffit d'afficher l'entrée resultat de la Map dans laquelle nous avons initialisé le message, à la ligne 39 dans le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<%@ page pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Inscription</title>
        <link type="text/css" rel="stylesheet" href="form.css" />
    </head>
    <body>
        <form method="post" action="inscription">
            <fieldset>
                <legend>Inscription</legend>
                <p>Vous pouvez vous inscrire via ce formulaire.</p>

                <label for="email">Adresse email <span class="requis">*</span></label>
                <input type="email" id="email" name="email" value="<c:out value="${param.email}"/>" size="20" maxlength="60" />
                <span class="erreur">${erreurs['email']}</span>
                <br />

                <label for="motdepasse">Mot de passe <span class="requis">*</span></label>
                <input type="password" id="motdepasse" name="motdepasse" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['motdepasse']}</span>
                <br />

                <label for="confirmation">Confirmation du mot de passe <span class="requis">*</span></label>
                <input type="password" id="confirmation" name="confirmation" value="" size="20" maxlength="20" />
                <span class="erreur">${erreurs['confirmation']}</span>
                <br />

                <label for="nom">Nom d'utilisateur</label>
                <input type="text" id="nom" name="nom" value="<c:out value="${param.nom}"/>" size="20" maxlength="20" />
                <span class="erreur">${erreurs['nom']}</span>
                <br />

                <input type="submit" value="Inscription" class="sansLabel" />
                <br />

                <p class="${empty erreurs ? 'succes' : 'erreur'}">${resultat}</p>
            </fieldset>
        </form>
    </body>
</html>

/WEB-INF/inscription.jsp

Vous remarquez ici l'utilisation d'un test ternaire sur notre Map erreurs au sein de la première expression EL mise en place, afin de déterminer la classe CSS à appliquer au paragraphe. Si la Map erreurs est vide, alors cela signifie qu'aucune erreur n'a eu lieu et on utilise le style nommé succes, sinon on utilise le style erreur. En effet, j'en ai profité pour ajouter un dernier style à notre feuille form.css, pour mettre en avant le succès de l'inscription :

1
2
3
form .succes {
    color: #090;
}

Et sous vos yeux ébahis, voici aux figures suivantes le résultat final en cas de succès et d'erreur.

Succès de la validation du formulaire

Erreur dans la validation du formulaire


  • Les pages placées sous /WEB-INF ne sont par défaut pas accessibles au public par une URL.
  • La méthode d'envoi des données d'un formulaire se définit dans le code HTML via <form method="...">.
  • Les données envoyées via un formulaire sont accessibles côté serveur via des appels à request.getParameter( "nom_du_champ" ).
  • La validation des données, lorsque nécessaire, peut se gérer simplement avec les exceptions et des interceptions via des blocs try / catch.
  • Le renvoi de messages à l'utilisateur peut se faire via une simple Map placée en tant qu'attribut de requête.
  • L'affichage de ces messages côté JSP se fait alors via de simples et courtes expressions EL.
  • Il ne faut jamais afficher du contenu issu d'un utilisateur sans le sécuriser, afin d'éliminer le risque de failles XSS.
  • Placer la validation et les traitements sur les données dans la servlet n'est pas une bonne solution, il faut trouver mieux.