Nouveau petit tutoriel, cette fois ci nous allons voir comment permettre à nos applications dialoguant avec CAS de récupérer plus d'informations sur nos utilisateurs via des requêtes SAML.

Voici le fichier deployerConfigContext.xml au complet, je vais expliquer en détail juste après ;)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:sec="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl">

        <property name="credentialsToPrincipalResolvers">
            <list>  
                <bean class="org.jasig.cas.authentication.principal.CredentialsToLDAPAttributePrincipalResolver">
                    <property name="credentialsToPrincipalResolver">
                        <bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver"/>
                    </property>
                    <property name="filter" value="(cn=%u)"/>
                    <property name="principalAttributeName" value="cn"/>
                    <property name="searchBase" value="dc=gnagna"/>
                    <property name="contextSource" ref="contextSource"/>
                    <property name="attributeRepository" ref="attributeRepository"/>
                </bean>
            </list>
        </property>

        <property name="authenticationHandlers">
            <list>
                <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient"/>
                <bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler"/>
                <bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler" p:filter="cn=%u" p:searchBase="dc=gnagna" p:contextSource-ref="contextSource" p:ignorePartialResultException="true"/>
            </list>
        </property>
    </bean>

    <sec:user-service id="userDetailsService">
        <sec:user name="sso_admin" password="sso_admin" authorities="ROLE_ADMIN"/>
    </sec:user-service>
    
    <bean id="attributeRepository" class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao">
        <property name="contextSource" ref="contextSource"/>
        <property name="baseDN" value="ou=Users,ou=gnagna"/>

        <property name="queryAttributeMapping">
          <map>
            <entry key="username" value="cn"/>
          </map>
          </property>
          <property name="resultAttributeMapping">
          <map>
            <entry key="cn" value="cn"/>
            <entry key="displayName" value="displayName"/>
          </map>
          </property>
    </bean>

    <bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"></bean>

    <bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager"/>
    
    <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
        <property name="pooled" value="false"/>
        <property name="url" value="ldap://<adresse IP LDAP>"/>
        <property name="userDn" value="cn=sso_svc,ou=Users,ou=gnagna"/>
        <property name="password" value="B@zing@"/>
        <property name="baseEnvironmentProperties">
            <map>
                <entry key="com.sun.jndi.ldap.connect.timeout" value="3000"/>
                <entry key="com.sun.jndi.ldap.read.timeout" value="3000"/>
                <entry key="java.naming.security.authentication" value="simple"/>
            </map>
        </property>
    </bean>
</beans>

Explications

Comme expliqué dans le précédent article le bean

<bean class="org.jasig.cas.authentication.principal.CredentialsToLDAPAttributePrincipalResolver">
      <property name="credentialsToPrincipalResolver">
             <bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver"/>
      </property>
      <property name="filter" value="(cn=%u)"/>
      <property name="principalAttributeName" value="cn"/>
      <property name="searchBase" value="dc=gnagna"/>
      <property name="contextSource" ref="contextSource"/>
      <property name="attributeRepository" ref="attributeRepository"/>
</bean>

Permet de définir les rêgles de recherche dans le LDAP pour authentifier l'utilisateur, l'important ici est de le déclarer dans le XML en tout premier dans la liste. En effet CAS va valider le premier bean qui fonctionnera.

Remplacez ici la valeur du filter par l'élément du LDAP qui va servir a identifier votre utilisateur (ici "cn").

La propriété attributeRepository est aussi très importante pour la suite de l'article, gardez la en tête.

authentificationHandler

<bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler" .../>

Ce bean va permettre de vérifier les identifiants entrés par l'utilisateur pour vérifier si ils correspondent bien à ce qui a été trouvé dans le LDAP.

attributeRepository

<bean id="attributeRepository" class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao">
   <property name="contextSource" ref="contextSource"/>
    <property name="baseDN" value="ou=Users,ou=gnagna"/>
    <property name="queryAttributeMapping">
        <map>
            <entry key="username" value="cn"/>
        </map>
    </property>
    <property name="resultAttributeMapping">
        <map>
            <entry key="cn" value="cn"/>
            <entry key="displayName" value="displayName"/>
        </map>
     </property>
