Glassfish et OpenID

Lorsqu’on développe une application, l’un des premiers challenge est de gérer la sécurité. Si les autorisations sont souvent internes aux applications ou aux sites web, quoiqu’elle peuvent correspondre à des rôles ou groupes dans l’entreprise, il en est une toute autre histoire de l’authentification. En effet pour identifier un utilisateur, on préfère, la plupart du temps, s’appuyer sur une session globale, un Single Sign-On ou SSO. Cela évite aux utilisateurs de conserver plusieurs mots de passe ou de se connecter à longueur de journée. 

Avec les offres « Software As a Service » mais surtout Yahoo, Google et maintenant Facebook et LinkedIn, le débat s’est déplacé. Là où il fallait avant intégrer des applications à des LDAP d’Enterprise et à des solutions spécifiques comme OAM ou Netegrity Siteminder, OpenID et OAuth offrent désormais des mécanismes standards qui permettent de déléguer les opérations d’authentification et l’accès à des données de ces sites. Ainsi les mots de passe ne sont plus éparpillés dans la nature ; un utilisateurs peut accéder via son compte Google ou Facebook aux informations des entreprises qui tirent ainsi partie des réseaux sociaux. C’est d’autant plus intéressant que de plus en plus d’entreprise délègueront la gestion de leurs domaines et de leurs identités à des fournisseurs comme Google, Microsoft ou Verisign.

Mais reprenons notre casquette de fournisseur d’infrastructure pour quelques instants et répondons à la question, comment développer des applications qui s’appuient sur la sécurité standard d’un serveur Java EE (JAAS) comme Glassfish tout en délégant l’authentification à des serveurs OpenID ?
A vrai dire, il n’y a rien de bien sorcier à trouver la réponse à cette question ; tous les morceaux du puzzle sont disponibles sur Internet, il suffit juste de le reconstituer…

Security Authentication Module (SAM) et JSR-196

« Quel mécanisme faut-il utiliser ? »; c’est en substance la question la plus délicate à laquelle vous devez trouver une réponse. Dans notre cas, il s’agit de « Java Authentication Service Provider Interface for Containers » ou JSR-196. Pour mettre en oeuvre cette solution, il suffit d’écrire un SAM; tout ce que vous devez savoir est dans cet article très bien écrit et qui met en oeuvre Glassfish v2 !

openid4java-jsr196

Mais au delà de l’article en lui-même, nous avons la chance d’avoir une implémentation complète disponible en GPL et développée par Ralph Soika de la société Imixs Software Solutions GmbH. C’est surement la façon la plus simple pour commencer à intégrer Glassfish et OpenID; il suffit de récupérer l’adaptateur openid4java-jsr196 sur le site Google Code et de le mettre en oeuvre comme décrit dans l’article de son auteur. Pour se faire, procédez comme suit :

Téléchargez et installez les bibliothèques

Voici un script correspondant à l’installation de toutes les bibliothèques :

cp imixs-openid-0.0.3.jar 
     $GLASSFISH_HOME/lib

cp commons-codec-1.4/commons-codec-1.4.jar
     $DOMAIN_HOME/lib/ext

cp commons-httpclient-3.1/commons-httpclient-3.1.jar
     $DOMAIN_HOME/lib/ext

cp commons-logging-1.1.1/commons-logging-1.1.1.jar
     $DOMAIN_HOME/lib/ext

cp openid4java-0.9.5.593/openid4java-0.9.5.jar
     $DOMAIN_HOME/lib/ext

Créer une nouvelle configuration de sécurité de messages

Après avoir redémarré Glassfish, sélectionnez le menu « Security » -> « Message Security » dans la console et cliquez sur le bouton « New… »

  • Authentication Layer: HttpServlet
  • Provider ID: SunOpenID
  • Default Provider: décochée
  • Provider Type: Server
  • Class Name: org.imixs.openid.openid4java.OpenID4JavaAuthModule

Cliquez sur le bouton « Ok ». Une fois le provider créé, sélectionnez « HttpServlet -> Providers -> ImixOpenID » comme ci-dessous :

Vous pouvez ensuite ajouter les propriétés du provider comme ci-dessous :

