We apologize for untranslated text, you can use the Google Translation button to get an automatic translation of the web page in the language of your choice.

Authentifications spécifiques avec les AuthenticationHandler

1. Introduction

JCMS fournit en standard deux mécanismes d'authentification :

  1. Authentification à partir de la base des membres JCMS
  2. Authentification à partir d'un annuaire LDAP/LDAPS

Cependant certaines architectures nécessitent des mécanismes d'authentification particuliers ; comme par exemple :

  1. Base des utilisateurs gérée dans une base de données
  2. Base des utilisateurs gérée dans plusieurs annuaires LDAP non synchronisés
  3. Authentification unique (Single Sign-On)
  4. Authentification à base de certificats
  5. Authentification forte (carte à puce, dispositif biométrique, …)

L'API de JCMS permet de développer et d'intégrer très facilement de nouveaux mécanismes d'authentification adaptés aux besoins de l'architecture cible.

Attention : L'API d'authentification de JCMS 5.7 n'est pas compatible avec les anciens AuthenticationManager de JCMS 5.0/5.5./5.6, pour plus d'informations sur comment migrer de l'une à l'autre, reportez-vous à la section 6 ci-dessous.

2. Présentation de l'architecture

Le gestionnaire d'authentification de JCMS (l'AuthenticationManager) gère une chaîne de composants responsables de fournir les divers mécanismes d'authentification disponible sur le site (les AuthenticationHandlers ou handler dans la suite de ce document).

Chaque handler effectue ses traitements et déclenche l'appel du handler suivant dans la chaîne grâce au contexte d'authentification (AuthenticationContext).

Schéma simplifié de l'architecture d'Authentification en chaîne

Fig. 1. Schéma simplifié de l'architecture d'Authentification en chaîne

2.1 Le gestionnaire d'authentification (AuthenticationManager)

L'AuthenticationManager est un singleton interne à JCMS, dont le role est de servir de point central pour demander une authentification. Cette classe gère la liste de tous les AuthenticationHandlers et se charge de les appeler à chaque requête ainsi que lors d'une déconnexion.

2.2 Les Authentication Handlers

Les AuthenticationHandlers sont des classes Java qui dérivent de la classe com.jalios.jcms.authentication.AuthenticationHandler et qui ont pour fonction principale de définir le membre JCMS authentifié pour la requête en cours de traitement.

Il est possible d'avoir plusieurs handlers. Ils sont appelées successivement les un après les autres. Dans ce cas l'ordre d'appel d'un handler par rapport aux autres est défini par le handler.

2.3 Le contexte d'authentification (AuthenticationContext)

L'AuthenticationContext est une classe Java dont une instance est créée pour chaque requête. Cet objet se charge de transmettre aux handlers toutes les informations requises pour réaliser l'authentification. C'est également grâce à ce contexte que chaque handler peut définir le membre authentifié et appeler les handlers suivants dans la chaîne d'authentification.

3. Les authentication handlers de JCMS

Tous les AuthenticationHandlers fournis en standard dans JCMS se situent dans le package com.jalios.jcms.authentication.handlers, exception faite pour LDAP dont le handler est custom.LdapAuthenticationHandler.

  • LoggingAuthenticationHandler
    Ce handler est le premier dans la liste des handlers, il se charge de journaliser le résultats de la chaîne d'authentification.
  • SessionAuthenticationHandler
    Ce handler enregistre l'authentification des autres handlers dans la session J2EE et l'utilise ultérieurement pour effectuer l'authentification.
  • CookieAuthenticationHandler
    Ce handler enregistre l'authentification des autres handlers dans un cookie envoyé au navigateur et l'utilise ultérieurement pour effectuer l'authentification.
  • SimpleAuthenticationHandler
    Ce handler réalise l'authentification à partir du login/mot de passe fourni en interrogeant la base des membres de JCMS.
  • DelegationAuthenticationHandler
    Ce handler autorise un membre déjà connecté à s'identifier en tant qu'un autre utilisateur en se basant sur les délégations dont il dispose.
  • SilentParamsAuthenticationHandler
    Ce handler effectue l'authentification à partir des paramètres silentLogin et silentPassword.
  • HttpBasicAuthenticationHandler
    Ce handler réalise une authentification en utilisant le login/mot de passe renvoyée par une authentification HTTP BASIC.
    Cet handler est requis pour l'utilisation de WebDAV.

