Réserver l'accès à certaines des pages de l'application à des utilisateurs privilégiés, en utilisant l'authentification par le container.
L'application se repose sur le serveur d'application pour filtrer l'accès aux pages de l'application. Jakarta EE fournit une API pour la sécurité, qui simplifie l'authentification des utilisateurs ; ce TP utilise cette API.
Créez un nouveau projet "tpsecurite" de type Java Web, puis Web Application, avec utilisation de JSF, comme dans le TP sur PRG.
Refaites tout ce que vous avez déjà fait pour les TPs précédents :
Vous allez créer 3 répertoires pour les pages Web. admin
sera réservé aux administrateurs, connect
sera réservé aux utilisateurs connectés et ouvert
sera ouvert à tous les utilisateurs, même ceux qui ne se sont pas authentifiés. Sous le répertoire racine des pages Web de votre application, créez ces 3 répertoires (clic droit sur "Web Pages" > New > Folder).
Ajoutez aussi une page index.xhtml à la racine du projet. Mettez cette page comme page "welcome" du projet (dans web.xml).
Ajoutez une page JSF index.xhtml dans chacun de ces répertoires. Dans la page située sous admin écrivez le texte "Réservé aux administrateurs", dans la page située sous connect écrivez "Réservé aux utilisateurs authentifiés" et dans la page située sous ouvert écrivez le texte "Ouvert à tous".
Dans ces 3 pages, ajoutez un lien vers cette page index.xhtml située à la racine du projet. Tous les liens de cet exercice seront de type <h:link>
.
Dans la page "welcome" du projet ajoutez 3 liens vers les 2 pages protégées et la page ouvert à tous.
Testez le projet en passant d'une page à l'autre en utilisant les liens.
Faites un commit Git et un push sur GitHub. N'oubliez pas de le faire après avoir testé et validé chaque étape importante de ce TP (au moins à la fin de chaque question).
Modifiez le fichier web.xml pour que l'accès aux pages placées sous ce répertoire soient protégées par mot de passe. Les pages ne seront accessibles que par les utilisateurs qui ont le rôle "admin". Créez aussi un rôle "user" que vous allez utiliser pour tester les utilisateurs authentifiés qui n'ont pas le rôle "admin" (role-name a pour valeur "**" dans web.xml).
Testez à nouveau le projet (faites d'abord un "clean and build" pour redéployer l'application). Est-ce que les pages sont protégées ? Comment ?
Commit et push. N'oubliez pas pour la suite.
Lignes à ajouter à web.xml
Il est possible de configurer des utilisateurs d'une façon statique dans Payara. C'est la configuration par défaut pour l'authentification des utilisateurs. Dans les exercices à suivre vous indiquerez un autre mécanisme d'authentification ("basic" ou "custom form") qui utilise les informations sur les utilisateurs enregistrées dans une base de données.
C'est un mécanisme basique qu'il n'est pas recommandé d'utiliser mais qui peut être utile pour faire des tests rapides. Nous verrons une autre façon plus sûre et plus souple de configurer les utilisateurs dans la suite de ce TP.
Pour cette configuration basique utilisez la console d'administration de Payara. Vous l'avez déjà utilisée pour l'installation des logiciels, au début de ce cours.
Dans la console d'administration ("realm" peut être traduit par "domaine" ; il peut exister plusieurs domaines avec des utilisateurs différents pour chaque domaine),
Testez les accès aux pages avec les utilisateurs que vous venez de créer. Comme les données de la session sont conservées dans des cookies, il faut supprimer les cookies de votre navigateur après avoir rempli les données dans le formulaire qui protège les pages. Pour éviter de le faire, vous pouvez changer de navigateur entre 2 tests et/ou utiliser le mode "private" de votre navigateur ("fenêtre de navigation privée" par exemple dans Chrome), en changeant de fenêtre privée entre chaque test (les cookies ne sont pas gardés par ce type de fenêtre).
Supprimez les 2 utilisateurs quand vous en avez terminé avec cette question.
Dans la page "welcome" placée à la racine de l'application, ajoutez un lien vers la page réservée aux administrateurs avec un composant JSF qui génère une requête POST, suivi d'un forward interne pour retourner la page protégée (<h:commandLink>
).
Est-ce que la page est protégée en passant par ce lien ?
Nous allons voir maintenant une autre façon de définir les utilisateurs d'une application, bien plus souple (pas besoin de relancer le serveur quand on ajoute des utilisateurs) et bien plus sure.
Les utilisateurs seront enregistrés dans une base de données.
Commencez par créer la base de données MySQL qui sera utilisée par ce TP. Nommez-la, par exemple, "tpsecurite".
Vous pourriez créer les tables et les données directement dans le gestionnaire de base de données. Dans cet exercice, vous allez voir comment faire créer au démarrage de l'application les tables et les données utilisées pour authentifier les utilisateurs. Vous avez déjà initialisé des données dans le TP sur les comptes bancaires ; cette fois-ci vous allez aussi créer les tables. Dans une application réelle, ces tables et ces données existeront déjà et cette étape ne sera pas nécessaire.
Commencez par créer une source données dans le serveur d'application comme vous l'avez fait dans l'installation des logiciels (avec la base de données tpsecurite à la place de la base de données customer). Cette source de données aura comme nom jndi "jdbc/tpsecurite".
Comme pour le TP sur JPA, créez une classe Init
de type EJB singleton pour créer les tables et y ajouter des données. L'EJB devra être exécuté automatiquement au démarrage de l'application.
La structure des tables sera la plus simple possible. Toutes les colonnes sont de type varchar ; la taille est écrite entre parenthèses. Voici la structure :
Vous allez écrire une classe utilitaire util.HashMdp qui servira à crypter des mots de passe. Cette classe est un bean CDI que vous pourrez injecter dans vos classes si vous en avez besoin. Vous éviterez ainsi d'avoir à répéter le même code à plusieurs endroits de votre code.
Pour cette initialisation on peut utiliser JPA (avec EntityManager.createNativeQuery
pour la création des tables) ou JDBC. La correction utilise JDBC (une petite révision...) car JPA pose un problème si on veut tester si les tables existent déjà. On n'a rien à faire pour créer les tables si la source de données est en mode "create-table" dans persistence.xml.
Comme la structure des tables est minimaliste c'est plus simple qu'avec JPA pour ajouter des données. Il serait possible d'ajouter d'autres informations dans ces tables ; par exemple des id non significatifs générés par le SGBD comme clé primaire, un email ou une adresse pour la table utilisateur.
La méthode d'initialisation devra tester si les tables ou les données existent déjà avant d'ajouter les tables ou les données.
Testez en lançant l'application. Lancez-la 2 fois pour voir si les données ne sont pas dupliquées. En fait il suffit de modifier du code pour que le déploiement soit effectué automatiquement et donc pour lancer l'exécution de la méthode init()
de l'EJB Init
.
Allez voir les données enregistrées dans les tables login et login_groupe. Remarquez que les 2 mots de passe cryptés ne sont pas égaux alors qu'ils correspondent tous les deux à "toto". En effet, l'algorithme de cryptage "sale" les valeurs cryptées pour compliquer la tâche des pirates informatiques.
Créez une classe Config
dans les sous-paquetage config
du paquetage de base de l'application. Elle est annotée pour la portée application de CDI. Une annotation CDI va lui permettre d'être découverte au déploiement par CDI (indispensable pour l'API de sécurité).
package xx.xxxx.xxxx.config; import jakarta.enterprise.context.ApplicationScoped; /** * Configuration de l'application pour la sécurité. */ @ApplicationScoped public class Config { }
Dans cette classe vous allez configurer l'entrepôt d'identité qui va servir à authentifier les utilisateurs.
Il suffit de donner les ordres SQL qui vont retourner le mot de passe et les groupes associés à un login donné.
Vous pouvez faire un test rapide en utilisant l'authentification "basic" par un formulaire généré par le navigateur. Cette authentification ne doit pas être utilisée normalement car elle génère des problèmes, par exemple pour faire des logout.
Pour ce test avec "basic", il suffit d'ajouter cette annotation à la classe Config
(vous pouvez donner n'importe quel nom au realm) :
@BasicAuthenticationMechanismDefinition(realmName="user-realm")
Lancez l'application et testez les accès autorisés pour l'utilisateur "toto". Pour tester pour l'utilisateur "admin" utilisez un autre navigateur ou bien utilisez le mode "private" de votre navigateur ("fenêtre de navigation privée" par exemple dans Chrome), en changeant de fenêtre privée entre chaque test (les cookies ne sont pas gardés par ce type de fenêtre).
Dans la suite du TP vous allez utiliser un autre type d'authentification, plus sûr et qui permettra d'ajouter un bouton pour déconnecter les utilisateurs de la session (logout). Enlevez la ligne @BasicAuthenticationMechanismDefinition(realmName="user-realm")
Dans le mécanisme d'authentification "basic", le formulaire de login, qui demande le nom et le mot de passe de l'utilisateur", et son traitement sont fournis par le navigateur. Le plus souvent vous utiliserez votre propre formulaire de login et vous voudrez personnaliser son traitement. L'API de sécurité de Jakarta EE vous fournit tous les éléments pour cela.
Vous allez écrire une page JSF de login. Elle contient un formulaire qui demande le nom de login et le mot de passe de l'utilisateur. Au bas de cette page ajoutez un lien vers la page "welcome" de l'application pour le cas où l'utilisateur changerait d'avis.
Le backing bean qui traite le formulaire utilise la méthode SecurityContext.authenticate
qui a 3 paramètres : la requête, la réponse et des paramètres qui contiennent en particulier les "credentials" qui vont servir à authentifier l'utilisateur. Pour notre cas les "credentials" sont le nom de login et le mot de passe. Attention à bien importer les bons types ; par exemple jakarta.security.enterprise.SecurityContext
. et jakarta.security.enterprise.AuthenticationStatus.SEND_CONTINUE
.
Il reste à configurer l'application pour que le container utilise cette page pour authentifier les utilisateurs. Pour cela annotez la classe Config
avec @CustomFormAuthenticationMechanismDefinition
.
Testez.
Classe Config
Page de login
Backing bean
Dans la page "welcome" ajoutez un bouton (balise <h:commandButton>
) qui permet de sortir de la session. Associez-le à une méthode logout()
du backing bean LoginBean
.
Pour vérifier plus facilement, ajoutez à la fin de la page "welcome" de l'application une zone (délimitée par une balise <h:panelGroup>
) qui n'est affichée que si l'utilisateur est authentifié et qui affiche "Administrateur" si l'utilisateur est administrateur. Mettez le bouton qui permet de sortir de l'application dans cette zone.
Cette information et ce bouton pourrait être mis dans un template pour être affichés sur toutes les pages.
Si l'utilisateur veut accéder à une page qu'il n'a pas le droit de voir une vilaine erreur 403 est affichée. Écrivez une page noauth.xhtml
pour afficher un message plus agréable et qui a lien vers la page de "welcome" de l'application.
Il suffit alors d'ajouter une balise <error-page> dans le fichier web.xml pour le code d'erreur 403.
Jusqu'à maintenant l'utilisateur devait s'authentifier lorsqu'il voulait accéder à une page protégée.
Vous allez ajouter un bouton qui permet à l'utilisateur de s'authentifier pour toute la session, même s'il n'accède pas à une page protégée. Ce bouton ne s'affiche que si l'utilisateur ne s'est pas déjà authentifié.
Après l'authentification, la page index.xhtml est affichée.
Page de login
Bouton vers la page de login
Backing bean
Les navigateurs n'acceptent plus les URL en "http" pour les sites non locaux qu'après avoir lancé un avertissement.
Tous les sites professionnels doivent avoir une adresse en "https". HTTPS est mis pour Hypertext Transfer Protocol Secure. Ce protocole nécessite d'obtenir un certificat SSL (voir fonctionnement de Secure Sockets Layer). Les données échangées entre le navigateur et le site Web sont chiffrées en utilisant ce certificat, ce qui rend plus difficile le vol ou l'interception de données.
Pour avoir un site en https, il faut
Vous allez avoir besoin de la commande openssl. Elle n'est pas installée sous Windows. Si vous avez ce système d'exploitation il va donc falloir l'installer.
Pour une application en production, il faudrait récupérer un certificat signé par une autorité de certification de confiance ("trusted CA", d'après les intiales en anglais) qui va prouver l'identité du serveur dans ses échanges avec les clients. Vous pourriez par exemple utiliser un certificat de "Let's Encrypt", une autorité de certification à but non lucratif ; cet article explique comment faire.
Pour tester, il est possible d'utiliser un certificat auto-signé (https://blog.payara.fish/securing-payara-server-with-custom-ssl-certificate), et c'est ce que vous allez faire dans ce TP.
Vous allez tout d'abord créer un certificat pour un CA que vous allez créer et qui va signer le certificat du serveur (en production le CA devra être un CA de confiance connu de tous les navigateurs). Ensuite vous créerez le certificat du serveur ; pour les tests locaux, il correspondra à l'adresse "localhost".
Ouvrez un terminal pour taper des commandes. Créez un répertoire et placez-vous dedans.
Voici les étapes pour le certificat du CA (je ne détaille pas les options des commandes ; vous pouvez les trouver facilement sur le Web) :
Cette configuration n'est pas standardisée. Elle dépend du serveur d'application.
Payara ne doit pas être démarré pendant ces manipulations.
Redémarrer Payara pour la prise en compte de sa configuration.
Lancez l'exécution d'un des projets des TPs, par exemple le TP sur PRG.
Si vous tapez alors cette adresse dans le navigateur,
https://localhost:8181/tpprg/formulaire_4.xhtml
il s'affiche un avertissement car le CA utilisé pour signer le certificat n'est pas connu du navigateur. Si on lui dit de continuer tout de même, le https de la barre de navigation du navigateur est barré.
Pour continuer le test jusqu'au bout, on pourrait ajouter le CA comme autorité de confiance pour tout l'ordinateur mais on va se contenter de l'ajouter pour le navigateur. Evidemment, si le certificat pour le serveur avait été obtenu d'une CA publique mondialement connue, cette dernière étape aurait été inutile.
Voici comment faire avec Chrome :
Si vous retapez l'URL du projet, tout se passe alors parfaitement, sans avertissement. https est utilisé à la place de http.