</bean>

Nous retrouvons ici notre attributeRepository. Cet élément est surement l'un des plus important puisqu'il va servir à faire la liaison entre les élément de notre LDAP et les élément de CAS. queryAttributeMapping permet de déterminer le champ sur lequel on va faire la requête (toujours "cn") et resultAttributeMapping de déterminer comment on va mapper les élément retournés. Ici je n'ai pris que cn et displayName mais à vous de compléter avec les différent champs que vous avez dans le LDAP.

contextSource

<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
    <property name="pooled" value="false"/>
    <property name="url" value="ldap://<adresse IP LDAP>"/>
    <property name="userDn" value="cn=sso_svc,ou=Users,ou=gnagna"/>
    <property name="password" value="B@zing@"/>
    <property name="baseEnvironmentProperties">
        <map>
            <entry key="com.sun.jndi.ldap.connect.timeout" value="3000"/>
            <entry key="com.sun.jndi.ldap.read.timeout" value="3000"/>
            <entry key="java.naming.security.authentication" value="simple"/>
        </map>
    </property>
</bean>

Ici, le contextSource ne change pas. Nous déterminons toujours les éléments nécessaires à la bonne connexion au serveur LDAP.

Il ne vous reste plus qu'à vérifier que tout fonctionne bien en redémarrant le serveur Tomcat.

Application de test en PHP

Nous allons maintenant compléter notre application PHP afin de tester si les éléments du LDAP remontent bien jusqu'à l'application. Vous pouvez reprendre l'application que nous avions fait précédemment.

Tout d'abord le code

<?php
error_reporting(-1);
// On inclue la librairie
include_once('CAS/CAS.php'); 

// Activation du mode debug
phpCAS::setDebug();

// On définit la localisation du serveur
$serveurSSO="localhost";
$serveurSSOPort=8443;
$serveurSSORacine='cas';

// On lance le client
phpCAS::client(SAML_VERSION_1_1,$serveurSSO,$serveurSSOPort,$serveurSSORacine);

// On spécifie la version SSL pour les requêtes Curl
phpCAS::setExtraCurlOption(CURLOPT_SSLVERSION,3);
// Puis le certificat du serveur CAS
phpCAS::setCasServerCACert('LabSSO.pem');

// On force l'authentification
phpCAS::forceAuthentication();

// SI l'utillisateur souhaite se déconnecter...
if (isset($_REQUEST['logout'])) {
    phpCAS::logout();
}
// Une petite page web pour montrer que tout vas bien :)
?>
<html>
    <head>
        <title>phpCAS simple client</title>
    </head>
    <body>
        <h1>Successfull Authentication!</h1>
<ul>
<?php
foreach (phpCAS::getAttributes() as $key => $value) {
    if (is_array($value)) {
        echo '<li>', $key, ':<ol>';
        foreach ($value as $item) {
            echo '<li><strong>', $item, '</strong></li>';
        }
        echo '</ol></li>';
    } else {
        echo '<li>', $key, ': <strong>', $value, '</strong></li>' . PHP_EOL;
    }
}
var_dump(phpCAS::hasAttributes());
    ?>
</ul>
        <p>the user's login is <b><?php echo phpCAS::getUser(); ?></b>.</p>
        <p>phpCAS version is <b><?php echo phpCAS::getVersion(); ?></b>.</p>
        <p><a href="?logout=">Logout</a></p>
    </body>
</html>

La première chose que j'ai ici changé c'est de demander à phpCAS de se connecter au serveur CAS via le protocole SAML (1.1).

phpCAS::client(SAML_VERSION_1_1,$serveurSSO,$serveurSSOPort,$serveurSSORacine);

Et un peu plus bas j'ai récupéré tout les attributs renvoyés par CAS puis je les ai affichés sous forme de liste.

foreach (phpCAS::getAttributes() as $key => $value) {
   ...
}

Et voici ce que vous devriez avoir si tout se passe bien, après à vous de faire marcher votre imagination pour construire une belle application autour de ça :)

Récupéation des attributs SAML avec phpCAS