12 01 2009

Single Sign On avec Kerberos

Comment mettre en place un mécanisme d’authentification java en single sign on (SSO) ? Dans ce petit article, je décris comment nous avons procédé sur un projet en utilisant JAAS, Kerberos et Active Directory. La configuration n’est pas forcément innée donc avis aux intéressés…

Besoin

Le besoin est simple : avoir une application java client lourd avec un système d’authentification Single Sign On basé sur Kerberos et Active Directory. On se base donc sur l’infrastructure Microsoft (AD, Windows qui utilise Kerberos pour l’authentification depuis la version 2000) et la sécurité java (JAAS).

Présentation des intervenants

Kerberos

Kerberos Kerberos vient du grec et signifie “Cerbère”, le chien à trois têtes gardien de la porte des enfers (mythologie grecque), joliment trouvé pour un protocole d’authentification ! Je ne rentrerai pas dans les détails du protocol, wikipedia le décrivant mieux que moi. Sachez simplement que le protocole est basé sur une notion de tickets et qu’une fois authentifié sur votre poste via l’AD, vous disposez d’un ticket que nous allons pouvoir réutiliser pour vous authentifier sur d’autres applications / serveurs…

JAAS

Java Authentication and Authorization Service (JAAS pour les intimes) est l’API sécurité de java. Elle fournit comme son nom l’indique des mécanismes d’authentification et d’habilitation. Nous allons nous concentrer sur la partie authentification. Je vous recommande vivement ce pdf si vous souhaitez une description claire et complète sur l’authent (en tout 10 documents expliquent en long en large et en travers l’API JAAS : très enrichissant). JAAS

Mise en place

LoginContext, LoginModule, CallBackHandler…

Si vous avez lu les documents que je citais ci-dessus, passez à la partie suivante. Sinon, voilà un petit résumé des classes dont nous allons avoir besoin :

Jaas classes

  • LoginContext : cette classe est la classe principale permettant d’authentifier un utilisateur (méthode login). Elle est indépendante du mécanisme d’authentification car elle s’appuie sur des LoginModule qui, eux, varient en fonction de la technologie sous-jacente.
  • LoginModule : Il s’agit d’une interface que les différents mécanismes d’authentification (Ldap, Kerberos, …) doivent implémenter pour authentifier un utilisateur (méthode login également).
  • CallBackHandler : Le CallbackHandler est une interface dont votre propre implémentation doit être passée au LoginModule utilisé. Il permet d’interagir avec l’application pour récupérer les crédentials de l’utilisateur (par exemple récupérer le login/password au travers d’une interface graphique ou d’un prompt…).

Typiquement vous aurez :

LoginContext lc = new LoginContext("MyLoginModuleName", new MyCallbackHandler(...));
try {
   lc.login();
} catch (LoginException le) {
  // do what you want
}

Lorsque le LoginContext est instancié, il va récupérer le(s) LoginModule(s) (classe d’implémentation + paramètres) correspondant au nom “MyLoginModuleName” dans un fichier de configuration. Il l’initialise.
Lorsque le login est fait, le context parcours tous les modules associés et effectue un login dans chacun d’eux. Si le module en a besoin, il fera appel au CallbackHandler associé pour récupérer les credentials et faire l’authentification.

Je vais entrer dans les détails dans la partie suivante.

Dans le vif du sujet

Configuration de JAAS

On commence par créer un fichier jaas.conf dont on référence le chemin avec la propriété java.security.auth.login.config (par exemple en lançant l’application avec java -Djava.security.auth.login.config=path/to/config ou System.setProperty(java.security.auth.login.config, "path/to/config")).

Voilà le contenu de ce fichier :

KerberosSecurityAuthSSO {
  com.sun.security.auth.module.Krb5LoginModule required useTicketCache="true" debug="false";
};

KerberosSecurityAuth {
  com.sun.security.auth.module.Krb5LoginModule required useTicketCache="false" debug="false";
};

KerberosSecurityAuth[SSO] étant les noms des modules que l’on utilisera lorsqu’on déclarera notre LoginContext.

Krb5LoginModule étant l’implémentation choisie (dans notre cas Kerberos 5). Il est possible de mettre plusieurs implémentations pour un même module, par exemple en plus du Kerberos, on aurait pu mettre du Ldap (avec LdapLoginModule)