Les propriétés indiquent :

  • debug.stage=all que les informations de debug s’affichent sur la console pour toutes les étapes
  • loginpage=/oid/faces/login.xhtml que si l’utilisateur a besoin de s’authentifier qu’il est redirigé vers la page /oid/faces/login.xhtml
  • assign.groups=Author indique qu’un utilisateur connecté fait parti du groupe Author (utilisé pour assigner les droits)

Ecran de connexion

Pour se connecter, développer une Facelet login.xhtml dans un projet ayant comme contexte /oid; voici le code correspondant qui redirige vers la page http://localhost:8080/demo/faces/index.xhtml en cas de connexion avec succès :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">

<form method="get"
action="/demo/faces/openid_login">
<f:facet name="header">
<h:outputLabel value="#{global.login_title} " />
</f:facet>
<h:panelGrid columns="2">
<h:outputLabel value="#{global.username}:" />
<h:inputText id="openid_identifier" />

<h:inputHidden id="return_to"
value="http://localhost:8080/demo/faces/index.xhtml" />

</h:panelGrid>
<input type="submit" value="#{global.login}" />


<!-- BEGIN ID SELECTOR -->
<script type="text/javascript" id="__openidselector"
src="https://www.idselector.com/selector/e0ed3a269b77fa785de90aeaa20fa0f985746767"
charset="utf-8"></script>
<!-- END ID SELECTOR -->
</form>
</html>

Développer une page /demo/faces/index.xhtml protég
ée

Enfin pour terminer, développer dans un autre projet une facelet index.xtml avec le contexte racine /demo. Dans ce projet, faites référence au SAM enregistré dans l’étape précédente à l’aide du fichier sun-web.xml :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN"
"http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">
<sun-web-app error-url="" httpservlet-security-provider="ImixOpenID">
<context-root>/demo</context-root>
<security-role-mapping>
<role-name>org.imixs.ACCESSLEVEL.AUTHORACCESS</role-name>
<group-name>Author</group-name>
</security-role-mapping>

<class-loader delegate="true"/>
<jsp-config>
<property name="keepgenerated" value="true">
<description>Keep a copy of the generated servlet class java code.</description>
</property>
</jsp-config>
</sun-web-app>

Une fois le provider enregistré ainsi que la correspondance entre le groupe de web.xml et le role associé utilisé par le provider, il suffit d’enregistrer dans le fichier web.xml le fait que les facelets du projet, de la forme /demo/faces/*, sont protégées par le role associé :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>demo</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
<security-constraint>
<display-name>Access Manager Security Constraint</display-name>
<web-resource-collection>
<web-resource-name>AUTHENTICATED_RESOURCE</web-resource-name>
<url-pattern>/faces/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>org.imixs.ACCESSLEVEL.AUTHORACCESS</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>org.imixs.ACCESSLEVEL.AUTHORACCESS
</security-role>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<context-param>
<description>State saving 'server' (=default).</description>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
<param-value>resources.application</param-value>
</context-param>
<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
</web-app>

Exécutez l’application

Voilà, il ne reste plus qu’à exécuter la page index.xhtml après avoir créé une dépendance entre les projets demo et oid comme ci-dessous; la page de connexion s’affiche et vous redirige vers les serveurs OpenID les plus répandus :

Plus d’informations

Pour en savoir plus :

  • l’article en question contient le code source de l’implémentation du SAM dans la section conclusion.
  • L’auteur a également écrit une série d’articles sur le même sujet dont voici les parties 1, partie 2 et partie 3.
  • Si vous voulez écrire une facelet avec Oracle Enterprise Pack for Eclipse, regardez cette video sur YouTube.com
  • Par ailleurs, il existe plusieurs articles intéressants sur le principe de mise en oeuvre du SAM avec Glassfish sur ce blog ou celui-ci.
  • Enfin, vous trouverez dans les exemples fournis avec openid4java des trucs pour aller chercher, en plus de la session en elle-même plusieurs informations liées à l’utilisateur; ainsi sur Google.com, vous pourrez récupérer Email, Nom et Prénom à l’aide du code suivant lors de l’émission des requêtes suivantes :
    fetch.addAttribute("Email",
    "http://schema.openid.net/contact/email", // type URI
    true); // required
    fetch.addAttribute("firstName",
    "http://axschema.org/namePerson/first",
    true);
    fetch.addAttribute("lastName",
    "http://axschema.org/namePerson/last",
    true);