diff --git a/.gitignore b/.gitignore
index ae09aba9b8e074dcf3c14bed38dc34c336d4c83a..31e3ac6d8150c272dbb855c640aaa3f34eefe3b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
 vendor/
-.idea
\ No newline at end of file
+.idea
diff --git a/Module.php b/Module.php
index b4d473d542f12285e9069d7bd8f8b0f91de28e80..1d540cb5e06e683237ca6a8145e919c343d4cba3 100644
--- a/Module.php
+++ b/Module.php
@@ -2,9 +2,18 @@
 
 namespace UnicaenAuth;
 
+use UnicaenAuth\Authentication\Adapter\Cas as CasAdapter;
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\ShibService;
+use Zend\EventManager\EventInterface;
 use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
 use Zend\ModuleManager\Feature\ConfigProviderInterface;
 use Zend\ModuleManager\Feature\ServiceProviderInterface;
+use Zend\ServiceManager\Exception\ServiceNotFoundException;
+use Zend\ServiceManager\ServiceLocatorInterface;
+use Zend\View\Helper\Navigation;
+use ZfcUser\Form\Login;
+use ZfcUser\Form\LoginFilter;
 
 /**
  * Point d'entrée du module d'authentification Unicaen.
@@ -14,7 +23,11 @@ use Zend\ModuleManager\Feature\ServiceProviderInterface;
 class Module implements AutoloaderProviderInterface, ConfigProviderInterface, ServiceProviderInterface
 {
     /**
-     *
+     * @var ModuleOptions
+     */
+    private $options;
+
+    /**
      * @return array
      * @see ConfigProviderInterface
      */
@@ -23,10 +36,7 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se
         return include __DIR__ . '/config/module.config.php';
     }
 
-
-
     /**
-     *
      * @return array
      * @see AutoloaderProviderInterface
      */
@@ -44,8 +54,6 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se
         ];
     }
 
-
-
     /**
      * This method is called once the MVC bootstrapping is complete,
      * after the "loadModule.post" event, once $application->bootstrap() is called.
@@ -54,9 +62,9 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se
      *
      * @see BootstrapListenerInterface
      */
-    public function onBootstrap(\Zend\EventManager\EventInterface $e)
-        /* @var \Zend\Mvc\MvcEvent $e */
+    public function onBootstrap(EventInterface $e)
     {
+        /* @var \Zend\Mvc\MvcEvent $e */
         $application = $e->getApplication();
         /* @var $services \Zend\ServiceManager\ServiceManager */
         $services = $application->getServiceManager();
@@ -65,63 +73,45 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se
         try {
             $authorizeService = $services->get('BjyAuthorize\Service\Authorize');
             /* @var $authorizeService \BjyAuthorize\Service\Authorize */
-            \Zend\View\Helper\Navigation::setDefaultAcl($authorizeService->getAcl());
-            \Zend\View\Helper\Navigation::setDefaultRole($authorizeService->getIdentity());
-        } catch (\Zend\ServiceManager\Exception\ServiceNotFoundException $snfe) {
+            Navigation::setDefaultAcl($authorizeService->getAcl());
+            Navigation::setDefaultRole($authorizeService->getIdentity());
+        } catch (ServiceNotFoundException $snfe) {
             // pas de module BjyAuthorize : pas d'ACL
         }
 
-        /* @var $options Options\ModuleOptions */
-        $options = $services->get('unicaen-auth_module_options');
+        /* @var $options ModuleOptions */
+        $this->options = $services->get('unicaen-auth_module_options');
 
-        // si l'auth CAS est demandée, modif de la route de connexion pour zapper le formulaire
-        if ($options->getCas() && php_sapi_name() !== 'cli') {
-            /* @var $router \Zend\Mvc\Router\Http\TreeRouteStack */
-            $router = $services->get('router');
-            $router->addRoutes([
-                // remplace les routes existantes (cf. config du module)
-                'zfcuser' => [
-                    'type'          => 'Literal',
-                    'priority'      => 1000,
-                    'options'       => [
-                        'route'    => '/auth',
-                        'defaults' => [
-                            'controller' => 'zfcuser',
-                            'action'     => 'index',
-                        ],
-                    ],
-                    'may_terminate' => true,
-                    'child_routes'  => [
-                        'login'  => [
-                            'type'    => 'Literal',
-                            'options' => [
-                                'route'    => '/connexion',
-                                'defaults' => [
-                                    'controller' => 'zfcuser',
-                                    'action'     => 'authenticate', // zappe l'action 'login'
-                                ],
-                            ],
-                        ],
-                        'logout' => [
-                            'type'    => 'Literal',
-                            'options' => [
-                                'route'    => '/deconnexion',
-                                'defaults' => [
-                                    'controller' => 'zfcuser',
-                                    'action'     => 'logout',
-                                ],
-                            ],
-                        ],
-                    ],
-                ],
-            ]);
-        }
+        $this->reconfigureRoutesForAuth($services);
     }
 
+    /**
+     * @param ServiceLocatorInterface $sl
+     */
+    private function reconfigureRoutesForAuth(ServiceLocatorInterface $sl)
+    {
+        /* @var $router \Zend\Mvc\Router\Http\TreeRouteStack */
+        $router = $sl->get('router');
+
+        // si l'auth CAS est activée, modif de la route de connexion pour zapper le formulaire d'auth maison.
+        $isCasEnable = (bool) $this->options->getCas();
+        if ($isCasEnable && php_sapi_name() !== 'cli') {
+            /** @var CasAdapter $casAdapter */
+            $casAdapter = $sl->get('UnicaenAuth\Authentication\Adapter\Cas');
+            $casAdapter->reconfigureRoutesForCasAuth($router);
+        }
 
+        // si l'auth Shibboleth est activée, modif de la route de déconnexion pour réaliser la déconnexion Shibboleth.
+        $shibOptions = $this->options->getShibboleth();
+        $isShibEnable = array_key_exists('enable', $shibOptions) && (bool) $shibOptions['enable'];
+        if ($isShibEnable && php_sapi_name() !== 'cli') {
+            /** @var ShibService $shibService */
+            $shibService = $sl->get(ShibService::class);
+            $shibService->reconfigureRoutesForShibAuth($router);
+        }
+    }
 
     /**
-     *
      * @return array
      * @see ServiceProviderInterface
      */