Tous ces handlers ont en commun les caractéristiques suivantes :

  • Ils sont tous développés sous forme de singleton.
  • Pour que vous puissiez placer votre AuthenticationHandler avant ou après l'un de ces handlers standards, leur numéro d'ordre de chacun est accessible via une constante
  • Il est possibilité des les enlever en récupérant le singleton et en le supprimant de la liste des handler via l'AuthenticationManager.

4. Développement d'un AuthenticationHandler

Pour développer un module d'authentification, il faut à la fois respecter l'API d'authentification et l'API des modules. Pour cela, il faut :

  1. Créer une classe qui dérive de la classe AuthenticationHandler, et qui implémente l'interface PluginComponent
  2. Implémenter les méthodes
    void login(AuthenticationContext ctxt) et/ou
    void logout(AuthenticationContext ctxt) selon le besoin.
  3. Créer un module et y déclarer ce handler :
    <authenticationhandler class="com.mycompany.jcmsplugin.myplugin.MyAuthenticationHandler"/>
    Lors du chargement du module, le handler est automatiquement ajouté à la liste des handlers gérés par l'AuthenticationManager.

4.1 Le contexte d'authentification

Le contexte d'authentification est le seul paramètre transmis aux méthodes login() et logout(). Ces caractéristiques sont les suivantes :

  • Il contient tous les éléments nécessaires pour l'authentification (requête, réponse, login, mot de passe, ...).
  • c'est dans ce contexte que le membre authentifié est renseigné par le handler via la méthode ctxt.setLoggedMember(Member mbr).
  • c'est grâce à ce contexte que la chaîne d'authentification (i.e. l'appel à la liste des autres handlers) est invoquée, (ctxt.doChain()).

4.2 Exemple d'AuthenticationHandler

Voici une implémentation typique d'un AuthenticationHandler.

public class MyAuthenticationHandler extends AuthenticationHandler {

public void login(AuthenticationContext ctxt) throws IOException { // 1.
if (!ctxt.isLogged()) { // 2.
Member mbr = getMember(ctxt.getRequest()); // 3.
if (mbr != null) {
ctxt.setLoggedMember(mbr); // 4.
}
}
ctxt.doChain(); // 5.
}

private Member getMember(HttpServletRequest request) {
// retrieve the authenticated member using your custom logic
[...]
return mbr;
}

}
  1. On implémente la méthode login(AuthenticationContext ctxt).
  2. On effectue une authentification uniquement lorsque aucun membre n'est loggué par un autre handler.
  3. On effectue l'authentification.
    Il est recommandé de faire le traitement de récupération du membre JCMS dans une méthode getMember() appelée depuis la méthode login.
  4. On identifie le membre correspondant en utilisant le contexte d'authentification.
  5. On appelle le handler suivant dans la chaîne d'authentification.

4.3 Développements avancés

Cette section explique les fonctionnalités avancées de l'API d'authentification qui vous permettront de réaliser certains développements plus complexes.
Dans la mesure du possible, il est recommandé de trouver des solutions génériques qui ne nécessitent pas de faire appel à ces solutions avancées.

4.3.1 Ordre des handlers

L'ordre d'invocation d'un handler par rapport aux autres handlers est déterminé par l'argument order du constructeur du handler.

Il est recommandé de faire en sorte que le handler puisse fonctionner sans qu'un ordre soit défini. Si vous devez utiliser un numéro d'ordre spécifique, assurez vous d'en choisir un compatible avec tous les autres AuthenticationHandlers standards de JCMS.

Si lors de la mise en place d'un handler dans JCMS, l'ordre choisi par le développeur entre en conflit avec d'autres modules, il est alors possible de redéfinir l'ordre d'un ou plusieurs modules en modifiant la propriété auth-mgr.auth-hdlr-order.{classname} à la valeur souhaitée, où {classname} est le nom complet de la classe de l'AuthenticationHandler dont on souhaite modifier l'ordre.

4.3.2 Suppression d'un handler existant

Pour certaines mécaniques d'authentification, il peut être nécessaire de débrancher complètement certains handlers standards en charge de l'authentification. Pour cela on fait appel à la méthode removeAuthenticationHandler de l'AuthenticationManager.

Par exemple dans le cadre d'une authentification SSO il peut être souhaitable de ne pas utilier le handler effectuant l'authentification à partir de Cookie. Cet handler peut être retiré lors de l'initialisation du handler (dans le constructeur ou dans la méthode init() du PluginComponent) :

  public boolean init(Plugin plugin) {
AuthenticationManager authMgr = AuthenticationManager.getInstance();
authMgr.removeAuthenticationHandler(CookieAuthenticationHandler.getInstance());
return true;
}

4.3.3 Interruption de la chaine d'authentification

Si un handler veut interrompre les traitements des handlers qui le succèdent dans la chaîne d'authentification, sans pour autant les enlever définitivement (cf. section 4.3.2), il peut le faire en n'invoquant pas la méthode ctxt.doChain().
Les handlers suivants dans la chaîne ne sont pas appelés (cf. Fig.2 Handler 3 et Handler 4). En revanche, les handlers précédents peuvent encore effectuer des traitements après leur appel à la méthode ctxt.doChain(). (cf. Fig.2 Handler 1).

Interruption de la chaîne d'authentification

Fig. 2 : Interruption de la chaîne d'authentification

Par exemple, si un handler constate un problème de sécurité et ne souhaite pas laisser aux autres handler la possibilité d'effectuer une authentification, alors il peut interrompre la chaîne. Bien entendu, ceci ne l'empêche pas d'appeler la chaîne dans le cas nominal.

  public void login(AuthenticationContext ctxt) throws IOException {

if (someSecurityCondition) {
ctxt.setWarningMsg("msg.security-problem");
return; // stop chain invokation
}

// Invoke login chain
ctxt.doChain();
}

5. Exemple : un SSO J2EE (Tomcat 5.5)

Dans cet exemple, nous allons créer le module "Tomcat SSO" qui effectue une authentification basée sur celle du serveur d'application.
Nous utiliserons Tomcat 5.5 qui permet de mettre en place rapidement et simplement une mécanique d'authentification J2EE basé sur un fichier texte. Cet exemple peut être généralisé à d'autres serveurs d'application J2EE.

La réalisation de cet exemple se déroule en trois étapes :

  1. Configuration du serveur d'application et de la webapp :
    Il s'agit de configurer Tomcat et la webapp JCMS pour qu'une authentification HTTP BASIC utilisant la base des utilisateurs de Tomcat soit requise lors de l'accès au site. Après cette étape l'accès à JCMS nécessitera la saisie d'un utilisateur Tomcat, mais cette authentification ne sera pas encore propagée à JCMS.
  2. Création du module JCMS "Tomcat SSO" :
    Cet étape va permettre de créer le squelette de base pour la création de l'AuthenticationHandler.
  3. Développement de la classe TomcatSSOAuthenticationHandler :
    Cette dernière étape explique le fonctionnement de l'AuthenticationHandler responsable de propager dans JCMS l'authentification effectuée sous Tomcat.

Le code de cet exemple est disponible en annexe de ce document.

5.1 Configuration de Tomcat

5.1.1 Création de la base des utilisateurs

La première étape consiste à déclarer la base des utilisateurs (Realm) dans Tomcat. Nous utilisons dans cet exemple un MemoryRealm qui permet de rapidement mettre en place une situation de test en utilisant un fichier comme base d'utilisateurs. Pour plus d'information sur les Realm de Tomcat, consultez l'article Tomcat 5.5 : Realm Configuration HOW-TO.

Ajoutez la ligne suivante dans la section <Engine> du fichier conf/server.xml de Tomcat :

<Realm className="org.apache.catalina.realm.MemoryRealm"/>

Ajoutez les roles et les utilisateurs dans le fichier conf/tomcat-users.xml de Tomcat :

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="jcms_role"/>
<user username="admin" password="welcome" roles="jcms_role"/>
<user username="martin" password="bonjour" roles="jcms_role"/>
</tomcat-users>

Dans cet exemple nous spécifions les utilisateurs "admin" (mot de passe "welcome") et "martin" (mot de passe "bonjour").
Etant donnée l'implémentation que nous mettons en place dans l'AuthenticationHandler ci-dessous, les username spécifiés dans cette base doivent être identiques aux logins des membres JCMS correspondants. Dans d'autres implémentations, il peut être nécessaire d'utiliser une table correspondance.

5.1.2 Définition des contraintes de sécurité de la webapp

Pour indiquer au serveur d'application que la webapp JCMS nécessite une authentification, il faut ajouter la section suivante à la fin du fichier de déploiement web.xml de la webapp :

<security-constraint>
<web-resource-collection>
<web-resource-name>JCMS</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>jcms_role</role-name>
</auth-constraint>
</security-constraint>

<login-config>
<auth-method>BASIC</auth-method>
<realm-name>JCMS</realm-name>
</login-config>

<security-role>
<description>JCMS role</description>
<role-name>jcms_role</role-name>
</security-role>

Ceci indique que tous les accès à JCMS (/*) nécessitent une authentification, et plus précisément une appartenance au role jcms_role.

Si vous démarrer votre webapp JCMS, une authentification BASIC est maintenant demandée (et requise) pour accéder au site. Après avoir entré l'un des utilisateurs de la base (admin ou martin) et son mot de passe, l'accès à la webapp JCMS est autorisé. Cependant l'authentification pas n'est pas encore propagée à JCMS et vous devez encore vous identifier via le formulaire de login de JCMS.

5.2 Le module "Tomcat SSO"

5.2.1 Création du module dans JCMS

  • Démarrez JCMS pour pouvoir créer un nouveau module.
  • Allez dans l'Espace d'administration / Exploitation / Gestion des modules et créez un nouveau module "Tomcat SSO".
    authhdlr-plugin-01-create
  • Dans l'interface d'édition du module, entrez une description, un auteur et une licence, enregistrez.
  • Vous pouvez maintenant arreter JCMS pour continuer vos développements.

5.2.2 Suppression des fichiers superflus

Supprimez les répertoires créés automatiquement par le gestionnaire de module qui ne concernent pas notre module : plugins/TomcatSSO et WEB-INF/plugins/TomcatSSO/properties.

Vous devriez maintenant avoir l'arborescence suivante dans votre webapp :

+ WEB-INF
+ classes
+ plugins
+ TomcatSSO
plugin.xml

5.2.3 Création du package et de la classe de l'Authentication Handler

Dans le répertoire WEB-INF/classes, créez le package net.jopale.jcmsplugin.tomcatsso (adaptez ce nom à votre société).

Dans ce package, créez une nouvelle classe TomcatSSOAuthenticationHandler, dérivant de la classe com.jalios.jcms.authentication.AuthenticationHandler et implémentant l'interface com.jalios.jcms.plugin.PluginComponent.

Création de la classe TomcatSSOAuthenticationHandler dans Eclipse

Fig. 3 : Création de la classe TomcatSSOAuthenticationHandler dans Eclipse

Implémentez la méthode init(Plugin) pour qu'elle renvoie true afin d'indiquer que le module est correctement initialisé.

  public boolean init(Plugin plugin) {
return true;
}

Notre composant de module est maintenant pret à être utilisé, nous développerons la partie concernant l'authentification dans la section 5.3 après avoir terminé la déclaration du module.

5.2.4 Edition du fichier plugin.xml

Editez le fichier WEB-INF/plugins/TomcatSSO/plugin.xml, supprimez tous les commentaires d'exemple ainsi que les références aux fichiers précédemment supprimés.
Enfin ajoutez la classe TomcatSSOAuthenticationHandler comme composant de module.

Votre fichier plugin.xml ressemble maintenant à ceci :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plugin PUBLIC "-//JALIOS//DTD JCMS-PLUGIN 1.0//EN" "http://support.jalios.com/dtd/jcms-plugin-1.0.dtd">
<plugin name="TomcatSSO" version="0.1" author="Olivier Jaquemet" license="Jopale">

<label xml:lang="fr">Tomcat SSO</label>
<description xml:lang="fr">Exemple de module d'authentification permettant d'utiliser le SSO de Tomcat pour identifier les membres dans JCMS.</description>

<plugincomponents>
<authenticationhandler class="net.jopale.jcmsplugin.tomcatsso.TomcatSSOAuthenticationHandler"/>
</plugincomponents>

</plugin>

Redémarrez votre webapp, et vérifiez que le module Tomcat SSO est bien chargé et initialisé dans l'interface de gestion des modules (ou dans admin/statusXml.jsp, section <plugins>).

Interface de gestion des modules

Fig. 4 : Interface de gestion des modules

5.3 La classe TomcatSSO AuthenticationHandler

L'objectif de ce handler est de lire l'authentification J2EE et de récupérer le membre JCMS associé pour l'identifier sur le site. Pour cela, on reprends les étapes d'implémentation d'un Authentication Handler (cf. section 4.2) :

Voici le contenu de la classe :

public class TomcatSSOAuthenticationHandler extends AuthenticationHandler implements PluginComponent {
private Logger logger = Logger.getLogger(TomcatSSOAuthenticationHandler.class);

public boolean init(Plugin plugin) {
return true;
}

public void login(AuthenticationContext ctxt) throws IOException { // 1.

if (!ctxt.isLogged()) { // 2.
Member mbr = getMember(ctxt.getRequest()); // 3.
if (mbr != null) {
logger.debug("Authentication processed using J2EE Tomcat SSO");
ctxt.setLoggedMember(mbr); // 4.
}
}

ctxt.doChain(); // 5.
}

private Member getMember(HttpServletRequest request) {
String username = request.getRemoteUser(); // retrieve the J2EE username
return channel.getMemberFromLogin(username); // return the JCMS member with this username
}

}
  1. On implémente la méthode login(AuthenticationContext ctxt).
  2. On effectue une authentification uniquement lorsque aucun membre n'est loggué par un autre handler.
  3. On effectue l'authentification à partir du login J2EE dans une méthode getMember().
  4. On identifie le membre correspondant en utilisant le contexte d'authentification
  5. On appelle le handler suivant dans la chaîne d'authentification.

5.4 Test du handler

Démarrez votre webapp et accédez à votre site.

Si vous effectuez une connexion J2EE avec l'identifiant martin, le membre correspondant n'existant pas (dans une webapp JCMS vierge), l'authentification 'est pas transférée.
Si vous effectuez une connexion J2EE avec l'identifiant admin, l'authentification est automatiquement transférée à JCMS.

6. Migration depuis JCMS 5.0/5.5/5.6

La nouvelle architecture d'authentification a été pensée pour s'intégrer à la mécanique des modules de JCMS 5.7. Elle est incompatible avec l'architecture utilisée dans les précédentes versions de JCMS et nécessite donc une migration.

Si vous aviez développé des AuthenticationManagers pour les précédentes versions de JCMS, vous devez migrer ceux-ci vers la nouvelle API.
Deux cas se présentent :

6.1 AuthenticationManager LDAP

Si vous aviez modifié la classe custom.LdapAuthenticationManager, cette dernière ayant subit de nombreuses modifications dans JCMS 5.7, notamment pour l'import de groupe LDAP, vous devrez réappliquer vos modifications à la nouvelle classe custom.LdapAuthenticationHandler et suivre les indications ci-dessous.

6.2 AuthenticationManager custom

Si vous aviez développé un AuthenticationManager personnalisé, voici quelques étapes qui vous guideront pour le migrer vers la nouvelle API (veillez à toutes les effectuer) :

  1. Dérivez de com.jalios.jcms.authentication.AuthenticationHandler, en lieu et place de l'ancienne classe AuthenticationManager.
  2. Déplacez le code des méthodes login(...), afterLogin(...), checkAuthentication(...) et afterCheckAuthentication(...) dans une nouvelle méthode void login(AuthenticationContext ctxt).
  3. Déplacez le code des méthodes logout(...) et afterLogout(...), dans une nouvelle méthode void logout(AuthenticationContext ctxt).
  4. Ajoutez un appel à la méthode ctxt.doChain() (généralement après votre traitement) pour déclencher l'appel des autres handlers.
  5. Remplacez les appels de variables et de méthodes comme suit :
loggedMember ctxt.getLoggedMember()
login ctxt.isCredentialProvided() && ctxt.getLogin()
password ctxt.isCredentialProvided() && ctxt.getPassword()
request ctxt.getRequest()
response ctxt.getResponse()
sendRedirect(url, request, response) ctxt.sendRedirect(url)
setInformationMsg(request, key, lang) setInformationMsg(key)
setWarningMsg(request, key, lang) setWarningMsg(key)
  • Attention : L'ancienne méthode login(...) était appelée uniquement lors de la soumission d'un formulaire d'authentification. Dans la nouvelle mécanique, la méthode login(AuthenticationContext ctxt) est appelée à chaque requête. Pour effectuer un traitement uniquement lors de la soumissions d'information d'authentification, utilisez la méthode ctxt.isCredentialProvided().
  • Attention : La méthode login(AuthenticationContext ctxt) de chaque Authentication Handler est appelée même si une authentification a été effectuée par un handler précédent, si vous ne souhaitez pas modifier l'authentification des autres handler, utilisez la méthode ctxt.isLogged().
  • Si vous ne migrez pas votre Authentication Handler dans un module JCMS, vous devrez ajouter votre handler à la liste des handlers de l'Authentication Manager. Pour cela, faites appel à la méthode channel.addAuthenticationHandler(...) dans la classe custom.JcmsInit.