Vieux 13/08/2004, 09h30   #1 (permalink)
Invité régulier
 
Date d'inscription: septembre 2002
Messages: 11
Par défaut [TOMCAT] [JAAS] Extension de LoginModule dans Tomcat

Bonjour,

je cherche désespérément une solution à l'intégration de JAAS dans Tomcat. J'ai développé ma propre classe d'authentification (LoginModule), authentification basée sur un schéma de ma base de données (JDBC).
Le test en mode console fonctionne, et j'aimerai à présent intégrer tout cela à tomcat, afin de protéger mes pages jsp et mes servlet.

Problème:
Je ne comprend pas comment le lien est effectué entre Tomcat et mon LoginModule.

J'ai effectué le paramétrage du fichier jaas.config, et l'ai installé dans le répertoire conf de tomcat.
Code :
 
GestLicJAAS {
   RdbmsLoginModule required debug="true" 
       url="jdbc:microsoft:sqlserver://SQL:1433;DatabaseName=test;User=sa;Password=admin" 
       driver="com.microsoft.jdbc.sqlserver.SQLServerDriver"
       table="login"
       userField="login"
       passField="motdepasse"
       nameField="nom"
       fornameField="prenom"
       insertPerm="insertion"
       deletePerm="suppression"
       updatePerm="miseajour"
       selectPerm="visu";
};
 
Puis, configuration du fichier context.xml pour ajouter un module Realm
Code :
 
<Realm className="org.apache.catalina.realm.JAASRealm"
	appname="GestLicJAAS"
	userClassNames="com.actemium.lille.gestlic.security.RDBPrincipal"
	roleClassNames="com.actemium.lille.gestlic.security.RDBCredential"
/>  
 
Enfin, j'ai ajouté les security constraints dans le fichier web.xml
Code :
 