Suivent ensuite les options :

  • required permet de préciser que le module est requis pour l’authentification.
  • useTicketCache permet de préciser qu’on souhaite réutiliser le TGT kerberos, donc lorsqu’on le set à true, on peut espérer avoir du SSO.
  • debug permet comme son nom l’indique de fournir des traces de l’authentification.
  • … de nombreuses autres options sont dispo et documentées ici.

Configuration de kerberos

De même que jaas, on a besoin d’un fichier de configuration pour kerberos. On créé donc un fichier krb5.conf dont on référence le chemin avec la propriété java.security.krb5.conf. Si cette propriété n’est pas mise, il ira dans java-home/lib/security puis dans divers répertoires du système s’il ne trouve pas.

Ce fichier contient :

[libdefaults] default_realm = MYDOMAIN.FR dns_fallback dns_lookup_kdc dns_lookup_realm [realms] MYDOMAIN.FR = { kdc = mydomain.fr }

C’est donc là que vous référencez votre domaine AD (ou plusieurs) et le(s) kdc associé(s).

Authentification SSO

On va maintenant arrêter de jouer avec les fichiers de configuration et coder un peu.

// cas SSO : utilisation du module KerberosSecurityAuthSSO
LoginContext lc = new LoginContext(“KerberosSecurityAuthSSO”, new SiouxCallbackHandler());
// cas non SSO : utilisation du module KerberosSecurityAuth
// LoginContext lc = new LoginContext(“KerberosSecurityAuth”, new SiouxCallbackHandler());

try {
  // authentification
  lc.login();

  // recuperation du Subject
  Subject subject = lc.getSubject();

  // recuperation du Principal
  Iterator it = subject.getPrincipals().iterator();
  Principal pr = null;
  while (it.hasNext()) {
    pr = (Principal) it.next();
  }

  // on retourne le nom du principal (login@domaine)
  if (pr != null) {
    return pr.getName();
  }
  return null;

} catch (LoginException le) {
  // ...
}

Le CallbackHandler ne devrait pas être utilisé dans ce cas. Étant donné que l’on récupère le ticket Kerberos, le LoginModule n’en a pas besoin puisqu’il a déjà les credentials.

Authentification non SSO

Si par contre nous avions utilisé le module “KerberosSecurityAuth” (avec useTicketCache=false), le module va avoir besoin du SiouxCallbackHandler que voilà :

public class SiouxCallbackHandler implements CallbackHandler {

  public SiouxCallbackHandler (/* parametres... */) {
    /* init ... */
  }

  public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
    String username;
    String domain;
    String password;

    // Affichage d'une boite de dialogue ...
    SecurityLoginDialog loginDialog = new SecurityLoginDialog();
    loginDialog.pack();
    loginDialog.setVisible(true);

    // ... pour récupérer le user, domaine et password
    username = loginDialog.getUser();
    password = loginDialog.getPassword();
    domain = loginDialog.getDomain();

    // On met à jour les callbacks avec les infos récupérées (login@domain et password)
    for (int i = 0; i < callbacks.length; i++) {
      Callback cb = callbacks[i];
      if (cb instanceof NameCallback) {
        username = username.concat("@");
        username = username.concat(domain);
        ((NameCallback) cb).setName(username);
      } else if (cb instanceof PasswordCallback) {
        // password is a char[]
        ((PasswordCallback) cb).setPassword(password.toCharArray());
      } else {
        throw new UnsupportedCallbackException(cb, "Unrecognized Callback");
      }
    }
  }
}

Ainsi le Krb5LoginModule aura les credentials dont il a besoin pour effectuer l'authentification (Aucun mot de passe ne circule en clair sur le réseau, de par l'utilisation de Kerberos et non LDAP).

Conclusion

Il y a pas mal de documentation autour de ces sujets (cf. les divers liens de l'article) et moyen de faire beaucoup plus que ce que j'ai montré. Mon exemple est relativement simple et vous fournira les bases d'une application sécurisée avec Kerberos (SSO ou non).
Un document de sun complète mon article avec l'utilisation de GSS-Api pour faire de la délégation de credentials et donc appeler des services avec son compte sans ré-authentification.

Leave a comment

Your comment

CAPTCHA image