JBoss 4 et JAAS

Cette page a été rédigée il y a fort fort longtemps, et n'a pas tellement été mise à jour.

 

Vous savez, moi je ne crois pas qu'il y ait de bonne ou de mauvaise page. Moi, si je devais résumer mon wiki aujourd'hui avec vous, je dirais que c'est d'abord des rencontres. Des gens qui m'ont tendu la main, peut-être à un moment où je ne pouvais pas, où j'étais seul chez moi. Et c'est assez curieux de se dire que les hasards, les rencontres forgent une destinée... Parce que quand on a le goût de la chose, quand on a le goût de la chose bien faite, le beau geste, parfois on ne trouve pas l'interlocuteur en face je dirais, le miroir qui vous aide à avancer. Alors ça n'est pas mon cas, comme je disais là, puisque moi au contraire, j'ai pu ; et je dis merci au wiki, je lui dis merci, je chante le wiki, je danse le wiki... je ne suis qu'amour ! Et finalement, quand des gens me disent « Mais comment fais-tu pour avoir cette humanité ? », je leur réponds très simplement que c'est ce goût de l'amour, ce goût donc qui m'a poussé aujourd'hui à entreprendre une construction logicielle... mais demain qui sait ? Peut-être simplement à me mettre au service de la communauté, à faire le don, le don de soi.

Les principes de JAAS sont exposés dans l’article Sécurité des EJB avec JAAS.

Nous allons à présent détailler la façon de développer un module personnalisé, qui travaille avec une classe Principal personnalisée, et nous verrons les éléments spécifiques à JBoss.

Développement d’un module client

Le rôle de ce module est de transmettre les informations de login, de password et d’éventuelles autres informations au serveur.

ClassesJaasSwClient.png

Ce module doit implémenter l’interface javax.security.auth.spi.LoginModule.

package fr.sewatech.j2ee.security.client;

import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.jboss.security.SecurityAssociation;

public class SwClientLoginModule implements LoginModule {

   private Subject subject;
   private CallbackHandler callbackHandler;
   private Map sharedState;
   private Map options;
   private boolean loginOk = false;
   private boolean commitOk = false;
   private String username;
   private char[] password;
   private SwPrincipal userPrincipal;
   private Object userCredential;

   public SwClientLoginModule() {
       super();
   }

   ...
}

Les méthodes à implémenter sont :

  • initialize : initialise le module avec le sujet qui cherche à acquérir une nouvelle identité, le gestionnaire de callback qui va fournir le login et le mot de passe ; la méthode reçoit aussi 2 autres paramètres que nous n’exploiterons pas : un état, commun aux différents modules et des options saisie dans la configuration du module.

   public void initialize(Subject subject, CallbackHandler handler, Map<String, ?> state, Map<String, ?> options) {
       this.subject = subject;
       this.callbackHandler = handler;
       this.sharedState = state;
       this.options = options;
   }
  • login : demande le login et le mot de passe au gestionnaire de callback puis les valide ; dans notre cas, aucune vérification n’est faite.

   public boolean login() throws LoginException {
       if (callbackHandler == null)
           throw new LoginException(
                   "Erreur d'initialisation : pas de callback handler");

       // Initialisation du callback demanant un nom et un mot de passe
       Callback[] callbacks = new Callback[2];
       callbacks[0] = new NameCallback("user name: ");
       callbacks[1] = new PasswordCallback("password: ", false);

       try {
           callbackHandler.handle(callbacks);
           username = ((NameCallback) callbacks[0]).getName();
           char[] tmpPassword = ((PasswordCallback) callbacks[1])
                   .getPassword();
           if (tmpPassword == null) {
               tmpPassword = new char[0];
           }
           password = new char[tmpPassword.length];
           System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
           ((PasswordCallback) callbacks[1]).clearPassword();
           userCredential = password;

       } catch (java.io.IOException ioe) {
           throw new LoginException(ioe.toString());
       } catch (UnsupportedCallbackException uce) {
           throw new LoginException(
                   "Erreur d'initialisation : mauvais callback handler");
       }

       loginOk = true;
       return true;
   }
  • commit : une fois que tous les modules ont approuvé le login et le mot de passe, les méthodes commit sont appelées pour la construction des objets principal ; dans notre cas, nous instantions un objet SwPrincipal.

   public boolean commit() throws LoginException {
       if (loginOk == false) {
           return false;
       } else {
           userPrincipal = new SwPrincipal(username);
           if (!subject.getPrincipals().contains(userPrincipal))
               subject.getPrincipals().add(userPrincipal);
           if (!subject.getPublicCredentials().contains(userCredential))
               subject.getPublicCredentials().add(userCredential);

           // Spécifique JBoss
           SecurityAssociation.pushSubjectContext(subject, userPrincipal, userCredential);

           username = null;
           password = null;

           commitOk = true;
           return true;
       }
   }
  • abort : réinitialise les champs en cas d’échec.

   public boolean abort() throws LoginException {
       if (loginOk == false) {
           return false;
       } else if (loginOk == true &amp;&amp; commitOk == false) {
           loginOk = false;
           username = null;
           password = null;
           userPrincipal = null;
       } else {
           logout();
       }
       return true;
   }
  • logout : réinitialise les champs en cas de déconnexion

   public boolean logout() throws LoginException {
       subject.getPrincipals().remove(userPrincipal);
       loginOk = false;
       username = null;
       password = null;
       userPrincipal = null;
       return true;
   }