<security-constraint>
  	<display-name>WebApp Administration</display-name>
	<web-resource-collection>
		<web-resource-name>WebApp Admin</web-resource-name>
		<url-pattern>/*</url-pattern>
		<http-method>DELETE</http-method>
		<http-method>GET</http-method>
		<http-method>POST</http-method>
		<http-method>PUT</http-method>
	</web-resource-collection>
	<auth-constraint>
		<role-name>administrator</role-name>
		<role-name>other-role</role-name>
	</auth-constraint>
 
  </security-constraint>
  
  <login-config>
  	<auth-method>FORM</auth-method>
  	<realm-name>GestLicJAAS</realm-name>
  	<form-login-config>
  		<form-login-page>
  			/login.jsp
  		</form-login-page>
  		<form-error-page>
  			/error.jsp
  		</form-error-page>
  	</form-login-config>
  </login-config>
  
  <security-role>
  	<role-name>administrator</role-name>
  </security-role>
  <security-role>
	<role-name>other-role</role-name>
  </security-role>
 

Le déploiement fonctionne.
A la connexions sur la page, j'obtiens le message d'erreur "Impossible de trouver une configuration de connexion".

J'ai cherché au niveau des forum sans grand succés, les tutorats sur JAAS sont nombreux, mais l'intégration dans Tomcat est quelque peu trouble.

Un coup de main serait sympa.

Merci.
GiHe est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 13/08/2004, 09h36   #2 (permalink)
Invité régulier
 
Date d'inscription: septembre 2002
Messages: 11
Par défaut

bonjour,

un petit détail, j'ai effectué aussi la relation entre la JVM et le fichier de configuration de JAAS (jaas.config) depuis le script de lancement de Tomcat.

Code :
 
rem JAVA_OPTS pour TOMCAT et JASS Realms
SET JAVA_OPTS=-DJAVA_OPTS=-Djava.security.auth.login.config==%CATALINA_HOME%/conf/jaas.config
 
GiHe est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 13/08/2004, 15h56   #3 (permalink)
Invité régulier
 
Date d'inscription: septembre 2002
Messages: 11
Par défaut

ok, solution trouvée, dur dur !!!! 8)

je donne la procédure détaillée, histoire que d'autres ne mettent pas autant de temps pour mettre en place JAAS sous Tomcat.

1. Développer un module implémentant LoginModule, il faut aussi une classe Principal, et Role.

voir tutorial suivant
http://forum.java.sun.com/thread.jsp?thread=233317&forum=60&message=1216065

Soit :

Fichier RDBLoginmodule
Code :
 
 
package my;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
 
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
 
public class RDBLoginModule implements LoginModule {
    
    // Etat initial, nécessaire par l'implements
    CallbackHandler callbackHandler;										// Chargé de l'interface avec l'utilisateur ou tout autre moyen d'authentification
    Subject subject;														// Informations concernant l'identification (princiapl, credential, nom, ...)
    Map sharedState;														// Informations partagées entre les différents LoginModule
    Map options;															// Options complémentaires du fichier de configuration, ici, l'URL et le driver d'accès à la base de données
    
    // Etat temporaire, stockage temporaire des informations d'identification
    // Une personne peut être identifié par son nom, N°= sécu, ... == Tableau
    ArrayList tempCredentials;												// Nom ou clé publique
    ArrayList tempPrincipals;												// mot de passe ou clé privée
    
    // Statut d'authentification
    boolean success;														// true si ok !
    
    // Options de configuration récupérées dans le fichier de configuration
    String jdbcUrl;															// URL d'accès à la base de données
    String jdbcDriver;														// Pilote d'accès à la base de données
    String jdbcTable;														// Table contenant les informations de connexion
    String userField;														// Champs contenant les informations du nom/login de l'utilisateur
    String passField;														// Champs contenant le mot de passe de l'utilisateur
    String nameField;														// Nom de l'utilisateur
    String fornameField;														// Prénom de l'utilisateur
    String insertPerm;														// Champs contenant la valeur de la permission d'insérer
    String deletePerm;														// Champs contenant la valeur de la persmission de supprimer
    String updatePerm;														// Champs contenant la valeur de la permission de mettre à jour
    String selectPerm;														// Champs contenant la valeur de la permission de visualiser les données
    
    private boolean commited;
   
    public RDBLoginModule() {
        tempCredentials = new ArrayList();
        tempPrincipals = new ArrayList();
        
        success = false;
        
        jdbcUrl = null;
        jdbcDriver = null;
        jdbcTable = null;
        userField = null;
        passField = null;
        nameField = null;
        fornameField = null;
        insertPerm = null;
        deletePerm = null;
        updatePerm = null;
        selectPerm = null;
        
        callbackHandler = null;
        subject = null;
        sharedState=null;
        options=null;
        System.out.println("depuis construct RDBLoginmodule");
        
        commited = false;
    }
 
    public boolean abort() throws LoginException {
        success = false;
        
        tempPrincipals.clear();
        tempCredentials.clear();
        
        if (callbackHandler instanceof PassiveCallbackHandler) 
            ((PassiveCallbackHandler)callbackHandler).clearPassword();
        
        logout();
        
        return true;
    }
 
    public boolean commit() throws LoginException {
        if (success) {
            try {
                subject.getPrincipals().addAll(tempPrincipals);
                subject.getPrincipals().add(new RDBRolePrincipal("admin"));
            } catch (Exception e) {
                e.printStackTrace();
                throw new LoginException(e.getMessage());
            }
        }
        commited=true;
        return true;
    }
 
    public boolean login() throws LoginException {
        // Y a-t-il bien un CallbackHandler initialisé ?
        if (callbackHandler == null) {
            throw new LoginException("Erreur: pas de callback !");
        }
        try {
            // initialisation des callbacks
            Callback[] callbacks = new Callback[]{
                    // Callback pour retrouver les informations de Nom, l'argument est le prompt à afficher
                    new NameCallback("Nom:"),
                    // Callback pour le mot de passe, prompt et true pour afficher le mot de passe en le tapant
                    new PasswordCallback("Mot de passe:",false)
            };
            // Appel de la méthode handle pour récupérer les informations d'identification
            // La méthode dce récupération ne nous interesse pas ici, seul nous intéresse la récupération
            // du user et du mot de passe ou des clés
            callbackHandler.handle(callbacks);  
            
            // Récupération des informations
            // L'ordre des données est donné plus haut
            String userName = ((NameCallback)callbacks[0]).getName();
            String password = new String(((PasswordCallback)callbacks[1]).getPassword());
            
            // Clear du password dans le callbacks
            ((PasswordCallback)callbacks[1]).clearPassword();
            
            // Validation des informations 
            success = rdbValidate(userName, password);
            
            // Annulation des callbacks
            callbacks[0]=null;
            callbacks[1]=null;
            
            if (!success) {
                throw new LoginException("Authentification incorrecte !");
            }
            return success;    
        } catch (Exception ex) {
            
        }
        return false;
    }
 
    public boolean logout() throws LoginException {
        // clear des informations utilisateur et autorisations
        tempPrincipals.clear();
        tempCredentials.clear();
        if (callbackHandler instanceof PassiveCallbackHandler)
            ((PassiveCallbackHandler)callbackHandler).clearPassword();
        // Clear des informations enregistrées dans le Subject
        Iterator it = subject.getPrincipals().iterator();
        while (it.hasNext()) {
            RDBPrincipal p = (RDBPrincipal)it.next();
            subject.getPrincipals().remove(p);
        }
        it = subject.getPublicCredentials().iterator();
        while (it.hasNext()) {
            RDBCredential c = (RDBCredential)it.next();
            subject.getPublicCredentials().remove(c);
        }
        return true;
    }
 
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState,
            Map options) {
        
        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.sharedState = sharedState;
        this.options = options;
        // récupération et initialisation des options
        jdbcUrl = (String) options.get("url");
        jdbcDriver = (String) options.get("driver");
        jdbcTable = (String) options.get("table");
        userField = (String) options.get("userField");
        passField = (String) options.get("passField");
        nameField = (String) options.get("nameField");
        fornameField = (String) options.get("fornameField");
        insertPerm = (String) options.get("insertPerm");
        deletePerm = (String) options.get("deletePerm");
        updatePerm = (String) options.get("updatePerm");
        selectPerm = (String) options.get("selectPerm");
        System.out.println("depuis init RDBLoginmodule");
    }
    
    private boolean rdbValidate(String userName, String password) throws LoginException {
        System.out.println("depuis validate RDBLoginmodule");
        Connection con;
        // Query avec les champs personnalisés
        String query = "SELECT * FROM " + jdbcTable + " where " + userField + "='" + userName + "'";
        Statement stmt;
        RDBPrincipal  p = null;
        RDBCredential c = null;
        boolean passwordMatch = false;
        
        try {
            Class.forName(jdbcDriver);
        } catch (ClassNotFoundException cnfex) {
            throw new LoginException("Pilote JDBC introuvable, vérifier votre CLASSPATH");
        }
        try {
            // Effectue la connexion à la base de données, requête sur l'utilisateur
            // récupération des données
            con = DriverManager.getConnection(jdbcUrl,"sa","admin");
            stmt = con.createStatement();
            ResultSet rs = stmt.executeQuery(query);
            String dbPassword = null, dbFname = null, dbLname = null;
            String updatePerm = null, deletePerm = null, insertPerm=null, selectPerm=null;
            boolean isEqual   = false;
            while (rs.next()) {
                // Test en dur !!!
                dbPassword = "jamaïc";
                dbFname = "bob";
                dbLname = "marley";
                insertPerm = "1";
                deletePerm = "1";
                updatePerm = "1";
                selectPerm = "1";
 
            }
            // Password null == utilisateur introuvable
            if (dbPassword==null) 
                throw new LoginException("Utilisateur ou mot de passe incorrect !");
            passwordMatch = password.equals(dbPassword);
            
            // Principals et Credentials
            if (passwordMatch) {
                c = new RDBCredential();
                c.setProperty("insertPerm",insertPerm);
                c.setProperty("deletePerm",deletePerm);
                c.setProperty("updatePerm",updatePerm);
                c.setProperty("selectPerm",selectPerm);
                this.tempCredentials.add(c);
                this.tempPrincipals.add(new RDBPrincipal(dbFname + " " + dbLname));
            }
            
            rs.close();
            stmt.close();
            con.close();
        } catch (SQLException sqlex) {
            sqlex.printStackTrace() ;
            System.out.println("exception capturée SQL");
            throw new LoginException(sqlex.getMessage());
        }
        return passwordMatch;
    }
    static public void main(String args[]) throws Exception{
 
        LoginContext ctx = new LoginContext("RDBLogin");
        System.out.println("depuis constructeur RDBLoginmodule");
        ctx.login();
    }
}
 
Fichier RDBRolePrincipal
Code :
 
 
package com.actemium.lille.gestlic.security;
public class RDBRolePrincipal extends RDBPrincipal {
    
    public RDBRolePrincipal(String s) {
        super(s);
    }
}
 
 
Fichier RDBPrincipal:
Code :
 
 
package my;
 
import java.io.Serializable;
import java.security.Principal;
 
public class RDBPrincipal implements Principal, Serializable {
    private String name;
    
    public RDBPrincipal(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    public boolean equals (Object another) {
        try {
            RDBPrincipal pm = (RDBPrincipal)another;
            return pm.name.equalsIgnoreCase(name);
        } catch(Exception e){
            return false;   
        }
    }
    public int hashCode() {
        return name.hashCode();
    }
    public String toString() {
        return name;
    }
}
 
Compiler le tout et plasser le répertoire racine du package dans le répertoire class du répertoire server de tomcat

1 bis. Créer une base de données de test et une table avec un schéma comparable à ce dernier :

Code :
 
CREATE TABLE [dbo].[login] (
	[idlogin] [int] NOT NULL ,
	[login] [varchar] (50) COLLATE French_CI_AS NULL ,
	[motdepasse] [varchar] (50) COLLATE French_CI_AS NULL ,
	[nom] [varchar] (50) COLLATE French_CI_AS NULL ,
	[prenom] [varchar] (50) COLLATE French_CI_AS NULL ,
	[insertion] [int] NULL ,
	[suppression] [int] NULL ,
	[miseajour] [int] NULL ,
	[visu] [int] NULL 
) ON [PRIMARY]
GO
 
2. Pour la validation à travers une base de données, il faut utiliser une méthode de validation qui effectue la connexion à la base et requête sur la table des utilisateurs. Le mot de passe de la table des utilisateurs est comparé avec le mot de passe fournis par le CallbackHandler de Tomcat.

3. Créer un fichier de config jaas :
les variables table, url, driver, userfield, passfield, ... permettent de rendre généric la méthode de connexion quelque soit le schéma de la base utilisé.

Code :
 
RDBLogin {
   my.RDBLoginModule required debug="true" 
       url="jdbc:microsoft:sqlserver://SQL:1433;DatabaseName=test;User=sa;Password=pass" 
       driver="com.microsoft.jdbc.sqlserver.SQLServerDriver"
       table="login"
       userField="login"
       passField="motdepasse"
       nameField="nom"
       fornameField="prenom"
       insertPerm="insertion"
       deletePerm="suppression"
       updatePerm="miseajour"
       selectPerm="visu";
};
 
Enregistrer ce fichier dans un répertoire sous jaas.config

3. Modification du fichier server.xml :

Modifier la section Realm du fichier serv.xml

Code :
 
<Realm className="org.apache.catalina.realm.JAASRealm" debug="3" appName="RDBLogin" 
userClassNames="my.RDBPrincipal" 
roleClassNames="my.RDBRolePrincipal">
</Realm>
 
appName doit correspondre au nom donné dans le fichier jaas.config

4. Créer ou mettre à jour la variable d'environnement JAVA_OPTS:

set JAVA_OPTS=-Djava.security.auth.login.config=g:/www/jaas.config

depuis une session Dos et lancer sous la session dos la commande startup depuis le répertoire bin de Tomcat.
Cette variable est importante et permet de donner le chemin d'accès au fichier de condfiguration de JAAS.

5. Lancer un navigateur Web et aller dans la section administration de Tomcat.
Le gestionnaire de sécurité est à présent basé sur votre classe LoginModule.
Ici, la gestion se fait depuis la base de données.
Créer donc un utilisateur dans la table des utilisateurs.

ATTENTION: depuis le LoginModule, dans la méthode commit, il faut ajouter un
Code :
 
 subject.getPrincipals().add(new RDBRolePrincipal("admin"));
 
pour permettre à Tomcat de connaître le rôle de la personne connecté.

Voilà, j'espère que ce n'était pas trop confus, ca l'est peut-être encore un peu pour moi.

Je début sous les Servlet, JSP, Hibernate et Tomcat et met en place une application minimale de test pour mettre tout cela en oeuvre.

Ca pourrait peut-être donné lieu à un tutorial si cela interresse quelques personnes.

@+
GiHe est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 12/10/2005, 10h03   #4 (permalink)
Membre régulier
 
Date d'inscription: avril 2003
Âge: 32
Messages: 122
Envoyer un message via MSN à nighma Envoyer un message via Skype™ à nighma
Par défaut

Salut GiHe,

Je cherche exactement la même chose pour le moment afin de faire un squelette pour toutes mes applications. Je viens de passer plus d'une semaine a essayer mais sans grand succès pour le moment.

Dans la fin de ton explication tu dis que tu mets en place un application minimale de test pour mettre tout ca en oeuvre.

Serais-tu d'accord de partager tes sources ?

Si tu es d'accord peux-tu me les envoyer par mail ?

Merci
nighma est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 12/10/2005, 16h40   #5 (permalink)
Membre régulier
 
Date d'inscription: février 2003
Messages: 136
Par défaut

salut nighma,
la solution de GiHe est élégante, et est un bon point de départ.
je suis parti sur la même voie et ai crée un projet open source appelé jGuard (http://jguard.sourceforge.net).

GiHe a rejoint le projet pour implémenter ensemble une solution plus aboutie.

je t'incite donc à jeter un oeil à jGuard.

cordialement,

Charles(jGuard team).
diabolo512 est déconnecté   Envoyer un message privé Réponse avec citation
Vieux 13/10/2005, 07h58   #6 (permalink)
Membre régulier
 
Date d'inscription: avril 2003
Âge: 32
Messages: 122
Envoyer un message via MSN à nighma Envoyer un message via Skype™ à nighma
Par défaut

Merci Charles,

Je vais de ce pas y jeter un oeil.

Je pense que c'est une bonne initiative, ça manquait.

Seb
nighma est déconnecté   Envoyer un message privé Réponse avec citation
NEWS JAVAFAQs JAVATUTORIELS JAVAJAVASEARCHSOURCESLIVRESOUTILS, EDI & APIBLOGDISCUSSIONSTV

Réponse

Précédent   Forum des professionnels en informatique > Java > Serveurs, conteneurs, et Java EE > Tomcat



Outils de la discussion

Règles de messages
Vous ne pouvez pas créer de nouvelles discussions
Vous ne pouvez pas envoyer des réponses
Vous ne pouvez pas envoyer des pièces jointes
Vous ne pouvez pas modifier vos messages

Les balises BB sont activées : oui
Les smileys sont activés : oui
La balise [IMG] est activée : oui
Le code HTML peut être employé : non
Trackbacks are non
Pingbacks are non
Refbacks are non

Navigation rapide


Fuseau horaire GMT +2. Il est actuellement 22h06.


Vos questions techniques : forum d'entraide Java - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Hébergement - Participez - Copyright © 2000-2009 www.developpez.com - Legal informations.