@@ -132,8 +122,8 @@ class Module implements AutoloaderProviderInterface, ConfigProviderInterface, Se
                 // verrue pour forcer le label de l'identifiant qqsoit l'options 'auth_identity_fields'
                 'zfcuser_login_form' => function ($sm) {
                     $options = $sm->get('zfcuser_module_options');
-                    $form    = new \ZfcUser\Form\Login(null, $options);
-                    $form->setInputFilter(new \ZfcUser\Form\LoginFilter($options));
+                    $form    = new Login(null, $options);
+                    $form->setInputFilter(new LoginFilter($options));
                     $form->get('identity')->setLabel("Username");
 
                     return $form;
diff --git a/composer.json b/composer.json
index b99ab199205eda21d92e32650ded4c7453294edc..268376b2ae1fe029744bc10416b77211a47d0784 100644
--- a/composer.json
+++ b/composer.json
@@ -4,7 +4,7 @@
     "repositories": [
         {
             "type": "composer",
-            "url": "https://dev.unicaen.fr/packagist"
+            "url": "https://gest.unicaen.fr/packagist"
         }
     ],
     "require": {
diff --git a/config/module.config.php b/config/module.config.php
index e0b80300c8d283b25ad59329c73f5392020f7b84..b0b657e3907434f6cebed79bfc97db4978ac5944 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -1,9 +1,11 @@
 <?php
 
-use UnicaenAuth\Provider\Privilege\Privileges;
+use UnicaenAuth\Controller\AuthControllerFactory;
+use UnicaenAuth\Service\ShibService;
+use UnicaenAuth\Service\ShibServiceFactory;
+use UnicaenAuth\View\Helper\ShibConnectViewHelperFactory;
 
 $settings = [
-
     /**
      * Fournisseurs d'identité.
      */
@@ -119,6 +121,8 @@ return [
                 ['controller' => 'UnicaenApp\Controller\Application', 'action' => 'informatique-et-libertes', 'roles' => []],
                 ['controller' => 'UnicaenApp\Controller\Application', 'action' => 'refresh-session', 'roles' => []],
                 ['controller' => 'UnicaenAuth\Controller\Utilisateur', 'action' => 'selectionner-profil', 'roles' => []],
+
+                ['controller' => 'UnicaenAuth\Controller\Auth', 'action' => 'shibboleth', 'roles' => []],
             ],
         ],
     ],
@@ -167,6 +171,27 @@ return [
     ],
     'router'          => [
         'routes' => [
+            'auth'     => [
+                'type'          => 'Literal',
+                'options'       => [
+                    'route'    => '/auth',
+                    'defaults' => [
+                        'controller' => 'UnicaenAuth\Controller\Auth',
+                    ],
+                ],
+                'may_terminate' => false,
+                'child_routes'  => [
+                    'shibboleth' => [
+                        'type' => 'Literal',
+                        'options' => [
+                            'route'    => '/shibboleth',
+                            'defaults' => [
+                                'action'     => 'shibboleth',
+                            ],
+                        ],
+                    ],
+                ],
+            ],
             'zfcuser'     => [
                 'type'          => 'Literal',
                 'priority'      => 1000,
@@ -346,12 +371,11 @@ return [
         'invokables'         => [
             'UnicaenAuth\Authentication\Storage\Db'   => 'UnicaenAuth\Authentication\Storage\Db',
             'UnicaenAuth\Authentication\Storage\Ldap' => 'UnicaenAuth\Authentication\Storage\Ldap',
+            'UnicaenAuth\Authentication\Storage\Shib' => 'UnicaenAuth\Authentication\Storage\Shib',
             'UnicaenAuth\View\RedirectionStrategy'    => 'UnicaenAuth\View\RedirectionStrategy',
             'UnicaenAuth\Service\UserContext'         => 'UnicaenAuth\Service\UserContext',
             'UnicaenAuth\Service\User'                => 'UnicaenAuth\Service\User',
-            'UnicaenAuth\Service\Privilege'           => 'UnicaenAuth\Service\PrivilegeService',
             'UnicaenAuth\Service\CategoriePrivilege'  => 'UnicaenAuth\Service\CategoriePrivilegeService',
-            'UnicaenAuth\Service\Role'                => 'UnicaenAuth\Service\RoleService',
         ],
         'abstract_factories' => [
             'UnicaenAuth\Authentication\Adapter\AbstractFactory',
@@ -367,8 +391,11 @@ return [
             'UnicaenAuth\Provider\Role\Config'         => 'UnicaenAuth\Provider\Role\ConfigServiceFactory',
             'UnicaenAuth\Provider\Role\DbRole'         => 'UnicaenAuth\Provider\Role\DbRoleServiceFactory',
             'UnicaenAuth\Provider\Role\Username'       => 'UnicaenAuth\Provider\Role\UsernameServiceFactory',
+            'UnicaenAuth\Service\Role'                 => 'UnicaenAuth\Service\RoleServiceFactory',
+            'UnicaenAuth\Service\Privilege'            => 'UnicaenAuth\Service\PrivilegeServiceFactory',
             'BjyAuthorize\Service\Authorize'           => 'UnicaenAuth\Service\AuthorizeServiceFactory', // substituion
             'zfcuser_redirect_callback'                => 'UnicaenAuth\Authentication\RedirectCallbackFactory', // substituion
+            ShibService::class                         => ShibServiceFactory::class,
             'MouchardCompleterAuth'        => 'UnicaenAuth\Mouchard\MouchardCompleterAuthFactory',
         ],
         'shared' => [
@@ -384,6 +411,9 @@ return [
             'UnicaenAuth\Controller\Utilisateur' => 'UnicaenAuth\Controller\UtilisateurController',
             'UnicaenAuth\Controller\Droits'      => 'UnicaenAuth\Controller\DroitsController',
         ],
+        'factories' => [
+            'UnicaenAuth\Controller\Auth'        => AuthControllerFactory::class,
+        ],
     ],
 
     'form_elements' => [
@@ -401,6 +431,7 @@ return [
             'userInfo'                   => 'UnicaenAuth\View\Helper\UserInfoFactory',
             'userProfileSelect'          => 'UnicaenAuth\View\Helper\UserProfileSelectFactory',
             'userProfileSelectRadioItem' => 'UnicaenAuth\View\Helper\UserProfileSelectRadioItemFactory',
+            'shibConnect'                => ShibConnectViewHelperFactory::class,
         ],
         'invokables' => [
             'appConnection' => 'UnicaenAuth\View\Helper\AppConnection',
diff --git a/config/unicaen-auth.global.php.dist b/config/unicaen-auth.global.php.dist
index 675d7624589561e4ddc79e5c03e0065483533a6d..4e857772af6eeb5884f556fcd8bc19884ec7f00f 100644
--- a/config/unicaen-auth.global.php.dist
+++ b/config/unicaen-auth.global.php.dist
@@ -67,6 +67,20 @@ $config = [
 
 if ($settings['enable_privileges']) {
     $privileges = [
+        'unicaen-auth' => [
+            /**
+             * Classes représentant les entités rôle et privilège.
+             * - Entité rôle      : héritant de \UnicaenAuth\Entity\Db\AbstractRole      ou implémentant \UnicaenAuth\Entity\Db\RoleInterface.
+             * - Entité privilège : héritant de \UnicaenAuth\Entity\Db\AbstractPrivilege ou implémentant \UnicaenAuth\Entity\Db\PrivilegeInterface.
+             *
+             * Valeurs par défaut :
+             * - 'role_entity_class'      : 'UnicaenAuth\Entity\Db\Role'
+             * - 'privilege_entity_class' : 'UnicaenAuth\Entity\Db\Privilege'
+             */
+            'role_entity_class'      => 'UnicaenAuth\Entity\Db\Role',
+            'privilege_entity_class' => 'UnicaenAuth\Entity\Db\Privilege',
+        ],
+
         'bjyauthorize' => [
 
             'resource_providers' => [
diff --git a/config/unicaen-auth.local.php.dist b/config/unicaen-auth.local.php.dist
index ee3a26e23473b2c52fa32def0ef683adf5c31a57..8cecd5de4426a9112b652beb74c454e0e88c31fe 100644
--- a/config/unicaen-auth.local.php.dist
+++ b/config/unicaen-auth.local.php.dist
@@ -6,6 +6,12 @@
  * drop this config file in it and change the values as you wish.
  */
 $settings = [
+    /**
+     * Activation ou non de l'authentification Shibboleth.
+     */
+    'shibboleth' => [
+        'enable' => false,
+    ],
     /**
      * Paramètres de connexion au serveur CAS :
      * - pour désactiver l'authentification CAS, le tableau 'cas' doit être vide.
diff --git a/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php b/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
index adb8ac15b482a3ed99e54e9b4fe1c2407876d7f7..d5ce3734895e54b34a65aa6f602008f194ccd5df 100644
--- a/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
+++ b/src/UnicaenAuth/Authentication/Adapter/AbstractFactory.php
@@ -1,10 +1,10 @@
 <?php
+
 namespace UnicaenAuth\Authentication\Adapter;
 
-use UnicaenApp\Exception;
-use UnicaenAuth\Authentication\Adapter\Cas;
-use UnicaenAuth\Authentication\Adapter\Db;
-use UnicaenAuth\Authentication\Adapter\Ldap;
+use UnicaenApp\Exception\LogicException;
+use Zend\EventManager\EventManager;
+use Zend\EventManager\EventManagerAwareInterface;
 use Zend\ServiceManager\AbstractFactoryInterface;
 use Zend\ServiceManager\ServiceLocatorInterface;
 
@@ -34,7 +34,7 @@ class AbstractFactory implements AbstractFactoryInterface
      * @param ServiceLocatorInterface $serviceLocator
      * @param $name
      * @param $requestedName
-     * @return mixed
+     * @return \ZfcUser\Authentication\Adapter\AbstractAdapter
      */
     public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
     {
@@ -48,12 +48,19 @@ class AbstractFactory implements AbstractFactoryInterface
             case __NAMESPACE__ . '\Cas':
                 $adapter = new Cas();
                 break;
+            //
+            // NB: pour faire simple, la stratégie de créer un adapter pour l'auth Shibboleth n'a pas été retenue.
+            //
+            // case __NAMESPACE__ . '\Shib':
+            //     $adapter = new Shib();
+            //     break;
             default:
-                throw new Exception("Service demandé inattendu : '$requestedName'!");
+                throw new LogicException("Service demandé inattendu : '$requestedName'!");
                 break;
         }
 
-        if ($adapter instanceof \Zend\EventManager\EventManagerAwareInterface) {
+        if ($adapter instanceof EventManagerAwareInterface) {
+            /** @var EventManager $eventManager */
             $eventManager = $serviceLocator->get('event_manager');
             $adapter->setEventManager($eventManager);
             $userService = $serviceLocator->get('unicaen-auth_user_service'); /* @var $userService \UnicaenAuth\Service\User */
diff --git a/src/UnicaenAuth/Authentication/Adapter/Cas.php b/src/UnicaenAuth/Authentication/Adapter/Cas.php
index 5c3a20343e21b3fc57c1dd624ac96541e50d915e..98d563a6ba7bcdd5dc4055d40e1021118ac46526 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Cas.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Cas.php
@@ -9,6 +9,7 @@ use Zend\Authentication\Result as AuthenticationResult;
 use Zend\EventManager\EventManager;
 use Zend\EventManager\EventManagerAwareInterface;
 use Zend\EventManager\EventManagerInterface;
+use Zend\Mvc\Router\Http\TreeRouteStack;
 use Zend\ServiceManager\ServiceManager;
 use Zend\ServiceManager\ServiceManagerAwareInterface;
 use ZfcUser\Authentication\Adapter\AbstractAdapter;
@@ -230,4 +231,48 @@ class Cas extends AbstractAdapter implements ServiceManagerAwareInterface, Event
         $this->eventManager = $eventManager;
         return $this;
     }
+
+    /**
+     * @param TreeRouteStack $router
+     */
+    public function reconfigureRoutesForCasAuth(TreeRouteStack $router)
+    {
+        $router->addRoutes([
+            // remplace les routes existantes (cf. config du module)
+            'zfcuser' => [
+                'type'          => 'Literal',
+                'priority'      => 1000,
+                'options'       => [
+                    'route'    => '/auth',
+                    'defaults' => [
+                        'controller' => 'zfcuser',
+                        'action'     => 'index',
+                    ],
+                ],
+                'may_terminate' => true,
+                'child_routes'  => [
+                    'login'  => [
+                        'type'    => 'Literal',
+                        'options' => [
+                            'route'    => '/connexion',
+                            'defaults' => [
+                                'controller' => 'zfcuser',
+                                'action'     => 'authenticate', // zappe l'action 'login'
+                            ],
+                        ],
+                    ],
+                    'logout' => [
+                        'type'    => 'Literal',
+                        'options' => [
+                            'route'    => '/deconnexion',
+                            'defaults' => [
+                                'controller' => 'zfcuser',
+                                'action'     => 'logout',
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+        ]);
+    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Adapter/Ldap.php b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
index e26828192027e2e1546ecf0589e66dd776e8070d..630f9e0c66d35ebe49b2cebb4d0d94f023f4a58d 100644
--- a/src/UnicaenAuth/Authentication/Adapter/Ldap.php
+++ b/src/UnicaenAuth/Authentication/Adapter/Ldap.php
@@ -1,16 +1,17 @@
 <?php
+
 namespace UnicaenAuth\Authentication\Adapter;
 
+use UnicaenApp\Exception\RuntimeException;
+use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
 use UnicaenAuth\Options\ModuleOptions;
-use Zend\Authentication\Exception\UnexpectedValueException;
-use Zend\Authentication\Result as AuthenticationResult;
+use UnicaenAuth\Service\User;
 use Zend\Authentication\Adapter\Ldap as LdapAuthAdapter;
-use Zend\Authentication\Result;
-use Zend\EventManager\Event;
+use Zend\Authentication\Exception\ExceptionInterface;
+use Zend\Authentication\Result as AuthenticationResult;
 use Zend\EventManager\EventManager;
 use Zend\EventManager\EventManagerAwareInterface;
 use Zend\EventManager\EventManagerInterface;
-use Zend\Ldap\Exception\LdapException;
 use Zend\ServiceManager\ServiceManager;
 use Zend\ServiceManager\ServiceManagerAwareInterface;
 use ZfcUser\Authentication\Adapter\AbstractAdapter;
@@ -41,6 +42,11 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
      */
     protected $ldapAuthAdapter;
 
+    /**
+     * @var LdapPeopleMapper
+     */
+    protected $ldapPeopleMapper;
+
     /**
      * @var ModuleOptions
      */
@@ -55,16 +61,21 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
      *
      * @param AuthEvent $e
      * @return boolean
-     * @throws UnexpectedValueException
+     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
+     * @throws \Zend\Ldap\Exception\LdapException
      * @see ChainableAdapter
      */
     public function authenticate(AuthEvent $e)
     {
         if ($this->isSatisfied()) {
-            $storage = $this->getStorage()->read();
+            try {
+                $storage = $this->getStorage()->read();
+            } catch (ExceptionInterface $e) {
+                throw new RuntimeException("Erreur de lecture du storage");
+            }
             $e->setIdentity($storage['identity'])
-                    ->setCode(AuthenticationResult::SUCCESS)
-                    ->setMessages(['Authentication successful.']);
+                ->setCode(AuthenticationResult::SUCCESS)
+                ->setMessages(['Authentication successful.']);
             return;
         }
 
@@ -82,28 +93,69 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
         // Failure!
         if (! $success) {
             $e->setCode(AuthenticationResult::FAILURE)
-              ->setMessages(['LDAP bind failed.']);
+                ->setMessages(['LDAP bind failed.']);
+            $this->setSatisfied(false);
+            return false;
+        }
+
+        // recherche de l'individu dans l'annuaire LDAP
+        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($username);
+        if (!$ldapPeople) {
+            $e
+                ->setCode(AuthenticationResult::FAILURE)
+                ->setMessages(['Authentication failed.']);
             $this->setSatisfied(false);
             return false;
         }
 
         $e->setIdentity($this->usernameUsurpe ?: $username);
         $this->setSatisfied(true);
-        $storage = $this->getStorage()->read();
-        $storage['identity'] = $e->getIdentity();
-        $this->getStorage()->write($storage);
+        try {
+            $storage = $this->getStorage()->read();
+            $storage['identity'] = $e->getIdentity();
+            $this->getStorage()->write($storage);
+        } catch (ExceptionInterface $e) {
+            throw new RuntimeException("Erreur de concernant le storage");
+        }
         $e->setCode(AuthenticationResult::SUCCESS)
-          ->setMessages(['Authentication successful.']);
+            ->setMessages(['Authentication successful.']);
+
+        /* @var $userService User */
+        $userService = $this->getServiceManager()->get('unicaen-auth_user_service');
+        $userService->userAuthenticated($ldapPeople);
+    }
+
+    /**
+     * Extrait le loginUsurpateur et le loginUsurpé si l'identifiant spécifé est de la forme
+     * "loginUsurpateur=loginUsurpé".
+     *
+     * @param string $identifiant Identifiant, éventuellement de la forme "loginUsurpateur=loginUsurpé"
+     * @return array
+     * [loginUsurpateur, loginUsurpé] si l'identifiant est de la forme "loginUsurpateur=loginUsurpé" ;
+     * [] sinon.
+     */
+    static public function extractUsernamesUsurpation($identifiant)
+    {
+        if (strpos($identifiant, self::USURPATION_USERNAMES_SEP) > 0) {
+            list($identifiant, $usernameUsurpe) = explode(self::USURPATION_USERNAMES_SEP, $identifiant, 2);
+
+            return [
+                $identifiant,
+                $usernameUsurpe
+            ];
+        }
 
-        $this->getEventManager()->trigger('userAuthenticated', $e);
+        return [];
     }
 
     /**
      * Authentifie l'identifiant et le mot de passe spécifiés.
      *
-     * @param string $username Identifiant de connexion
+     * @param string $username   Identifiant de connexion
      * @param string $credential Mot de passe
      * @return boolean
+     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
+     * @throws \Zend\Ldap\Exception\LdapException
      */
     public function authenticateUsername($username, $credential)
     {
@@ -111,20 +163,17 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
         // - le format attendu est "loginUsurpateur=loginUsurpé"
         // - le mot de passe attendu est celui du compte usurpateur (loginUsurpateur)
         $this->usernameUsurpe = null;
-        if (strpos($username, self::USURPATION_USERNAMES_SEP) > 0) {
-            list($username, $this->usernameUsurpe) = explode(self::USURPATION_USERNAMES_SEP, $username, 2);
+        $usernames = self::extractUsernamesUsurpation($username);
+        if (count($usernames) === 2) {
+            list ($username, $this->usernameUsurpe) = $usernames;
             if (!in_array($username, $this->getOptions()->getUsurpationAllowedUsernames())) {
                 $this->usernameUsurpe = null;
             }
         }
 
         // LDAP auth
-
-
-        /** @var Result $result */
         $result  = $this->getLdapAuthAdapter()->setUsername($username)->setPassword($credential)->authenticate();
 
-
         if ($result && count($result->getMessages())) {
             // Obtenir le message LDAP
             // $msg = preg_replace('/\[0x\d* \((.*)\):/','$1', $event->getParam('result')->getMessages()[1]);
@@ -149,6 +198,31 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
         return $success;
     }
 
+    /**
+     * get ldap people mapper
+     *
+     * @return LdapPeopleMapper
+     */
+    public function getLdapPeopleMapper()
+    {
+        if (null === $this->ldapPeopleMapper) {
+            $this->ldapPeopleMapper = $this->getServiceManager()->get('ldap_people_mapper');
+        }
+        return $this->ldapPeopleMapper;
+    }
+
+    /**
+     * set ldap people mapper
+     *
+     * @param LdapPeopleMapper $mapper
+     * @return self
+     */
+    public function setLdapPeopleMapper(LdapPeopleMapper $mapper)
+    {
+        $this->ldapPeopleMapper = $mapper;
+        return $this;
+    }
+
     /**
      * @param ModuleOptions $options
      */
@@ -164,8 +238,8 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
     {
         if (!$this->options instanceof ModuleOptions) {
             $options = array_merge(
-                    $this->getServiceManager()->get('zfcuser_module_options')->toArray(),
-                    $this->getServiceManager()->get('unicaen-auth_module_options')->toArray());
+                $this->getServiceManager()->get('zfcuser_module_options')->toArray(),
+                $this->getServiceManager()->get('unicaen-auth_module_options')->toArray());
             $this->setOptions(new ModuleOptions($options));
         }
         return $this->options;
@@ -250,6 +324,10 @@ class Ldap extends AbstractAdapter implements ServiceManagerAwareInterface, Even
      */
     public function setEventManager(EventManagerInterface $eventManager)
     {
+        $eventManager->setIdentifiers([
+            __NAMESPACE__,
+            __CLASS__,
+        ]);
         $this->eventManager = $eventManager;
         return $this;
     }
diff --git a/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php b/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php
index 8da41999f2756a3b1f5f8ea69f9c697c89366cdf..99f5f1b256da8effd52036e5a5fcacb7a508d568 100644
--- a/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php
+++ b/src/UnicaenAuth/Authentication/Storage/ChainableStorage.php
@@ -2,8 +2,6 @@
 
 namespace UnicaenAuth\Authentication\Storage;
 
-use UnicaenAuth\Authentication\Storage\ChainEvent;
-
 interface ChainableStorage
 {
     /**
@@ -11,25 +9,21 @@ interface ChainableStorage
      *
      * Behavior is undefined when storage is empty.
      *
-     * @throws InvalidArgumentException If reading contents from storage is impossible
-     * @return People
+     * @param \UnicaenAuth\Authentication\Storage\ChainEvent $e
      */
     public function read(ChainEvent $e);
-    
+
     /**
      * Writes $contents to storage
      *
-     * @param  mixed $contents
-     * @throws InvalidArgumentException If writing $contents to storage is impossible
-     * @return void
+     * @param \UnicaenAuth\Authentication\Storage\ChainEvent $e
      */
     public function write(ChainEvent $e);
 
     /**
      * Clears contents from storage
      *
-     * @throws InvalidArgumentException If clearing contents from storage is impossible
-     * @return void
+     * @param \UnicaenAuth\Authentication\Storage\ChainEvent $e
      */
     public function clear(ChainEvent $e);
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Authentication/Storage/Shib.php b/src/UnicaenAuth/Authentication/Storage/Shib.php
new file mode 100644
index 0000000000000000000000000000000000000000..1bc25efa09bfdb760746b6a8e63aa2ec2fad23d1
--- /dev/null
+++ b/src/UnicaenAuth/Authentication/Storage/Shib.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace UnicaenAuth\Authentication\Storage;
+
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
+use UnicaenAuth\Options\ModuleOptions;
+use UnicaenAuth\Service\ShibService;
+use Zend\Authentication\Storage\Session;
+use Zend\Authentication\Storage\StorageInterface;
+use Zend\ServiceManager\ServiceLocatorAwareInterface;
+use Zend\ServiceManager\ServiceLocatorAwareTrait;
+use Zend\ServiceManager\ServiceManager;
+
+/**
+ * Shibboleth authentication storage.
+ *
+ * @author Unicaen
+ */
+class Shib implements ChainableStorage, ServiceLocatorAwareInterface
+{
+    use ServiceLocatorAwareTrait;
+
+    /**
+     * @var StorageInterface
+     */
+    protected $storage;
+    
+    /**
+     * @var ModuleOptions
+     */
+    protected $options;
+
+    /**
+     * @var ShibUser
+     */
+    protected $resolvedIdentity;
+
+    /**
+     * @var ServiceManager
+     */
+    protected $serviceManager;
+
+    /**
+     * Returns the contents of storage
+     *
+     * Behavior is undefined when storage is empty.
+     *
+     * @param ChainEvent $e
+     * @return ShibUser
+     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     */
+    public function read(ChainEvent $e)
+    {
+        /** @var ShibService $shib */
+        $shib = $this->getServiceLocator()->get(ShibService::class);
+        $shibUser = $shib->getAuthenticatedUser();
+
+        $e->addContents('shib', $shibUser);
+        
+        return $shibUser;
+    }
+
+    /**
+     * Writes $contents to storage
+     *
+     * @param ChainEvent $e
+     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     */
+    public function write(ChainEvent $e)
+    {
+        $contents = $e->getParam('contents');
+        $this->resolvedIdentity = null;
+        $this->getStorage()->write($contents);
+    }
+
+    /**
+     * Clears contents from storage
+     *
+     * @param ChainEvent $e
+     * @throws \Zend\Authentication\Exception\ExceptionInterface
+     */
+    public function clear(ChainEvent $e)
+    {
+        $this->resolvedIdentity = null;
+        $this->getStorage()->clear();
+    }
+
+    /**
+     * getStorage
+     *
+     * @return StorageInterface
+     */
+    public function getStorage()
+    {
+        if (null === $this->storage) {
+            $this->setStorage(new Session());
+        }
+
+        return $this->storage;
+    }
+
+    /**
+     * setStorage
+     *
+     * @param StorageInterface $storage
+     * @return self
+     */
+    public function setStorage(StorageInterface $storage)
+    {
+        $this->storage = $storage;
+
+        return $this;
+    }
+}
diff --git a/src/UnicaenAuth/Controller/AuthController.php b/src/UnicaenAuth/Controller/AuthController.php
new file mode 100644
index 0000000000000000000000000000000000000000..d3b468c39e9fc06686bdbd0a30023854a27e1124
--- /dev/null
+++ b/src/UnicaenAuth/Controller/AuthController.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace UnicaenAuth\Controller;
+
+use UnicaenApp\Exception\RuntimeException;
+use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
+use UnicaenAuth\Service\Traits\UserServiceAwareTrait;
+use Zend\Authentication\AuthenticationService;
+use Zend\Authentication\Exception\ExceptionInterface;
+use Zend\Http\Response;
+use Zend\Mvc\Controller\AbstractActionController;
+use ZfcUser\Controller\Plugin\ZfcUserAuthentication;
+
+/**
+ * Classe ajoutée lors de l'implémentation de l'auth Shibboleth.
+ *
+ * @method ZfcUserAuthentication zfcUserAuthentication()
+ * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
+ */
+class AuthController extends AbstractActionController
+{
+    use ShibServiceAwareTrait;
+    use UserServiceAwareTrait;
+
+    /**
+     * @return Response|array
+     */
+    public function shibbolethAction()
+    {
+        $operation = $this->params()->fromRoute('operation');
+
+        if ($operation === 'deconnexion') {
+            // déconnexion applicative quoiqu'il arrive
+            $this->zfcUserAuthentication()->getAuthAdapter()->resetAdapters();
+            $this->zfcUserAuthentication()->getAuthAdapter()->logoutAdapters();
+            $this->zfcUserAuthentication()->getAuthService()->clearIdentity();
+
+            // déconnexion Shibboleth le cas échéant
+            if ($this->shibService->isShibbolethEnable()) {
+                $homeUrl = $this->url()->fromRoute('home', [], ['force_canonical' => true]);
+                $returnAbsoluteUrl = $this->params()->fromQuery('return', $homeUrl);
+                return $this->redirect()->toUrl($this->shibService->getLogoutUrl($returnAbsoluteUrl));
+            } else {
+                return []; // une page d'aide s'affichera
+            }
+        }
+
+        $shibUser = $this->shibService->getAuthenticatedUser();
+
+        if ($shibUser === null) {
+            return []; // une page d'aide s'affichera
+        }
+
+        /** @var AuthenticationService $authService */
+        $authService = $this->getServiceLocator()->get('zfcuser_auth_service');
+        try {
+            $authService->getStorage()->write($shibUser->getId());
+        } catch (ExceptionInterface $e) {
+            throw new RuntimeException("Impossible d'écrire dans le storage");
+        }
+
+        $this->userService->userAuthenticated($shibUser);
+
+        $redirectUrl = $this->params()->fromQuery('redirect', '/');
+
+        return $this->redirect()->toUrl($redirectUrl);
+    }
+
+    public function shibboleth()
+    {
+
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Controller/AuthControllerFactory.php b/src/UnicaenAuth/Controller/AuthControllerFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..e771651f537a0b3566d3de03b8fbc24ecce55068
--- /dev/null
+++ b/src/UnicaenAuth/Controller/AuthControllerFactory.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace UnicaenAuth\Controller;
+
+use UnicaenAuth\Service\ShibService;
+use UnicaenAuth\Service\User as UserService;
+use Zend\Mvc\Controller\ControllerManager;
+
+class AuthControllerFactory
+{
+    /**
+     * @param ControllerManager $cm
+     * @return AuthController
+     */
+    public function __invoke(ControllerManager $cm)
+    {
+        /** @var ShibService $shibService */
+        $shibService = $cm->getServiceLocator()->get(ShibService::class);
+
+        /* @var $userService UserService */
+        $userService = $cm->getServiceLocator()->get('unicaen-auth_user_service');
+
+        $controller = new AuthController();
+        $controller->setShibService($shibService);
+        $controller->setUserService($userService);
+
+        return $controller;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Controller/DroitsController.php b/src/UnicaenAuth/Controller/DroitsController.php
index cb0343fce9b4854f0a9096ae1dea4cda1dd725ff..a4970f0b469bb760322a2d699183bbdae6d966d9 100644
--- a/src/UnicaenAuth/Controller/DroitsController.php
+++ b/src/UnicaenAuth/Controller/DroitsController.php
@@ -2,8 +2,6 @@
 
 namespace UnicaenAuth\Controller;
 
-use UnicaenAuth\Entity\Db\Privilege;
-use UnicaenAuth\Entity\Db\Role;
 use UnicaenAuth\Form\Droits\Traits\RoleFormAwareTrait;
 use UnicaenAuth\Service\Traits\PrivilegeServiceAwareTrait;
 use UnicaenAuth\Service\Traits\RoleServiceAwareTrait;
@@ -11,11 +9,9 @@ use Zend\Form\Form;
 use Zend\Mvc\Controller\AbstractActionController;
 use Zend\View\Model\ViewModel;
 
-
 /**
  * Description of DroitsController
  *
- *
  * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr>
  */
 class DroitsController extends AbstractActionController
@@ -24,19 +20,11 @@ class DroitsController extends AbstractActionController
     use RoleFormAwareTrait;
     use PrivilegeServiceAwareTrait;
 
-
-
-    /**
-     *
-     * @return type
-     */
     public function indexAction()
     {
         return [];
     }
 
-
-
     public function rolesAction()
     {
         $roles = $this->getServiceRole()->getList();
@@ -44,8 +32,6 @@ class DroitsController extends AbstractActionController
         return compact('roles');
     }
 
-
-
     public function roleEditionAction()
     {
         $roleId = $this->params()->fromRoute('role');
@@ -79,8 +65,6 @@ class DroitsController extends AbstractActionController
         return compact('form', 'title', 'errors');
     }
 
-
-
     public function roleSuppressionAction()
     {
         $roleId = $this->params()->fromRoute('role');
@@ -101,8 +85,6 @@ class DroitsController extends AbstractActionController
         return compact('role', 'title', 'form', 'errors');
     }
 
-
-
     public function privilegesAction()
     {
         $ps         = $this->getServicePrivilege()->getList();
@@ -123,8 +105,6 @@ class DroitsController extends AbstractActionController
         return compact('privileges', 'roles');
     }
 
-
-
     public function privilegesModifierAction()
     {
         $roleId = $this->params()->fromPost('role');
@@ -151,8 +131,6 @@ class DroitsController extends AbstractActionController
         return $viewModel;
     }
 
-
-
     public function getFormSupprimer()
     {
         $form = new Form();
diff --git a/src/UnicaenAuth/Entity/Db/AbstractPrivilege.php b/src/UnicaenAuth/Entity/Db/AbstractPrivilege.php
new file mode 100644
index 0000000000000000000000000000000000000000..2883ac8459ac8020a407a81d82e57742f418c03e
--- /dev/null
+++ b/src/UnicaenAuth/Entity/Db/AbstractPrivilege.php
@@ -0,0 +1,227 @@
+<?php
+
+namespace UnicaenAuth\Entity\Db;
+
+use Doctrine\Common\Collections\Collection;
+use UnicaenAuth\Provider\Privilege\Privileges;
+use Zend\Permissions\Acl\Resource\ResourceInterface;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Privilege entity abstract mother class.
+ *
+ * @ORM\MappedSuperclass
+ */
+abstract class AbstractPrivilege implements PrivilegeInterface, ResourceInterface
+{
+    /**
+     * @var int
+     * @ORM\Id
+     * @ORM\Column(type="integer")
+     * @ORM\GeneratedValue(strategy="AUTO")
+     */
+    protected $id;
+
+    /**
+     * @var string
+     * @ORM\Column(name="code", type="string", length=150, unique=false, nullable=false)
+     */
+    protected $code;
+
+    /**
+     * @var string
+     * @ORM\Column(name="libelle", type="string", length=200, unique=false, nullable=false)
+     */
+    protected $libelle;
+
+    /**
+     * @var int
+     * @ORM\Column(name="ordre", type="integer", unique=false, nullable=true)
+     */
+    protected $ordre;
+
+    /**
+     * @var CategoriePrivilege
+     * @ORM\ManyToOne(targetEntity="CategoriePrivilege", inversedBy="privilege")
+     * @ORM\JoinColumn(name="categorie_id", referencedColumnName="id")
+     */
+    protected $categorie;
+
+    /**
+     * @ORM\ManyToMany(targetEntity="UnicaenAuth\Entity\Db\Role",cascade={"all"})
+     * @ORM\JoinTable(
+     *     name="role_privilege",
+     *     joinColumns={@ORM\JoinColumn(name="privilege_id", referencedColumnName="id", onDelete="cascade")},
+     *     inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id", onDelete="cascade")}
+     *
+     * )
+     */
+    protected $role;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->role = new \Doctrine\Common\Collections\ArrayCollection();
+    }
+
+    /**
+     * Set code
+     *
+     * @param string $code
+     *
+     * @return Privilege
+     */
+    public function setCode($code)
+    {
+        $this->code = $code;
+
+        return $this;
+    }
+
+    /**
+     * Get code
+     *
+     * @return string
+     */
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    public function getFullCode()
+    {
+        return $this->getCategorie()->getCode() . '-' . $this->getCode();
+    }
+
+    /**
+     * Set libelle
+     *
+     * @param string $libelle
+     *
+     * @return Privilege
+     */
+    public function setLibelle($libelle)
+    {
+        $this->libelle = $libelle;
+
+        return $this;
+    }
+
+    /**
+     * Get libelle
+     *
+     * @return string
+     */
+    public function getLibelle()
+    {
+        return $this->libelle;
+    }
+
+    /**
+     *
+     * @return integer
+     */
+    function getOrdre()
+    {
+        return $this->ordre;
+    }
+
+    /**
+     *
+     * @param integer $ordre
+     *
+     * @return self
+     */
+    function setOrdre($ordre)
+    {
+        $this->ordre = $ordre;
+
+        return $this;
+    }
+
+    /**
+     * Get id
+     *
+     * @return integer
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Set categorie
+     *
+     * @param CategoriePrivilege $categorie
+     *
+     * @return self
+     */
+    public function setCategorie(CategoriePrivilege $categorie = null)
+    {
+        $this->categorie = $categorie;
+
+        return $this;
+    }
+
+    /**
+     * Get categorie
+     *
+     * @return CategoriePrivilege
+     */
+    public function getCategorie()
+    {
+        return $this->categorie;
+    }
+
+    /**
+     * Add role
+     *
+     * @param RoleInterface $role
+     *
+     * @return self
+     */
+    public function addRole(RoleInterface $role)
+    {
+        $this->role->add($role);
+
+        return $this;
+    }
+
+    /**
+     * Remove role
+     *
+     * @param RoleInterface $role
+     */
+    public function removeRole(RoleInterface $role)
+    {
+        $this->role->removeElement($role);
+    }
+
+    /**
+     * Get role
+     *
+     * @return Collection
+     */
+    public function getRole()
+    {
+        return $this->role;
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getLibelle();
+    }
+
+    /**
+     * @return string
+     */
+    public function getResourceId()
+    {
+        return Privileges::getResourceId($this);
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/AbstractRole.php b/src/UnicaenAuth/Entity/Db/AbstractRole.php
new file mode 100644
index 0000000000000000000000000000000000000000..aaa2e6cb05f9ca59c221ecd7cde903bda9e34250
--- /dev/null
+++ b/src/UnicaenAuth/Entity/Db/AbstractRole.php
@@ -0,0 +1,202 @@
+<?php
+
+namespace UnicaenAuth\Entity\Db;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Role entity abstract mother class.
+ *
+ * @ORM\MappedSuperclass
+ */
+abstract class AbstractRole implements RoleInterface
+{
+    /**
+     * @var int
+     * @ORM\Id
+     * @ORM\Column(type="integer")
+     * @ORM\GeneratedValue(strategy="AUTO")
+     */
+    protected $id;
+
+    /**
+     * @var string
+     * @ORM\Column(name="role_id", type="string", length=255, unique=true, nullable=false)
+     */
+    protected $roleId;
+
+    /**
+     * @var boolean
+     * @ORM\Column(name="is_default", type="boolean", nullable=true)
+     */
+    protected $isDefault = false;
+
+    /**
+     * @var Role
+     * @ORM\ManyToOne(targetEntity="Role")
+     */
+    protected $parent;
+
+    /**
+     * @var string
+     * @ORM\Column(name="ldap_filter", type="string", length=255, unique=true, nullable=true)
+     */
+    protected $ldapFilter;
+
+    /**
+     * @var \Doctrine\Common\Collections\Collection
+     * @ORM\ManyToMany(targetEntity="UnicaenAuth\Entity\Db\User")
+     * @ORM\JoinTable(name="user_role_linker",
+     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
+     *      inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
+     * )
+     */
+    protected $users;
+
+    /**
+     * Get the id.
+     *
+     * @return int
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Set the id.
+     *
+     * @param int $id
+     *
+     * @return self
+     */
+    public function setId($id)
+    {
+        $this->id = (int)$id;
+
+        return $this;
+    }
+
+    /**
+     * Get the role id.
+     *
+     * @return string
+     */
+    public function getRoleId()
+    {
+        return $this->roleId;
+    }
+
+    /**
+     * Set the role id.
+     *
+     * @param string $roleId
+     *
+     * @return self
+     */
+    public function setRoleId($roleId)
+    {
+        $this->roleId = (string)$roleId;
+
+        return $this;
+    }
+
+    /**
+     * Is this role the default one ?
+     *
+     * @return boolean
+     */
+    public function getIsDefault()
+    {
+        return $this->isDefault;
+    }
+
+    /**
+     * Set this role as the default one.
+     *
+     * @param boolean $isDefault
+     *
+     * @return self
+     */
+    public function setIsDefault($isDefault)
+    {
+        $this->isDefault = (boolean)$isDefault;
+
+        return $this;
+    }
+
+    /**
+     * Get the parent role
+     *
+     * @return Role
+     */
+    public function getParent()
+    {
+        return $this->parent;
+    }
+
+    /**
+     * Set the parent role.
+     *
+     * @param RoleInterface $parent
+     *
+     * @return self
+     */
+    public function setParent(RoleInterface $parent = null)
+    {
+        $this->parent = $parent;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLdapFilter()
+    {
+        return $this->ldapFilter;
+    }
+
+    /**
+     * @param string $ldapFilter
+     *
+     * @return Role
+     */
+    public function setLdapFilter($ldapFilter)
+    {
+        $this->ldapFilter = $ldapFilter;
+
+        return $this;
+    }
+
+    /**
+     * Get users.
+     *
+     * @return array
+     */
+    public function getUsers()
+    {
+        return $this->users->getValues();
+    }
+
+    /**
+     * Add a user to the role.
+     *
+     * @param UserInterface $user
+     *
+     * @return void
+     */
+    public function addUser(UserInterface $user)
+    {
+        $this->users[] = $user;
+    }
+
+    /**
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getRoleId();
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/AbstractUser.php b/src/UnicaenAuth/Entity/Db/AbstractUser.php
index 654891a03a91bc7a11130ca922018eda8fa546a1..e19aba664c5006252e0ad54fd581e13bf2ef83a0 100644
--- a/src/UnicaenAuth/Entity/Db/AbstractUser.php
+++ b/src/UnicaenAuth/Entity/Db/AbstractUser.php
@@ -1,12 +1,11 @@
 <?php
- 
+
 namespace UnicaenAuth\Entity\Db;
 
 use BjyAuthorize\Provider\Role\ProviderInterface;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
-use ZfcUser\Entity\UserInterface;
 
 /**
  * User entity abstract mother class.
@@ -216,17 +215,17 @@ abstract class AbstractUser implements UserInterface, ProviderInterface
     /**
      * Add a role to the user.
      *
-     * @param Role $role
+     * @param RoleInterface $role
      *
      * @return void
      */
-    public function addRole(Role $role)
+    public function addRole(RoleInterface $role)
     {
         $this->roles->add($role);
     }
-    
+
     /**
-     * 
+     *
      * @return string
      */
     public function __toString()
diff --git a/src/UnicaenAuth/Entity/Db/CategoriePrivilege.php b/src/UnicaenAuth/Entity/Db/CategoriePrivilege.php
index e3009d9e13bebb094a1659c44171ae78fcccadc8..6f91c1adc06a65d1736d1c39b8e6323229d85da9 100644
--- a/src/UnicaenAuth/Entity/Db/CategoriePrivilege.php
+++ b/src/UnicaenAuth/Entity/Db/CategoriePrivilege.php
@@ -2,6 +2,7 @@
 
 namespace UnicaenAuth\Entity\Db;
 
+use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -43,18 +44,14 @@ class CategoriePrivilege
      */
     private $privilege;
 
-
-
     /**
      * Constructor
      */
     public function __construct()
     {
-        $this->privilege = new \Doctrine\Common\Collections\ArrayCollection();
+        $this->privilege = new ArrayCollection();
     }
 
-
-
     /**
      * Set code
      *
@@ -69,8 +66,6 @@ class CategoriePrivilege
         return $this;
     }
 
-
-
     /**
      * Get code
      *
@@ -81,8 +76,6 @@ class CategoriePrivilege
         return $this->code;
     }
 
-
-
     /**
      * Set libelle
      *
@@ -97,8 +90,6 @@ class CategoriePrivilege
         return $this;
     }
 
-
-
     /**
      * Get libelle
      *
@@ -109,8 +100,6 @@ class CategoriePrivilege
         return $this->libelle;
     }
 
-
-
     /**
      *
      * @return integer
@@ -120,8 +109,6 @@ class CategoriePrivilege
         return $this->ordre;
     }
 
-
-
     /**
      *
      * @param integer $ordre
@@ -135,8 +122,6 @@ class CategoriePrivilege
         return $this;
     }
 
-
-
     /**
      * Get id
      *
@@ -147,8 +132,6 @@ class CategoriePrivilege
         return $this->id;
     }
 
-
-
     /**
      * Add privilege
      *
@@ -163,8 +146,6 @@ class CategoriePrivilege
         return $this;
     }
 
-
-
     /**
      * Remove privilege
      *
@@ -175,8 +156,6 @@ class CategoriePrivilege
         $this->privilege->removeElement($privilege);
     }
 
-
-
     /**
      * Get privilege
      *
@@ -187,8 +166,6 @@ class CategoriePrivilege
         return $this->privilege;
     }
 
-
-
     /**
      * @return string
      */
@@ -196,4 +173,4 @@ class CategoriePrivilege
     {
         return $this->getLibelle();
     }
-}
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/Privilege.php b/src/UnicaenAuth/Entity/Db/Privilege.php
index 4e0b7ae858eeea4023d55e1dd4cd53a9bf1ac29e..77702cd0dbaf1fface8c7530a1a068c076146ea8 100644
--- a/src/UnicaenAuth/Entity/Db/Privilege.php
+++ b/src/UnicaenAuth/Entity/Db/Privilege.php
@@ -2,257 +2,15 @@
 
 namespace UnicaenAuth\Entity\Db;
 
-use UnicaenAuth\Provider\Privilege\Privileges;
-use Zend\Permissions\Acl\Resource\ResourceInterface;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
+ * Privilege entity class.
  *
  * @ORM\Entity
  * @ORM\Table(name="privilege")
  */
-class Privilege implements ResourceInterface
+class Privilege extends AbstractPrivilege
 {
-    /**
-     * @var int
-     * @ORM\Id
-     * @ORM\Column(type="integer")
-     * @ORM\GeneratedValue(strategy="AUTO")
-     */
-    private $id;
 
-    /**
-     * @var string
-     * @ORM\Column(name="code", type="string", length=150, unique=false, nullable=false)
-     */
-    private $code;
-
-    /**
-     * @var string
-     * @ORM\Column(name="libelle", type="string", length=200, unique=false, nullable=false)
-     */
-    private $libelle;
-
-    /**
-     * @var int
-     * @ORM\Column(name="ordre", type="integer", unique=false, nullable=true)
-     */
-    private $ordre;
-
-    /**
-     * @var CategoriePrivilege
-     * @ORM\ManyToOne(targetEntity="CategoriePrivilege", inversedBy="privilege")
-     * @ORM\JoinColumn(name="categorie_id", referencedColumnName="id")
-     */
-    private $categorie;
-
-    /**
-     * @ORM\ManyToMany(targetEntity="UnicaenAuth\Entity\Db\Role",cascade={"all"})
-     * @ORM\JoinTable(
-     *     name="role_privilege",
-     *     joinColumns={@ORM\JoinColumn(name="privilege_id", referencedColumnName="id", onDelete="cascade")},
-     *     inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id", onDelete="cascade")}
-     *
-     * )
-     */
-    private $role;
-
-
-
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->role = new \Doctrine\Common\Collections\ArrayCollection();
-    }
-
-
-
-    /**
-     * Set code
-     *
-     * @param string $code
-     *
-     * @return Privilege
-     */
-    public function setCode($code)
-    {
-        $this->code = $code;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Get code
-     *
-     * @return string
-     */
-    public function getCode()
-    {
-        return $this->code;
-    }
-
-
-
-    public function getFullCode()
-    {
-        return $this->getCategorie()->getCode() . '-' . $this->getCode();
-    }
-
-
-
-    /**
-     * Set libelle
-     *
-     * @param string $libelle
-     *
-     * @return Privilege
-     */
-    public function setLibelle($libelle)
-    {
-        $this->libelle = $libelle;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Get libelle
-     *
-     * @return string
-     */
-    public function getLibelle()
-    {
-        return $this->libelle;
-    }
-
-
-
-    /**
-     *
-     * @return integer
-     */
-    function getOrdre()
-    {
-        return $this->ordre;
-    }
-
-
-
-    /**
-     *
-     * @param integer $ordre
-     *
-     * @return self
-     */
-    function setOrdre($ordre)
-    {
-        $this->ordre = $ordre;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Get id
-     *
-     * @return integer
-     */
-    public function getId()
-    {
-        return $this->id;
-    }
-
-
-
-    /**
-     * Set categorie
-     *
-     * @param CategoriePrivilege $categorie
-     *
-     * @return self
-     */
-    public function setCategorie(CategoriePrivilege $categorie = null)
-    {
-        $this->categorie = $categorie;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Get categorie
-     *
-     * @return CategoriePrivilege
-     */
-    public function getCategorie()
-    {
-        return $this->categorie;
-    }
-
-
-
-    /**
-     * Add role
-     *
-     * @param Role $role
-     *
-     * @return self
-     */
-    public function addRole(Role $role)
-    {
-        $this->role->add($role);
-
-        return $this;
-    }
-
-
-
-    /**
-     * Remove role
-     *
-     * @param Role $role
-     */
-    public function removeRole(Role $role)
-    {
-        $this->role->removeElement($role);
-    }
-
-
-
-    /**
-     * Get role
-     *
-     * @return \Doctrine\Common\Collections\Collection
-     */
-    public function getRole()
-    {
-        return $this->role;
-    }
-
-
-
-    /**
-     * @return string
-     */
-    public function __toString()
-    {
-        return $this->getLibelle();
-    }
-
-
-
-    /**
-     * @return string
-     */
-    public function getResourceId()
-    {
-        return Privileges::getResourceId($this);
-    }
 }
diff --git a/src/UnicaenAuth/Entity/Db/PrivilegeInterface.php b/src/UnicaenAuth/Entity/Db/PrivilegeInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..e8f7469debc41dd751ff14f857589046b1458e59
--- /dev/null
+++ b/src/UnicaenAuth/Entity/Db/PrivilegeInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace UnicaenAuth\Entity\Db;
+
+use Doctrine\Common\Collections\Collection;
+
+interface PrivilegeInterface
+{
+    /**
+     * @param string $code
+     * @return self
+     */
+    public function setCode($code);
+
+    /**
+     * @return string
+     */
+    public function getCode();
+
+    /**
+     * @return string
+     */
+    public function getFullCode();
+
+    /**
+     * @param string $libelle
+     * @return self
+     */
+    public function setLibelle($libelle);
+
+    /**
+     * @return string
+     */
+    public function getLibelle();
+
+    /**
+     * @return integer
+     */
+    function getOrdre();
+
+    /**
+     * @param integer $ordre
+     * @return self
+     */
+    function setOrdre($ordre);
+
+    /**
+     * @return integer
+     */
+    public function getId();
+
+    /**
+     * @param CategoriePrivilege $categorie
+     * @return self
+     */
+    public function setCategorie(CategoriePrivilege $categorie = null);
+
+    /**
+     * @return CategoriePrivilege
+     */
+    public function getCategorie();
+
+    /**
+     * @param RoleInterface $role
+     * @return self
+     */
+    public function addRole(RoleInterface $role);
+
+    /**
+     * @param RoleInterface $role
+     */
+    public function removeRole(RoleInterface $role);
+
+    /**
+     * @return Collection
+     */
+    public function getRole();
+
+    /**
+     * @return string
+     */
+    public function __toString();
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/Role.php b/src/UnicaenAuth/Entity/Db/Role.php
index fe8a4c7a04713dcce5b23fb7f044d0c17750a45c..70ff84981354ed0aeee9fddad1897701e11b90b6 100644
--- a/src/UnicaenAuth/Entity/Db/Role.php
+++ b/src/UnicaenAuth/Entity/Db/Role.php
@@ -1,236 +1,16 @@
 <?php
-/**
- * BjyAuthorize Module (https://github.com/bjyoungblood/BjyAuthorize)
- *
- * @link    https://github.com/bjyoungblood/BjyAuthorize for the canonical source repository
- * @license http://framework.zend.com/license/new-bsd New BSD License
- */
 
 namespace UnicaenAuth\Entity\Db;
 
-use BjyAuthorize\Acl\HierarchicalRoleInterface;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
- * An example entity that represents a role.
+ * Role entity class.
  *
  * @ORM\Entity
  * @ORM\Table(name="user_role")
  */
-class Role implements HierarchicalRoleInterface
+class Role extends AbstractRole
 {
-    /**
-     * @var int
-     * @ORM\Id
-     * @ORM\Column(type="integer")
-     * @ORM\GeneratedValue(strategy="AUTO")
-     */
-    protected $id;
-
-    /**
-     * @var string
-     * @ORM\Column(name="role_id", type="string", length=255, unique=true, nullable=false)
-     */
-    protected $roleId;
-
-    /**
-     * @var boolean
-     * @ORM\Column(name="is_default", type="boolean", nullable=true)
-     */
-    protected $isDefault = false;
-
-    /**
-     * @var Role
-     * @ORM\ManyToOne(targetEntity="Role")
-     */
-    protected $parent;
-
-    /**
-     * @var string
-     * @ORM\Column(name="ldap_filter", type="string", length=255, unique=true, nullable=true)
-     */
-    protected $ldapFilter;
-
-    /**
-     * @var \Doctrine\Common\Collections\Collection
-     * @ORM\ManyToMany(targetEntity="UnicaenAuth\Entity\Db\User")
-     * @ORM\JoinTable(name="user_role_linker",
-     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
-     *      inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
-     * )
-     */
-    protected $users;
-
-
-
-    /**
-     * Get the id.
-     *
-     * @return int
-     */
-    public function getId()
-    {
-        return $this->id;
-    }
-
-
-
-    /**
-     * Set the id.
-     *
-     * @param int $id
-     *
-     * @return self
-     */
-    public function setId($id)
-    {
-        $this->id = (int)$id;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Get the role id.
-     *
-     * @return string
-     */
-    public function getRoleId()
-    {
-        return $this->roleId;
-    }
-
-
-
-    /**
-     * Set the role id.
-     *
-     * @param string $roleId
-     *
-     * @return self
-     */
-    public function setRoleId($roleId)
-    {
-        $this->roleId = (string)$roleId;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Is this role the default one ?
-     *
-     * @return boolean
-     */
-    public function getIsDefault()
-    {
-        return $this->isDefault;
-    }
-
-
-
-    /**
-     * Set this role as the default one.
-     *
-     * @param boolean $isDefault
-     *
-     * @return self
-     */
-    public function setIsDefault($isDefault)
-    {
-        $this->isDefault = (boolean)$isDefault;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Get the parent role
-     *
-     * @return Role
-     */
-    public function getParent()
-    {
-        return $this->parent;
-    }
-
-
-
-    /**
-     * Set the parent role.
-     *
-     * @param Role $role
-     *
-     * @return self
-     */
-    public function setParent(Role $parent = null)
-    {
-        $this->parent = $parent;
-
-        return $this;
-    }
-
-
-
-    /**
-     * @return string
-     */
-    public function getLdapFilter()
-    {
-        return $this->ldapFilter;
-    }
-
-
-
-    /**
-     * @param string $ldapFilter
-     *
-     * @return Role
-     */
-    public function setLdapFilter($ldapFilter)
-    {
-        $this->ldapFilter = $ldapFilter;
-
-        return $this;
-    }
-
-
-
-    /**
-     * Get users.
-     *
-     * @return array
-     */
-    public function getUsers()
-    {
-        return $this->users->getValues();
-    }
-
-
-
-    /**
-     * Add a user to the role.
-     *
-     * @param User $user
-     *
-     * @return void
-     */
-    public function addUser($user)
-    {
-        $this->users[] = $user;
-    }
-
-
 
-    /**
-     *
-     * @return string
-     */
-    public function __toString()
-    {
-        return $this->getRoleId();
-    }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/RoleInterface.php b/src/UnicaenAuth/Entity/Db/RoleInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..1555fa4c604f264b2ce0105d9b4a9bf2d4c80b55
--- /dev/null
+++ b/src/UnicaenAuth/Entity/Db/RoleInterface.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace UnicaenAuth\Entity\Db;
+
+use BjyAuthorize\Acl\HierarchicalRoleInterface;
+
+interface RoleInterface extends HierarchicalRoleInterface
+{
+    /**
+     * @param int $id
+     * @return self
+     */
+    public function setId($id);
+
+    /**
+     * @return int
+     */
+    public function getId();
+
+    /**
+     * @return string
+     */
+    public function getRoleId();
+
+    /**
+     * @param string $roleId
+     * @return self
+     */
+    public function setRoleId($roleId);
+
+    /**
+     * @return boolean
+     */
+    public function getIsDefault();
+
+    /**
+     * @param boolean $isDefault
+     * @return self
+     */
+    public function setIsDefault($isDefault);
+
+    /**
+     * @return RoleInterface|null
+     */
+    public function getParent();
+
+    /**
+     * @param RoleInterface|null $parent
+     * @return self
+     */
+    public function setParent(RoleInterface $parent = null);
+
+    /**
+     * @return string
+     */
+    public function getLdapFilter();
+
+    /**
+     * @param string $ldapFilter
+     * @return self
+     */
+    public function setLdapFilter($ldapFilter);
+
+    /**
+     * @return UserInterface[]
+     */
+    public function getUsers();
+
+    /**
+     * @param UserInterface $user
+     * @return self
+     */
+    public function addUser(UserInterface $user);
+
+    /**
+     * @return string
+     */
+    public function __toString();
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/User.php b/src/UnicaenAuth/Entity/Db/User.php
index b041a31a6a4b04f63dac57fb1a0329cf6ceb3a36..77209f677574bfb1a1b7aa0e232b4aa2d9daaf73 100644
--- a/src/UnicaenAuth/Entity/Db/User.php
+++ b/src/UnicaenAuth/Entity/Db/User.php
@@ -1,5 +1,5 @@
 <?php
- 
+
 namespace UnicaenAuth\Entity\Db;
 
 use Doctrine\ORM\Mapping as ORM;
@@ -12,5 +12,5 @@ use Doctrine\ORM\Mapping as ORM;
  */
 class User extends AbstractUser
 {
-    
+
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Db/UserInterface.php b/src/UnicaenAuth/Entity/Db/UserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..479a859259d8df604542c0bf700e3f490c66045e
--- /dev/null
+++ b/src/UnicaenAuth/Entity/Db/UserInterface.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace UnicaenAuth\Entity\Db;
+
+use Doctrine\Common\Collections\Collection;
+
+interface UserInterface extends \ZfcUser\Entity\UserInterface
+{
+    /**
+     * @return int
+     */
+    public function getId();
+
+    /**
+     * @param int $id
+     * @return self
+     */
+    public function setId($id);
+
+    /**
+     * @return string
+     */
+    public function getUsername();
+
+    /**
+     * @param string $username
+     * @return self
+     */
+    public function setUsername($username);
+
+    /**
+     * @return string
+     */
+    public function getEmail();
+
+    /**
+     * @param string $email
+     * @return self
+     */
+    public function setEmail($email);
+
+    /**
+     * @return string
+     */
+    public function getDisplayName();
+
+    /**
+     * @param string $displayName
+     * @return self
+     */
+    public function setDisplayName($displayName);
+
+    /**
+     * Get password.
+     *
+     * @return string
+     */
+    public function getPassword();
+
+    /**
+     * @param string $password
+     * @return self
+     */
+    public function setPassword($password);
+
+    /**
+     * @return int
+     */
+    public function getState();
+
+    /**
+     * @param int $state
+     * @return self
+     */
+    public function setState($state);
+
+    /**
+     * @return Collection
+     */
+    public function getRoles();
+
+    /**
+     * @param RoleInterface $role
+     * @return self
+     */
+    public function addRole(RoleInterface $role);
+
+    /**
+     * @return string
+     */
+    public function __toString();
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
new file mode 100644
index 0000000000000000000000000000000000000000..a51faa91f492fce9ed8260fa48bb533f4f2af6ef
--- /dev/null
+++ b/src/UnicaenAuth/Entity/Shibboleth/ShibUser.php
@@ -0,0 +1,171 @@
+<?php
+
+namespace UnicaenAuth\Entity\Shibboleth;
+
+use ZfcUser\Entity\UserInterface;
+
+class ShibUser implements UserInterface
+{
+    /**
+     * @var string
+     */
+    protected $id;
+
+    /**
+     * @var string
+     */
+    protected $username;
+
+    /**
+     * @var string
+     */
+    protected $email;
+
+    /**
+     * @var string
+     */
+    protected $displayName;
+
+    /**
+     * @var int
+     */
+    protected $state = 1;
+
+
+    /**
+     * @return string
+     */
+    public function getEppn()
+    {
+        return $this->getUsername();
+    }
+
+    /**
+     * Get id.
+     *
+     * @return string
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Set id.
+     *
+     * @param string $id
+     */
+    public function setId($id)
+    {
+        $this->id = $id;
+    }
+
+    /**
+     * Get username.
+     *
+     * @return string
+     */
+    public function getUsername()
+    {
+        return $this->username;
+    }
+
+    /**
+     * Set username.
+     *
+     * @param string $username
+     */
+    public function setUsername($username)
+    {
+        $this->username = $username;
+    }
+
+    /**
+     * Get email.
+     *
+     * @return string
+     */
+    public function getEmail()
+    {
+        return $this->email;
+    }
+
+    /**
+     * Set email.
+     *
+     * @param string $email
+     */
+    public function setEmail($email)
+    {
+        $this->email = $email;
+    }
+
+    /**
+     * Get displayName.
+     *
+     * @return string
+     */
+    public function getDisplayName()
+    {
+        return $this->displayName;
+    }
+
+    /**
+     * Set displayName.
+     *
+     * @param string $displayName
+     */
+    public function setDisplayName($displayName)
+    {
+        $this->displayName = $displayName;
+    }
+
+    /**
+     * Get password.
+     *
+     * @return string
+     */
+    public function getPassword()
+    {
+        return 'shib';
+    }
+
+    /**
+     * Set password.
+     *
+     * @param string $password
+     */
+    public function setPassword($password)
+    {
+
+    }
+
+    /**
+     * Get state.
+     *
+     * @return int
+     */
+    public function getState()
+    {
+        return $this->state;
+    }
+
+    /**
+     * Set state.
+     *
+     * @param int $state
+     */
+    public function setState($state)
+    {
+        $this->state = $state;
+    }
+
+    /**
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getDisplayName();
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php b/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php
index 9d56da029d3113e7c66d10d760071f1bc9ab2b3c..24410c2b111b2719c2b588197d6437920b1c4e99 100644
--- a/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php
+++ b/src/UnicaenAuth/Event/Listener/UserRoleSelectedEventAbstractListener.php
@@ -28,7 +28,6 @@ abstract class UserRoleSelectedEventAbstractListener implements ListenerAggregat
      * Renseigne les relations 'intervenant' et 'personnel' avant que l'objet soit persisté.
      *
      * @param UserRoleSelectedEvent $e
-     * @return
      */
     abstract public function postSelection(UserRoleSelectedEvent $e);
 
diff --git a/src/UnicaenAuth/Event/UserAuthenticatedEvent.php b/src/UnicaenAuth/Event/UserAuthenticatedEvent.php
index dfb893f354844b479b602efedaf14f51b80f32da..725bf30aa88ccdf8a86b74852c71d7eaa5ad637a 100644
--- a/src/UnicaenAuth/Event/UserAuthenticatedEvent.php
+++ b/src/UnicaenAuth/Event/UserAuthenticatedEvent.php
@@ -2,6 +2,7 @@
 
 namespace UnicaenAuth\Event;
 
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
 use UnicaenApp\Entity\Ldap\People;
 use Zend\EventManager\Event;
 use ZfcUser\Entity\UserInterface;
@@ -14,12 +15,26 @@ use ZfcUser\Entity\UserInterface;
 class UserAuthenticatedEvent extends Event
 {
     const PRE_PERSIST      = 'prePersist';
+
     const PARAM_DB_USER    = 'db_user';
     const PARAM_LDAP_USER  = 'ldap_user';
-    
+    const PARAM_SHIB_USER  = 'shib_user';
+
+    /**
+     * Spécifie l'entité utilisateur issue de la base de données.
+     *
+     * @param UserInterface $dbUser
+     * @return UserAuthenticatedEvent
+     */
+    public function setDbUser(UserInterface $dbUser)
+    {
+        $this->setParam(self::PARAM_DB_USER, $dbUser);
+        return $this;
+    }
+
     /**
      * Retourne l'entité utilisateur issue de la base de données.
-     * 
+     *
      * @return UserInterface
      */
     public function getDbUser()
@@ -27,9 +42,21 @@ class UserAuthenticatedEvent extends Event
         return $this->getParam(self::PARAM_DB_USER);
     }
 
+    /**
+     * Spécifie l'entité utilisateur issue de l'annuaire LDAP.
+     *
+     * @param People $ldapUser
+     * @return UserAuthenticatedEvent
+     */
+    public function setLdapUser(People $ldapUser)
+    {
+        $this->setParam(self::PARAM_LDAP_USER, $ldapUser);
+        return $this;
+    }
+
     /**
      * Retourne l'entité utilisateur issue de l'annuaire LDAP.
-     * 
+     *
      * @return People
      */
     public function getLdapUser()
@@ -38,26 +65,24 @@ class UserAuthenticatedEvent extends Event
     }
 
     /**
-     * Spécifie l'entité utilisateur issue de la base de données.
-     * 
-     * @param UserInterface $dbUser
+     * Spécifie l'entité utilisateur issue de l'authentification Shibboleth.
+     *
+     * @param ShibUser $shibUser
      * @return UserAuthenticatedEvent
      */
-    public function setDbUser(UserInterface $dbUser)
+    public function setShibUser(ShibUser $shibUser)
     {
-        $this->setParam(self::PARAM_DB_USER, $dbUser);
+        $this->setParam(self::PARAM_SHIB_USER, $shibUser);
         return $this;
     }
 
     /**
-     * Spécifie l'entité utilisateur issue de l'annuaire LDAP.
-     * 
-     * @param People $ldapUser
-     * @return UserAuthenticatedEvent
+     * Retourne l'entité utilisateur issue de l'authentification Shibboleth.
+     *
+     * @return ShibUser
      */
-    public function setLdapUser(People $ldapUser)
+    public function getShibUser()
     {
-        $this->setParam(self::PARAM_LDAP_USER, $ldapUser);
-        return $this;
+        return $this->getParam(self::PARAM_SHIB_USER);
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Options/ModuleOptions.php b/src/UnicaenAuth/Options/ModuleOptions.php
index c5f942aa1972f0a539647a42adcfdda7fac5d7d3..4d3b5fb5570b353bda22c0e452f6d71517ed6cc1 100644
--- a/src/UnicaenAuth/Options/ModuleOptions.php
+++ b/src/UnicaenAuth/Options/ModuleOptions.php
@@ -1,4 +1,5 @@
 <?php
+
 namespace UnicaenAuth\Options;
 
 /**
@@ -18,6 +19,11 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      */
     protected $saveLdapUserInDatabase = false;
 
+    /**
+     * @var array
+     */
+    protected $shibboleth = [];
+
     /**
      * @var array
      */
@@ -27,9 +33,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
      * @var string
      */
     protected $entityManagerName = 'doctrine.entitymanager.orm_default';
-
-
-
+    
     /**
      * set usernames allowed to make usurpation
      *
@@ -44,8 +48,6 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
         return $this;
     }
 
-
-
     /**
      * get usernames allowed to make usurpation
      *
@@ -56,8 +58,6 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
         return $this->usurpationAllowedUsernames;
     }
 
-
-
     /**
      * Spécifie si l'utilisateur authentifié doit être enregistré dans la base
      * de données de l'appli
@@ -72,9 +72,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 
         return $this;
     }
-
-
-
+    
     /**
      * Retourne la valeur du flag spécifiant si l'utilisateur authentifié doit être
      * enregistré dans la base de données de l'appli
@@ -85,9 +83,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     {
         return $this->saveLdapUserInDatabase;
     }
-
-
-
+    
     /**
      * set cas connection params
      *
@@ -101,9 +97,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 
         return $this;
     }
-
-
-
+    
     /**
      * get cas connection params
      *
@@ -113,9 +107,31 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     {
         return $this->cas;
     }
+    
+    /**
+     * set shibboleth connection params
+     *
+     * @param array $shibboleth
+     *
+     * @return ModuleOptions
+     */
+    public function setShibboleth(array $shibboleth = [])
+    {
+        $this->shibboleth = $shibboleth;
 
+        return $this;
+    }
 
-
+    /**
+     * get shibboleth connection params
+     *
+     * @return array
+     */
+    public function getShibboleth()
+    {
+        return $this->shibboleth;
+    }
+    
     /**
      * @return string
      */
@@ -123,9 +139,7 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
     {
         return $this->entityManagerName;
     }
-
-
-
+    
     /**
      * @param string $entityManagerName
      *
@@ -137,6 +151,4 @@ class ModuleOptions extends \ZfcUser\Options\ModuleOptions
 
         return $this;
     }
-
-
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/Provider/Identity/Db.php b/src/UnicaenAuth/Provider/Identity/Db.php
index baa3f1453eb21b3538bc4169011a0e09bf91de18..cb92485e2cb12198ee7120d4922cdffc7959e54b 100644
--- a/src/UnicaenAuth/Provider/Identity/Db.php
+++ b/src/UnicaenAuth/Provider/Identity/Db.php
@@ -4,10 +4,9 @@ namespace UnicaenAuth\Provider\Identity;
 use BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider;
 use BjyAuthorize\Provider\Role\ProviderInterface;
 use UnicaenApp\Entity\Ldap\People;
-use UnicaenAuth\Entity\Db\Role;
+use UnicaenAuth\Entity\Db\AbstractRole;
 use UnicaenAuth\Service\Traits\RoleServiceAwareTrait;
 use Zend\Ldap\Ldap;
-use Zend\ServiceManager\ServiceLocatorAwareTrait;
 use ZfcUser\Entity\UserInterface;
 use Traversable;
 
@@ -91,12 +90,12 @@ class Db extends AuthenticationIdentityProvider implements ChainableProvider, \B
 
 
     /**
-     * @param Role   $role
+     * @param AbstractRole $role
      * @param string $dn
      *
      * @return bool
      */
-    protected function roleMatches(Role $role, $dn)
+    protected function roleMatches(AbstractRole $role, $dn)
     {
         try {
             return 1 === $this->getLdap()->count($role->getLdapFilter(), $dn, Ldap::SEARCH_SCOPE_SUB);
diff --git a/src/UnicaenAuth/Service/AbstractService.php b/src/UnicaenAuth/Service/AbstractService.php
index 190b8cbd8d3a7e75fdd1e2553ed3d8ae11680638..6ed1d710e51eb6887be836c9a49142a244a7216e 100644
--- a/src/UnicaenAuth/Service/AbstractService.php
+++ b/src/UnicaenAuth/Service/AbstractService.php
@@ -17,8 +17,6 @@ abstract class AbstractService implements ServiceLocatorAwareInterface
      */
     private $serviceAuthorize;
 
-
-
     /**
      * @return \UnicaenAuth\Service\AuthorizeService
      */
@@ -31,12 +29,10 @@ abstract class AbstractService implements ServiceLocatorAwareInterface
         return $this->serviceAuthorize;
     }
 
-
-
     /**
      * @return \Doctrine\ORM\EntityManager
      */
-    protected function getEntityManager()
+    public function getEntityManager()
     {
         if (!$this->entityManager) {
             $moduleOptions = $this->getServiceLocator()->get('unicaen-auth_module_options');
@@ -46,4 +42,4 @@ abstract class AbstractService implements ServiceLocatorAwareInterface
 
         return $this->entityManager;
     }
-}
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/PrivilegeService.php b/src/UnicaenAuth/Service/PrivilegeService.php
index 2326e3a6914564a6b972ce011934ee66d6b9ab78..82e29c21e24938bfa9bf0302a0b88a072eab376d 100644
--- a/src/UnicaenAuth/Service/PrivilegeService.php
+++ b/src/UnicaenAuth/Service/PrivilegeService.php
@@ -2,75 +2,95 @@
 
 namespace UnicaenAuth\Service;
 
-use UnicaenAuth\Entity\Db\Privilege;
-use UnicaenAuth\Entity\Db\Role;
+use Doctrine\ORM\EntityRepository;
+use InvalidArgumentException;
+use UnicaenAuth\Entity\Db\PrivilegeInterface;
+use UnicaenAuth\Entity\Db\RoleInterface;
 use UnicaenAuth\Provider\Privilege\PrivilegeProviderInterface;
-use \BjyAuthorize\Provider\Resource\ProviderInterface as ResourceProviderInterface;
+use BjyAuthorize\Provider\Resource\ProviderInterface as ResourceProviderInterface;
 use UnicaenAuth\Provider\Privilege\Privileges;
 
-class PrivilegeService extends AbstractService implements PrivilegeProviderInterface, ResourceProviderInterface
+class PrivilegeService extends AbstractService
+    implements PrivilegeProviderInterface, ResourceProviderInterface
 {
+    /**
+     * @var string
+     */
+    protected $privilegeEntityClass;
+
+    /**
+     * @param string $privilegeEntityClass
+     */
+    public function setPrivilegeEntityClass($privilegeEntityClass)
+    {
+        if (! class_exists($privilegeEntityClass) || ! in_array(PrivilegeInterface::class, class_implements($privilegeEntityClass))) {
+            throw new InvalidArgumentException("La classe spécifiée pour l'entité privilège doit implémenter " . PrivilegeInterface::class);
+        }
+
+        $this->privilegeEntityClass = $privilegeEntityClass;
+    }
+
     /**
      * @var array
      */
     protected $privilegesRoles;
 
-
+    /**
+     * @return EntityRepository
+     */
+    public function getRepo()
+    {
+        return $this->getEntityManager()->getRepository($this->privilegeEntityClass);
+    }
 
     /**
-     * @return Privilege[]
+     * @return PrivilegeInterface[]
      */
     public function getList()
     {
-        $dql        = 'SELECT p, c FROM UnicaenAuth\Entity\Db\Privilege p JOIN p.categorie c ORDER BY c.ordre, p.ordre';
-        $query      = $this->getEntityManager()->createQuery($dql);
-        $privileges = $query->getResult();
+        $qb = $this->getRepo()->createQueryBuilder('p')
+            ->addSelect('c')
+            ->join('p.categorie', 'c')
+            ->addOrderBy('c.ordre')
+            ->addOrderBy('p.ordre');
 
-        return $privileges;
+        return $qb->getQuery()->getResult();
     }
 
-
-
     /**
      * @param $id
      *
-     * @return null|Privilege
+     * @return null|PrivilegeInterface
      */
     public function get($id)
     {
-        return $this->getEntityManager()->getRepository('UnicaenAuth\Entity\Db\Privilege')->findOneBy(['id' => $id]);
+        return $this->getRepo()->findOneBy(['id' => $id]);
     }
 
-
-
     /**
-     * @param Privilege $privilege
-     * @param Role      $role
+     * @param PrivilegeInterface $privilege
+     * @param RoleInterface      $role
      *
      * @throws \Doctrine\DBAL\DBALException
      */
-    public function addRole(Privilege $privilege, Role $role)
+    public function addRole(PrivilegeInterface $privilege, RoleInterface $role)
     {
         $privilege->addRole($role);
         $this->getEntityManager()->flush();
     }
 
-
-
     /**
-     * @param Privilege $privilege
-     * @param Role      $role
+     * @param PrivilegeInterface $privilege
+     * @param RoleInterface      $role
      *
      * @throws \Doctrine\DBAL\DBALException
      */
-    public function removeRole(Privilege $privilege, Role $role)
+    public function removeRole(PrivilegeInterface $privilege, RoleInterface $role)
     {
         $privilege->removeRole($role);
         $this->getEntityManager()->flush();
     }
 
-
-
     /**
      * Retourne un tableau à deux dimentions composé de chaînes de caractère UNIQUEMENT
      *
@@ -86,20 +106,18 @@ class PrivilegeService extends AbstractService implements PrivilegeProviderInter
     {
         if (null === $this->privilegesRoles) {
             $this->privilegesRoles = [];
-            $dql = '
-            SELECT
-              p, c, r
-            FROM
-              UnicaenAuth\Entity\Db\Privilege p
-              JOIN p.categorie c
-              JOIN p.role r
-            ';
-            $result = $this->getEntityManager()->createQuery($dql)->getResult();
-            foreach( $result as $privilege){
-                /* @var $privilege Privilege */
+
+            $qb = $this->getRepo()->createQueryBuilder('p')
+                ->addSelect('c, r')
+                ->join('p.categorie', 'c')
+                ->join('p.role', 'r');
+            $result = $qb->getQuery()->getResult();
+
+            foreach ($result as $privilege) {
+                /* @var $privilege PrivilegeInterface */
                 $pr = [];
-                foreach( $privilege->getRole() as $role ){
-                    /* @var $role Role */
+                foreach ($privilege->getRole() as $role) {
+                    /* @var $role RoleInterface */
                     $pr[] = $role->getRoleId();
                 }
                 $this->privilegesRoles[$privilege->getFullCode()] = $pr;
@@ -109,14 +127,12 @@ class PrivilegeService extends AbstractService implements PrivilegeProviderInter
         return $this->privilegesRoles;
     }
 
-
-
     /**
      * @return array
      */
     public function getResources()
     {
-        $resources  = [];
+        $resources = [];
         $privileges = array_keys($this->getPrivilegesRoles());
         foreach ($privileges as $privilege) {
             $resources[] = Privileges::getResourceId($privilege);
@@ -124,5 +140,4 @@ class PrivilegeService extends AbstractService implements PrivilegeProviderInter
 
         return $resources;
     }
-
-}
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/PrivilegeServiceFactory.php b/src/UnicaenAuth/Service/PrivilegeServiceFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..04271e3b7121d866e302cba9ece5d006218d2639
--- /dev/null
+++ b/src/UnicaenAuth/Service/PrivilegeServiceFactory.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use UnicaenAuth\Entity\Db\Privilege;
+use Zend\ServiceManager\FactoryInterface;
+use Zend\ServiceManager\ServiceLocatorInterface;
+
+class PrivilegeServiceFactory implements FactoryInterface
+{
+    public function createService(ServiceLocatorInterface $serviceLocator)
+    {
+        $config = $serviceLocator->get('Config');
+
+        if (! isset($config['unicaen-auth']['privilege_entity_class'])) {
+//            throw new InvalidArgumentException("La classe de l'entité privilège n'a pas été trouvée dans la config");
+            $config['unicaen-auth']['privilege_entity_class'] = Privilege::class;
+        }
+
+        $service = new PrivilegeService();
+        $service->setPrivilegeEntityClass($config['unicaen-auth']['privilege_entity_class']);
+
+        return $service;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/RoleService.php b/src/UnicaenAuth/Service/RoleService.php
index 162042dd9a966908482262f5e73ebbb500355665..64b2713a5c4be2a748e3b049a3d52b3f5821d602 100644
--- a/src/UnicaenAuth/Service/RoleService.php
+++ b/src/UnicaenAuth/Service/RoleService.php
@@ -2,9 +2,9 @@
 
 namespace UnicaenAuth\Service;
 
-use Doctrine\Common\Persistence\ObjectRepository;
-use UnicaenAuth\Entity\Db\Role;
-
+use Doctrine\ORM\EntityRepository;
+use InvalidArgumentException;
+use UnicaenAuth\Entity\Db\RoleInterface;
 
 /**
  * Class RoleService
@@ -15,49 +15,59 @@ use UnicaenAuth\Entity\Db\Role;
 class RoleService extends AbstractService
 {
     /**
-     * @return ObjectRepository
+     * @var string
      */
-    public function getRepo()
+    protected $roleEntityClass;
+
+    /**
+     * @param string $roleEntityClass
+     */
+    public function setRoleEntityClass($roleEntityClass)
     {
-        return $this->getEntityManager()->getRepository('UnicaenAuth\Entity\Db\Role');
-    }
+        if (! class_exists($roleEntityClass) || ! in_array(RoleInterface::class, class_implements($roleEntityClass))) {
+            throw new InvalidArgumentException("La classe spécifiée pour l'entité rôle doit implémenter " . RoleInterface::class);
+        }
 
+        $this->roleEntityClass = $roleEntityClass;
+    }
 
+    /**
+     * @return EntityRepository
+     */
+    public function getRepo()
+    {
+        return $this->getEntityManager()->getRepository($this->roleEntityClass);
+    }
 
     /**
-     * @return Role[]
+     * @return RoleInterface[]
      */
     public function getList()
     {
-        $dql   = 'SELECT r FROM UnicaenAuth\Entity\Db\Role r ORDER BY r.roleId';
-        $query = $this->getEntityManager()->createQuery($dql);
-        $roles = $query->getResult();
+        $qb = $this->getRepo()->createQueryBuilder('r')->addOrderBy('r.roleId');
+        $roles = $qb->getQuery()->getResult();
 
         return $roles;
     }
 
-
-
     /**
      * @param $id
      *
-     * @return null|Role
+     * @return null|RoleInterface
      */
     public function get($id)
     {
         return $this->getRepo()->findOneBy(['id' => $id]);
     }
 
-
-
     /**
      * Sauvegarde le rôle en BDD
      *
-     * @param Role $role
-     *
+     * @param RoleInterface $role
      * @return self
+     * @throws \Doctrine\ORM\OptimisticLockException
      */
-    public function save(Role $role)
+    public function save(RoleInterface $role)
     {
         $this->getEntityManager()->persist($role);
         $this->getEntityManager()->flush($role);
@@ -65,16 +75,14 @@ class RoleService extends AbstractService
         return $this;
     }
 
-
-
     /**
      * Supprime un rôle
      *
-     * @param Role $role
-     *
+     * @param RoleInterface $role
      * @return $this
+     * @throws \Doctrine\ORM\OptimisticLockException
      */
-    public function delete(Role $role)
+    public function delete(RoleInterface $role)
     {
         $this->getEntityManager()->remove($role);
         $this->getEntityManager()->flush($role);
@@ -82,15 +90,13 @@ class RoleService extends AbstractService
         return $this;
     }
 
-
-
     /**
      * Nouvelle entité
      *
-     * @return Role
+     * @return RoleInterface
      */
     public function newEntity()
     {
-        return new Role;
+        return new $this->roleEntityClass;
     }
-}
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/RoleServiceFactory.php b/src/UnicaenAuth/Service/RoleServiceFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..74c4057c8d3d7bf455a4459f060d828453e3e84a
--- /dev/null
+++ b/src/UnicaenAuth/Service/RoleServiceFactory.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use UnicaenAuth\Entity\Db\Role;
+use Zend\ServiceManager\FactoryInterface;
+use Zend\ServiceManager\ServiceLocatorInterface;
+
+class RoleServiceFactory implements FactoryInterface
+{
+    public function createService(ServiceLocatorInterface $serviceLocator)
+    {
+        $config = $serviceLocator->get('Config');
+
+        if (! isset($config['unicaen-auth']['role_entity_class'])) {
+            $config['unicaen-auth']['role_entity_class'] = Role::class;
+        }
+
+        $service = new RoleService();
+        $service->setRoleEntityClass($config['unicaen-auth']['role_entity_class']);
+
+        return $service;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/ShibService.php b/src/UnicaenAuth/Service/ShibService.php
new file mode 100644
index 0000000000000000000000000000000000000000..902e3f55c0eb97eb5fc52843e8040f68b164762f
--- /dev/null
+++ b/src/UnicaenAuth/Service/ShibService.php
@@ -0,0 +1,175 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use UnicaenApp\Exception\RuntimeException;
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
+use UnicaenAuth\Options\ModuleOptions;
+use Zend\Mvc\Router\Http\TreeRouteStack;
+
+/**
+ * Shibboleth service
+ *
+ * @author Unicaen
+ */
+class ShibService
+{
+    /**
+     * @var ModuleOptions
+     */
+    protected $options;
+
+    /**
+     * @var \UnicaenAuth\Entity\Shibboleth\ShibUser
+     */
+    protected $authenticatedUser;
+
+    /**
+     * @return string
+     */
+    static public function apacheConfigSnippet()
+    {
+        $text = <<<EOS
+<Location "/">
+    AuthType Shibboleth
+    ShibRequestSetting requireSession false
+    Require shibboleth
+</Location>
+<Location "/auth/shibboleth">
+        AuthType Shibboleth
+        ShibRequestSetting requireSession true
+        Require shibboleth
+</Location>
+EOS;
+        return $text;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isShibbolethEnable()
+    {
+        $options = $this->options->getShibboleth();
+
+        return array_key_exists('enable', $options) && (bool) $options['enable'];
+    }
+
+    /**
+     * @return ShibUser|null
+     */
+    public function getAuthenticatedUser()
+    {
+        if ($this->authenticatedUser === null) {
+            if (empty($_SERVER['REMOTE_USER'])) {
+                return null;
+            }
+            $this->authenticatedUser = $this->createShibUser();
+        }
+
+        return $this->authenticatedUser;
+    }
+
+    /**
+     * @return ShibUser
+     */
+    private function createShibUser()
+    {
+        $eppn = $_SERVER['REMOTE_USER'];
+
+        if (isset($_SERVER['supannEtuId'])) {
+            $id = $_SERVER['supannEtuId'];
+        } elseif (isset($_SERVER['supannEmpId'])) {
+            $id = $_SERVER['supannEmpId'];
+        } else {
+            throw new RuntimeException('Un au moins des attributs suivants doivent exister dans $_SERVER : supannEtuId, supannEmpId.');
+        }
+
+        $mail = null;
+        if (isset($_SERVER['mail'])) {
+            $mail = $_SERVER['mail'];
+        }
+
+        $displayName = null;
+        if (isset($_SERVER['displayName'])) {
+            $displayName = $_SERVER['displayName'];
+        }
+
+        $shibUser = new ShibUser();
+        $shibUser->setId($id);
+        $shibUser->setUsername($eppn);
+        $shibUser->setDisplayName($displayName);
+        $shibUser->setEmail($mail);
+
+        return $shibUser;
+    }
+
+    /**
+     * Retourne l'URL de déconnexion Shibboleth.
+     *
+     * @param string $returnAbsoluteUrl Eventuelle URL *absolue* de retour après déconnexion
+     * @return string
+     */
+    public function getLogoutUrl($returnAbsoluteUrl = null)
+    {
+        $logoutRelativeUrl = '/Shibboleth.sso/Logout?return='; // NB: '?return=' semble obligatoire!
+
+        if ($returnAbsoluteUrl) {
+            $logoutRelativeUrl .= urlencode($returnAbsoluteUrl);
+        }
+
+        return $logoutRelativeUrl;
+    }
+
+    /**
+     * @param ModuleOptions $options
+     */
+    public function setOptions(ModuleOptions $options)
+    {
+        $this->options = $options;
+    }
+
+    /**
+     * @param TreeRouteStack $router
+     */
+    public function reconfigureRoutesForShibAuth(TreeRouteStack $router)
+    {
+        $router->addRoutes([
+            // remplace les routes existantes (cf. config du module)
+            'zfcuser' => [
+                'type'          => 'Literal',
+                'priority'      => 1000,
+                'options'       => [
+                    'route'    => '/auth',
+                    'defaults' => [
+                        'controller' => 'zfcuser',
+                        'action'     => 'index',
+                    ],
+                ],
+                'may_terminate' => true,
+                'child_routes'  => [
+                    'login' => [
+                        'type' => 'Literal',
+                        'options' => [
+                            'route' => '/connexion',
+                            'defaults' => [
+                                'controller' => 'zfcuser', // NB: lorsque l'auth Shibboleth est activée, la page propose
+                                'action'     => 'login',   //     2 possibilités d'auth : LDAP et Shibboleth.
+                            ],
+                        ],
+                    ],
+                    'logout' => [
+                        'type'    => 'Segment',
+                        'options' => [
+                            'route'    => '/:operation/shibboleth/',
+                            'defaults' => [
+                                'controller' => 'UnicaenAuth\Controller\Auth',
+                                'action'     => 'shibboleth',
+                                'operation'  => 'deconnexion'
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/ShibServiceFactory.php b/src/UnicaenAuth/Service/ShibServiceFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..418f717049ccb1b7477009a156456d05ca9062eb
--- /dev/null
+++ b/src/UnicaenAuth/Service/ShibServiceFactory.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace UnicaenAuth\Service;
+
+use UnicaenAuth\Options\ModuleOptions;
+use Zend\ServiceManager\ServiceLocatorInterface;
+
+class ShibServiceFactory
+{
+    public function __invoke(ServiceLocatorInterface $sl)
+    {
+        /** @var ModuleOptions $options */
+        $options = $sl->get('unicaen-auth_module_options');
+
+        $service = new ShibService();
+        $service->setOptions($options);
+
+        return $service;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/Traits/ShibServiceAwareTrait.php b/src/UnicaenAuth/Service/Traits/ShibServiceAwareTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..825e8987bf4197d0d8be28539e73bbb53986f9f0
--- /dev/null
+++ b/src/UnicaenAuth/Service/Traits/ShibServiceAwareTrait.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace UnicaenAuth\Service\Traits;
+
+use UnicaenAuth\Service\ShibService;
+
+trait ShibServiceAwareTrait
+{
+    /**
+     * @var ShibService
+     */
+    protected $shibService;
+
+    /**
+     * @param ShibService $shibService
+     */
+    public function setShibService(ShibService $shibService)
+    {
+        $this->shibService = $shibService;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/Traits/UserServiceAwareTrait.php b/src/UnicaenAuth/Service/Traits/UserServiceAwareTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..74a268ff27467b748e46a095e0e907b38f90ff07
--- /dev/null
+++ b/src/UnicaenAuth/Service/Traits/UserServiceAwareTrait.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace UnicaenAuth\Service\Traits;
+
+use UnicaenAuth\Service\User as UserService;
+
+trait UserServiceAwareTrait
+{
+    /**
+     * @var UserService
+     */
+    protected $userService;
+
+    /**
+     * @param UserService $userService
+     */
+    public function setUserService(UserService $userService)
+    {
+        $this->userService = $userService;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/Service/User.php b/src/UnicaenAuth/Service/User.php
index c1f2f67ca09721fe917ac1b1ae48d400442d4e2f..180513a7ecefd1e8f9d2641d65de1ff245f84c08 100644
--- a/src/UnicaenAuth/Service/User.php
+++ b/src/UnicaenAuth/Service/User.php
@@ -1,18 +1,19 @@
 <?php
+
 namespace UnicaenAuth\Service;
 
+use UnicaenAuth\Event\UserAuthenticatedEvent;
 use PDOException;
-use UnicaenApp\Exception;
+use UnicaenApp\Entity\Ldap\People;
+use UnicaenApp\Exception\RuntimeException;
 use UnicaenApp\Mapper\Ldap\People as LdapPeopleMapper;
+use UnicaenAuth\Entity\Shibboleth\ShibUser;
 use UnicaenAuth\Options\ModuleOptions;
-use UnicaenAuth\Event\UserAuthenticatedEvent;
-use Zend\ServiceManager\ServiceLocatorAwareInterface;
-use Zend\ServiceManager\ServiceLocatorAwareTrait;
-use Zend\ServiceManager\ServiceManager;
-use Zend\ServiceManager\ServiceManagerAwareInterface;
 use Zend\EventManager\EventManagerAwareInterface;
 use Zend\EventManager\EventManagerInterface;
-use ZfcUser\Authentication\Adapter\AdapterChainEvent as AuthEvent;
+use Zend\ServiceManager\ServiceLocatorAwareInterface;
+use Zend\ServiceManager\ServiceLocatorAwareTrait;
+use ZfcUser\Entity\UserInterface;
 use ZfcUser\Options\AuthenticationOptionsInterface;
 use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions;
 
@@ -20,17 +21,13 @@ use ZfcUser\Options\ModuleOptions as ZfcUserModuleOptions;
  * Service d'enregistrement dans la table des utilisateurs de l'application
  * de l'utilisateur authentifié avec succès.
  *
- * Est notifié via la méthode 'userAuthenticated()' lorsque l'authentification
- * est terminée avec succès.
- *
  * @see \UnicaenAuth\Authentication\Adapter\AbstractFactory
- * @author Bertrand GAUTHIER <bertrand.gauthier at unicaen.fr>
+ * @author Unicaen
  */
 class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
 {
     use ServiceLocatorAwareTrait;
 
-
     const EVENT_USER_AUTHENTICATED_PRE_PERSIST = 'userAuthenticated.prePersist';
 
     /**
@@ -56,14 +53,35 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
     /**
      * Save authenticated user in database from LDAP data.
      *
+     * @param UserInterface|People $userData
      * @return bool
      */
-    public function userAuthenticated(AuthEvent $e)
+    public function userAuthenticated($userData)
     {
         if (!$this->getOptions()->getSaveLdapUserInDatabase()) {
             return false;
         }
-        if (!($username = $e->getIdentity())) {
+
+        switch (true) {
+            case $userData instanceof People:
+                $username = $userData->getSupannAliasLogin();
+                $email = $userData->getMail();
+                $password = 'ldap';
+                $state = in_array('deactivated', ldap_explode_dn($userData->getDn(), 1)) ? 0 : 1;
+
+                break;
+            case $userData instanceof ShibUser:
+                $username = $userData->getUsername();
+                $email = $userData->getEmail();
+                $password = 'shib';
+                $state = 1;
+                break;
+            default:
+                throw new RuntimeException("A implémenter!!");
+                break;
+        }
+
+        if (!$username) {
             return false;
         }
 
@@ -73,18 +91,13 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
         }
 
         if (!is_string($username)) {
-            throw new Exception("Identité rencontrée inattendue.");
-        }
-
-        // recherche de l'individu dans l'annuaire LDAP
-        $ldapPeople = $this->getLdapPeopleMapper()->findOneByUsername($username);
-        if (!$ldapPeople) {
-            return false;
+            throw new RuntimeException("Identité rencontrée inattendue.");
         }
 
         // update/insert de l'utilisateur dans la table de l'appli
         $mapper = $this->getServiceLocator()->get('zfcuser_user_mapper'); /* @var $mapper \ZfcUserDoctrineORM\Mapper\User */
         try {
+            /** @var UserInterface $entity */
             $entity = $mapper->findByUsername($username);
             if (!$entity) {
                 $entityClass = $this->getZfcUserOptions()->getUserEntityClass();
@@ -95,30 +108,44 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
             else {
                 $method = 'update';
             }
-            $entity->setEmail($ldapPeople->getMail());
-            $entity->setDisplayName($ldapPeople->getDisplayName());
-            $entity->setPassword('ldap');
-            $entity->setState(in_array('deactivated', ldap_explode_dn($ldapPeople->getDn(), 1)) ? 0 : 1);
-
-            // déclenche l'événement donnant aux applications clientes l'opportunité de modifier l'entité
-            // utilisateur avant qu'elle ne soit persistée
-            $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::PRE_PERSIST);
-            $event
-                    ->setDbUser($entity)
-                    ->setLdapUser($ldapPeople)
-                    ->setTarget($this);
-            $this->getEventManager()->trigger($event);
+            $entity->setEmail($email);
+            $entity->setDisplayName($userData->getDisplayName());
+            $entity->setPassword($password);
+            $entity->setState($state);
+
+            // pre-persist
+            $this->triggerUserAuthenticatedEvent($entity, $userData);
 
             // persist
             $mapper->$method($entity);
         }
         catch (PDOException $pdoe) {
-            throw new Exception("Impossible d'enregistrer l'utilisateur authentifié dans la base de données.", null, $pdoe);
+            throw new RuntimeException("Impossible d'enregistrer l'utilisateur authentifié dans la base de données.", null, $pdoe);
         }
 
         return true;
     }
 
+    /**
+     * Déclenche l'événement donnant aux applications clientes l'opportunité de modifier l'entité
+     * utilisateur avant qu'elle ne soit persistée.
+     *
+     * @param mixed $entity
+     * @param People|ShibUser $userData
+     */
+    private function triggerUserAuthenticatedEvent($entity, $userData)
+    {
+        $event = new UserAuthenticatedEvent(UserAuthenticatedEvent::PRE_PERSIST);
+        $event->setTarget($this);
+        $event->setDbUser($entity);
+        if ($userData instanceof People) {
+            $event->setLdapUser($userData);
+        } elseif ($userData instanceof ShibUser) {
+            $event->setShibUser($userData);
+        }
+
+        $this->getEventManager()->trigger($event);
+    }
 
     /**
      * Retrieve the event manager
@@ -136,7 +163,7 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
      * Inject an EventManager instance
      *
      * @param  EventManagerInterface $eventManager
-     * @return void
+     * @return self
      */
     public function setEventManager(EventManagerInterface $eventManager)
     {
@@ -145,40 +172,18 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
             get_called_class(),
         ]);
         $this->eventManager = $eventManager;
-        return $this;
-    }
-
-    /**
-     * get ldap people mapper
-     *
-     * @return LdapPeopleMapper
-     */
-    public function getLdapPeopleMapper()
-    {
-        if (null === $this->ldapPeopleMapper) {
-            $this->ldapPeopleMapper = $this->getServiceLocator()->get('ldap_people_mapper');
-        }
-        return $this->ldapPeopleMapper;
-    }
 
-    /**
-     * set ldap people mapper
-     *
-     * @param LdapPeopleMapper $mapper
-     * @return User
-     */
-    public function setLdapPeopleMapper(LdapPeopleMapper $mapper)
-    {
-        $this->ldapPeopleMapper = $mapper;
         return $this;
     }
 
     /**
      * @param ModuleOptions $options
+     * @return self
      */
     public function setOptions(ModuleOptions $options)
     {
         $this->options = $options;
+
         return $this;
     }
 
@@ -190,15 +195,18 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
         if (!$this->options instanceof ModuleOptions) {
             $this->setOptions($this->getServiceLocator()->get('unicaen-auth_module_options'));
         }
+
         return $this->options;
     }
 
     /**
      * @param ZfcUserModuleOptions $options
+     * @return self
      */
     public function setZfcUserOptions(ZfcUserModuleOptions $options)
     {
         $this->zfcUserOptions = $options;
+
         return $this;
     }
 
@@ -210,6 +218,7 @@ class User implements ServiceLocatorAwareInterface, EventManagerAwareInterface
         if (!$this->zfcUserOptions instanceof ZfcUserModuleOptions) {
             $this->setZfcUserOptions($this->getServiceLocator()->get('zfcuser_module_options'));
         }
+
         return $this->zfcUserOptions;
     }
 }
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..2038e8f7d86fc3d34ec46f410663bf1e508b83eb
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/ShibConnectViewHelper.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use UnicaenAuth\Service\Traits\ShibServiceAwareTrait;
+use Zend\View\Helper\AbstractHelper;
+use Zend\View\Renderer\PhpRenderer;
+
+/**
+ * Aide de vue dessinant le bouton de connexion via Shibboleth,
+ * si l'authentification Shibboleth est activée.
+ *
+ * @method PhpRenderer getView()
+ * @author Unicaen
+ */
+class ShibConnectViewHelper extends AbstractHelper
+{
+    use ShibServiceAwareTrait;
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        try {
+            return $this->render();
+        } catch (\Exception $e) {
+            return '<p>' . $e->getMessage() . '</p><p>' . $e->getTraceAsString() . '</p>';
+        }
+    }
+
+    /**
+     * @return string
+     */
+    private function render()
+    {
+        if (! $this->shibService->isShibbolethEnable()) {
+            return '';
+        }
+
+        $shibUrl = $this->getView()->url('auth/shibboleth', [], ['query' => $this->getView()->queryParams()], true);
+
+        return <<<EOS
+Se connecter plutôt avec la 
+<a href="$shibUrl" class="btn btn-success btn-lg">Fédération d'identité Renater</a>
+EOS;
+    }
+}
\ No newline at end of file
diff --git a/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php b/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9828c0e12a42a208f8a3b680f8d9459b7a6cdcf
--- /dev/null
+++ b/src/UnicaenAuth/View/Helper/ShibConnectViewHelperFactory.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace UnicaenAuth\View\Helper;
+
+use UnicaenAuth\Service\ShibService;
+use Zend\View\HelperPluginManager;
+
+class ShibConnectViewHelperFactory
+{
+    /**
+     * @param HelperPluginManager $hpm
+     * @return ShibConnectViewHelper
+     */
+    public function __invoke(HelperPluginManager $hpm)
+    {
+        /** @var ShibService $shibService */
+        $shibService = $hpm->getServiceLocator()->get(ShibService::class);
+
+        $helper = new ShibConnectViewHelper();
+        $helper->setShibService($shibService);
+
+        return $helper;
+    }
+}
\ No newline at end of file
diff --git a/view/unicaen-auth/auth/shibboleth.phtml b/view/unicaen-auth/auth/shibboleth.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..63addc60275602b3b9b0d546d1d8adce1b6543ee
--- /dev/null
+++ b/view/unicaen-auth/auth/shibboleth.phtml
@@ -0,0 +1,23 @@
+<h1 class="page-header">Authentification Shibboleth</h1>
+
+<p>
+    Si vous arrivez sur cette page, c'est sans doute que vous cherchez à utiliser l'authentification Shibboleth, mais
+    qu'elle est mal configurée !
+</p>
+
+<p>
+    Vous devez activer l'authentification Shibboleth dans la config :
+</p>
+<pre>
+    'unicaen-auth' => [
+        ...
+        'shibboleth' => [
+            'enable' => true
+        ],
+    ];
+</pre>
+
+<p>
+    Et voici ce que vous devez ajouter dans la configuration Apache de votre site :
+</p>
+<pre><?php echo htmlspecialchars(\UnicaenAuth\Service\ShibService::apacheConfigSnippet()) ?></pre>
\ No newline at end of file
diff --git a/view/zfc-user/user/login.phtml b/view/zfc-user/user/login.phtml
index e52f5d7d9a2616b90276777ac6096f0f02160525..99a0546c7f235a2aebfc0a601e23d512d1d5d9c8 100644
--- a/view/zfc-user/user/login.phtml
+++ b/view/zfc-user/user/login.phtml
@@ -11,48 +11,61 @@ $form->setAttributes([
 ?>
 
 <style>
-    #div-connexion { max-width: 350px; margin: auto; }
+    .div-connexion { max-width: 350px; margin: auto auto 30px; }
+    .div-connexion h2 { margin: 0; }
+    .div-connexion .btn-primary { margin: 10px 0; }
+    .div-connexion .btn-success { margin: 10px 0; display: inline-block; }
 </style>
 
-<div id="div-connexion">
+<div class="div-connexion panel panel-primary">
 
-    <h1 class="page-header"><?php echo $this->translate("Connexion"); ?></h1>
+    <div class="panel-heading">
+        <h2><?php echo $this->translate("Connexion"); ?></h2>
+    </div>
 
-    <?php echo $this->form()->openTag($form) ?>
+    <div class="panel-body">
+        <?php echo $this->form()->openTag($form) ?>
+        <?php if (($errors = $this->formErrors($this->loginForm))): ?>
+            <p><?php echo $errors ?></p>
+        <?php endif ?>
+        <p>
+            <?php
+            $identity = $form->get($name     = 'identity')->setAttributes(['id' => $name, 'class' => 'form-control']);
+            echo $this->formLabel($identity);
+            echo $this->formInput($identity);
+            ?>
+        </p>
+        <p>
+            <?php
+            $identity = $form->get($name     = 'credential')->setAttributes(['id' => $name, 'class' => 'form-control']);
+            echo $this->formLabel($identity);
+            echo $this->formInput($identity);
+            ?>
+        </p>
+        <?php if ($this->redirect): ?>
+            <input type="hidden" name="redirect" value="<?php echo $this->redirect ?>" />
+        <?php endif ?>
+        <p>
+            <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary')) ?>
+        </p>
+        <?php echo $this->form()->closeTag() ?>
 
-    <?php if (($errors = $this->formErrors($this->loginForm))): ?>
-        <p><?php echo $errors ?></p>
-    <?php endif ?>
+        <hr>
 
-    <p>
-        <?php
-        $identity = $form->get($name     = 'identity')->setAttributes(['id' => $name, 'class' => 'form-control']);
-        echo $this->formLabel($identity);
-        echo $this->formInput($identity);
-        ?>
-    </p>
-    <p>
-        <?php
-        $identity = $form->get($name     = 'credential')->setAttributes(['id' => $name, 'class' => 'form-control']);
-        echo $this->formLabel($identity);
-        echo $this->formInput($identity);
-        ?>
-    </p>
-    <?php if ($this->redirect): ?>
-        <input type="hidden" name="redirect" value="<?php echo $this->redirect ?>" />
-    <?php endif ?>
+        <!-- Connexion Shibboleth (si activée) -->
+        <?php echo $this->shibConnect() ?>
+    </div>
+</div>
 
-    <p>
-        <?php echo $this->formButton($form->get('submit')->setAttribute('class', 'btn btn-primary btn-block')) ?>
-    </p>
 
-    <?php echo $this->form()->closeTag() ?>
+<!-- Création d'un compte local (si autorisée) -->
+<?php if ($this->enableRegistration) : ?>
+<div id="div-connexion" class="panel panel-primary">
+    <?php echo $this->translate("Not registered?"); ?> <a href="<?php echo $this->url('zfcuser/register') . ($this->redirect ? '?redirect=' . $this->redirect : '') ?>"><?php echo $this->translate("Sign up!"); ?></a>
+</div>
+<?php endif; ?>
 
-    <?php if ($this->enableRegistration) : ?>
-        <?php echo $this->translate("Not registered?"); ?> <a href="<?php echo $this->url('zfcuser/register') . ($this->redirect ? '?redirect=' . $this->redirect : '') ?>"><?php echo $this->translate("Sign up!"); ?></a>
-    <?php endif; ?>
 
-</div>
 <script type="text/javascript">
     // focus sur le 1er champ vide
     $("input").filter(function() {