Une partie spécifique à JBoss a été insérée dans la méthode commit(), sans laquelle les informations de login et password ne sont pas transmises aux modules du serveur.

Cette classe crée des objets de type SwPrincipal, qui seront transmis au serveur. Les objets doivent au moins avoir le login, il est possble d’ajouter des information spécifiques (mot de passe, nom du poste de travail,…​).

package fr.sewatech.j2ee.security.common;

import java.io.Serializable;
import java.security.Principal;

public class SwPrincipal implements Principal, Serializable {

   private String name;

   public SwPrincipal(String username) {
       super();
       this.name = username;
   }

   public String getName() {
       return name;
   }

   public String toString() {
       return ("Principal:  " + name);
   }

   public boolean equals(Object o) {
       if (o == null)
           return false;
       if (this == o)
           return true;
       if (!(o instanceof Principal))
           return false;
       Principal that = (Principal) o;
       if (this.getName().equals(that.getName()))
           return true;
       return false;
   }

   public int hashCode() {
       return name.hashCode();
   }
}

Développement d’un module serveur

Ce module doit implémenter l’interface javax.security.auth.spi.LoginModule, comme pour le module client. Il doit implémenter les mêmes méthodes.

package fr.sewatech.j2ee.security.server;
...
public class SwServerLoginModule implements LoginModule {
    ...
}
  • login

   public boolean login() throws LoginException {
       if (callbackHandler == null)
           throw new LoginException(
                   "Erreur d'initialisation : pas de callback handler");

       // Initialisation du callback demanant un nom et un mot de passe
       Callback[] callbacks = new Callback[] {
               new NameCallback("user name: "),
               new PasswordCallback("password: ", false),
               new SecurityAssociationCallback() };

       try {
           callbackHandler.handle(callbacks);
           username = ((NameCallback) callbacks[0]).getName();
           char[] tmpPassword = ((PasswordCallback) callbacks[1])
                   .getPassword();
           if (tmpPassword == null) {
               tmpPassword = new char[0];
           }
           password = new char[tmpPassword.length];
           System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
           ((PasswordCallback) callbacks[1]).clearPassword();
       } catch (java.io.IOException ioe) {
           throw new LoginException(ioe.toString());
       } catch (UnsupportedCallbackException uce) {
           throw new LoginException(
                   "Erreur d'initialisation : mauvais callback handler");
       }

       loginOk = true;
       return true;
   }
  • commit

   public boolean commit() throws LoginException {
       if (loginOk == false) {
           return false;
       } else {
           Set<Principal> principals = subject.getPrincipals();
           userPrincipal = new SwPrincipal(username);
           if (!principals.contains(userPrincipal)) {
               principals.add(userPrincipal);

               Group roles = new SwGroup("Roles");
               Group group = new SwGroup("sewa");
               group.addMember(userPrincipal);
               roles.addMember(group);
               principals.add(roles);
           }

           username = null;
           password = null;

           commitOk = true;
           return true;
       }
   }
  • initialize, logout et abort sont identiques au module client La principale différence réside dans le fait que c’est le serveur d’application qui gère le CallbackHandler. Les seuls callback qui sont obligatoirement gérés sont le NameCallback et le PasswordCallback. Dans le cas de JBoss, un SecurityAssociationCallback permettrait de récupérer le Principal transmis par le client, mais de façon strictement spécifique à JBoss.

Pour approuver le login, il faut affecter l’objet principal à un rôle autorisé aux EJB. Pour gérer les rôles, nous avons développé une classe qui implémente l’interface Group.

package fr.sewatech.j2ee.security.server;

import java.security.Principal;
import java.security.acl.Group;
import java.util.*;

public class SwGroup implements Group {

   private String name;
   private Map members;

   public SwGroup(String name){
       super();
       this.name = name;
       members = new HashMap();
   }

   public boolean addMember(Principal user) {
       boolean isMember = members.containsKey(user.getName());
       if( !isMember )
           members.put(user.getName(), user);
       return !isMember;
   }

   public boolean removeMember(Principal user) {
       return (members.remove(user.getName()) != null);
   }

   public boolean isMember(Principal member) {
       return members.containsKey(member.getName());
   }

   public Enumeration<? extends Principal> members() {
       return Collections.enumeration(members.values());
   }

   public String getName() {
       return name;
   }

   public String toString()
   {
      StringBuffer tmp = new StringBuffer(getName());
      tmp.append("(members:");
      Iterator iter = members.keySet().iterator();
      while( iter.hasNext() )
      {
         tmp.append(iter.next());
         tmp.append(',');
      }
      tmp.setCharAt(tmp.length()-1, ')');
      return tmp.toString();
   }
}