Intégrer Alfresco à un système d’authentification unique (SSO) a toujours été un sujet complexe à aborder. C’est un sujet qui revient à chaque nouveau déploiement d’Alfresco chez nos clients et il n’y a pas de réponse toute prête à apporter :
- il existe beaucoup de systèmes de SSO, chacun ayant ses particularités
- côté Alfresco, on retrouve 4 mécanismes de SSO gérés nativement
Le but est d’arriver à faire correspondre les attentes du client en terme d’authentification (prendre en compte l’existant et la cible à atteindre), les possibilités offertes par Alfresco et les contraintes techniques. Et tout ceci en conservant un bon niveau de sécurité.
Dans ce billet, nous allons aborder le mécanisme de SSO « External authentication
» proposé par Alfresco. C’est le plus simple à comprendre et il est facile à mettre en place. C’est également l’occasion de donner une suite à un billet de blog écrit il y a plus de 10 ans sur le même sujet. À l’époque le sujet concernait la mise en place d’un SSO entre Alfresco Explorer et Share via l’utilisation de CAS. Nous allons voir que l’intégration de CAS est maintenant grandement facilitée.
Cette introduction étant déjà bien trop longue, commençons tout de suite avec une rapide présentation du « External authentication
».
# Principe de fonctionnement
Ce billet n’a pas pour but de remplacer la documentation officielle, je vous invite donc à la lire en complément de ce billet. Toutefois, ne vous laissez pas influencer par la documentation, celle-ci fait souvent référence à CAS mais le système « External authentication
» n’est pas lié à un mécanisme de SSO particulier. Et c’est d’ailleurs là tout son intérêt et sa simplicité :
- l’authentification (unique ou pas d’ailleurs) est déportée en amont d’Alfresco (d’où le «
External
», ce n’est plus Alfresco qui gère l’authentification, mais un élément externe) - l’information sur l’utilisateur authentifié est portée par un en-tête HTTP
- Alfresco et Share utilisent le contenu de cet en-tête pour authentifier automatiquement l’utilisateur (une confiance totale est faite au contenu de cet en-tête)
Le mécanisme « External authentication
» ne présuppose en aucun cas de la méthode utilisée pour authentifier l’utilisateur. Le seul pré-requis concerne la présence de l’en-tête HTTP contenant le login de l’utilisateur. Le nom de cet en-tête est configurable côté Alfresco, vous pouvez utilisez n’importe quelle valeur (dans la suite de ce billet, nous utiliserons X-Alfresco-Remote-User
). La valeur de l’en-tête doit contenir uniquement le login de l’utilisateur, tel que connu par Alfresco. Les comptes étant créés généralement via une synchronisation avec un annuaire LDAP, il faut bien s’assurer que le contenu de l’en-tête correspond au login remonté par la synchronisation LDAP.
Et c’est tout ce qu’il faut savoir sur cette méthode d’authentification.
Point important à noter : il s’agit d’un mécanisme d’authentification qui fonctionne uniquement en HTTP. N’espérez pas le faire fonctionner avec d’autres protocoles comme CIFS ou FTP.
# Proxy authentifiant ?
Vous avez certainement remarqué la présence du proxy authentifiant sur le schéma juste au-dessus. Il s’agit de la brique réalisant l’authentification des utilisateurs. Le mécanisme utilisé pour authentifier les utilisateurs n’a aucune incidence sur la partie Alfresco ou Share, du moment que l’en-tête est bien présent avec la bonne valeur. Et vous êtes libre d’utiliser ce que vous voulez à ce niveau.
Concrètement, tous les modules d’authentification gérés par Apache HTTPD sont possibles : CAS bien sûr, mais également SAML, Kerberos ou OpenID Connect. On peut même envisager de l’authentification à deux facteurs (plus connu sous le nom 2FA) et de l’authentification via certificat.
Si vous préférez Nginx, ne partez pas, il y a tout ce qu’il faut (ou presque) : CAS, OpenID Connect, Kerberos, certificat , etc.
Il est également possible d’utiliser des solutions plus complètes du type LemonLDAP::NG. Configuré en tant que reverse-proxy authentifiant devant les applications Alfresco et Share, vous aurez alors accès à un nombre impressionnant de backends pour réaliser l’authentification.
Bref, le choix ne manque pas et vous trouverez certainement votre bonheur avec tous ces modules d’authentification.
# Configuration d’Alfresco
Côté serveur Alfresco, il y a 3 éléments à configurer :
- Tomcat
- Alfresco lui-même
- Share
Ces trois points de configuration seront toujours les mêmes quelque soit le mécanisme utilisé au niveau du proxy authentifiant. À noter que les exemples de configuration concernent Alfresco 6.2. Il faudra les adapter pour les autres versions d’Alfresco.
# Tomcat
Il faut indiquer à Tomcat de propager l’authentification provenant du proxy authentifiant vers les applications. Ceci est valable uniquement si vous utilisez le connecteur AJP entre le proxy authentifiant et Tomcat. Si vous passez par le connecteur HTTP ou HTTPS, cette étape n’est pas nécessaire.
- dans le fichier
conf/server.xml
- ajouter l’attribut
tomcatAuthentication=false
au connecteur AJP :
1 2 3 4 |
<!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" URIEncoding="UTF-8" tomcatAuthentication="false" /> |
# Alfresco
Il faut ajouter un nouveau type d’authentification (de type external
) dans votre chaîne d’authentification.
- dans le fichier
shared/classes/alfresco-global.properties
- modifier le paramètre
authentication.chain
pour lui ajouter une authentification de typeexternal
1 |
authentication.chain=cas:external,alfrescoNtlm1:alfrescoNtlm |
- ajouter également les paramètres suivants pour configurer l’authentification
external
1 2 3 4 |
external.authentication.enabled=true external.authentication.defaultAdministratorUserNames=admin external.authentication.proxyUserName= external.authentication.proxyHeader=X-Alfresco-Remote-User |
Vous l’aurez compris, le paramètre external.authentication.proxyHeader
doit correspondre au nom de l’en-tête envoyé par le proxy authentifiant.
# Share
La configuration de Share passe par la création de fichiers XML à placer dans le dossier shared/classes/alfresco/web-extension/
.
- créer le fichier
external-auth-context.xml
avec le contenu suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans> <bean id="external.auth.config" class="org.springframework.extensions.config.ConfigBootstrap" init-method="register"> <property name="configService" ref="web.config" /> <property name="configs"> <list> <value>classpath:alfresco/web-extension/external-auth-config.xml</value> </list> </property> </bean> </beans> |
- créer le fichier
external-auth-config.xml
avec le contenu suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<alfresco-config> <config evaluator="string-compare" condition="Remote"> <remote> <connector> <id>alfrescoCookie</id> <name>Alfresco Connector</name> <description>Connects to an Alfresco instance using cookie-based authentication</description> <class>org.alfresco.web.site.servlet.SlingshotAlfrescoConnector</class> </connector> <connector> <id>alfrescoHeader</id> <name>Alfresco Connector</name> <description>Connects to an Alfresco instance using header and cookie-based authentication</description> <class>org.alfresco.web.site.servlet.SlingshotAlfrescoConnector</class> <userHeader>X-Alfresco-Remote-User</userHeader> </connector> <endpoint> <id>alfresco</id> <name>Alfresco - user access</name> <description>Access to Alfresco Repository WebScripts that require user authentication</description> <connector-id>alfrescoHeader</connector-id> <endpoint-url>http://localhost:8080/alfresco/wcs</endpoint-url> <identity>user</identity> <external-auth>true</external-auth> </endpoint> <endpoint> <id>alfresco-feed</id> <parent-id>alfresco</parent-id> <name>Alfresco Feed</name> <description>Alfresco Feed - supports basic HTTP authentication via the EndPointProxyServlet</description> <connector-id>alfrescoHeader</connector-id> <endpoint-url>http://localhost:8080/alfresco/wcs</endpoint-url> <identity>user</identity> <external-auth>true</external-auth> </endpoint> <endpoint> <id>alfresco-api</id> <parent-id>alfresco</parent-id> <name>Alfresco Public API - user access</name> <description>Access to Alfresco Repository Public API that require user authentication. This makes use of the authentication that is provided by parent 'alfresco' endpoint.</description> <connector-id>alfrescoHeader</connector-id> <endpoint-url>http://localhost:8080/alfresco/api</endpoint-url> <identity>user</identity> <external-auth>true</external-auth> </endpoint> </remote> </config> </alfresco-config> |
N’oubliez pas de modifier la balise userHeader
avec la nom de l’en-tête envoyé par le proxy authentifiant.
# Comment tester ?
Rien de plus simple, une simple commande curl
en vous assurant de passer l’en-tête attendu par Alfresco :
- accès à la console d’administration d’Alfresco
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[root@acs62 ~]# curl -sv -o /dev/null -H "X-Alfresco-Remote-User: admin" http://localhost:8080/alfresco/wcs/enterprise/admin/admin-systemsummary > GET /alfresco/wcs/enterprise/admin/admin-systemsummary HTTP/1.1 > User-Agent: curl/7.29.0 > Host: localhost:8080 > Accept: */* > X-Alfresco-Remote-User: admin > < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Set-Cookie: JSESSIONID=63BBE29F27CEFD2A277518F6163462E8; Path=/alfresco; HttpOnly < Set-Cookie: alf-csrftoken=rNRl0bLNABnBqsnfvFHaepeX4sxO%2bbkbhf8CIPtQ158%3d; Expires=Tue, 21-Apr-2020 12:58:01 GMT; Path=/alfresco < Cache-Control: no-cache < Expires: Thu, 01 Jan 1970 00:00:00 GMT < Pragma: no-cache < Content-Type: text/html;charset=UTF-8 < Transfer-Encoding: chunked < Date: Tue, 14 Apr 2020 12:58:04 GMT |
Alfresco répond avec un code 200 OK
, tout va bien, on a été authentifié et l’accès à la console d’administration est autorisé. Un code 401 Unauthorized
aurait indiqué un échec d’authentification et donc un souci dans la configuration d’Alfresco.
- accès au Tableau de bord de Share
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[root@acs62 ~]# curl -sv -o /dev/null -H "X-Alfresco-Remote-User: admin" http://localhost:8080/share/page/ > GET /share/page/ HTTP/1.1 > User-Agent: curl/7.29.0 > Host: localhost:8080 > Accept: */* > X-Alfresco-Remote-User: admin > < HTTP/1.1 302 Found < Server: Apache-Coyote/1.1 < Set-Cookie: JSESSIONID=40A1908D6D013644798E1978F35B1BEA; Path=/share; HttpOnly < Set-Cookie: Alfresco-CSRFToken=W5ElnBa6rM%2bXiP513eW7yr9g23Xlpo1trexNdoDwnhA%3d; Expires=Tue, 21-Apr-2020 13:01:58 GMT; Path=/share < X-Frame-Options: SAMEORIGIN < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Cache-Control: no-cache < Location: /share/page/user/admin/dashboard < Content-Type: text/html;charset=utf-8 < Content-Language: en-US < Content-Length: 0 < Date: Tue, 14 Apr 2020 13:01:57 GMT |
L’authentification a également fonctionné avec Share : nous obtenons une redirection 302 Found
vers le tableau de bord de l’utilisateur admin
(voir l’en-tête HTTP Location
dans la réponse). En cas d’échec d’authentification, la redirection aurait été faite vers le formulaire d’authentification Location: /share/page?pt=login
).
# Et niveau sécurité ?
Bonne remarque ! Comme souvent avec les composants qui touchent à la sécurité et à l’authentification, il ne faut pas juste s’arrêter à « Ça fonctionne, je ne touche plus à rien »
. Il est important de bien comprendre ce que nous venons de faire.
Comme vous l’avez certainement compris, Alfresco fait totalement confiance à la valeur de l’en-tête pour authentifier l’utilisateur. Il faut donc faire en sorte que l’en-tête arrivant au niveau d’Alfresco soit digne de confiance. Il est très facile pour un attaquant de forger un en-tête malveillant et ainsi se faire passer pour n’importe quel autre utilisateur. Il n’est pas nécessaire de connaitre toutes les options de la commande curl
pour y arriver, il existe des extensions pour nos navigateurs préférés dont le but est de créer et/ou modifier les en-têtes HTTP (par exemple Modify Header Value pour Firefox).
Les bonnes pratiques à respecter :
- autoriser l’accès à Alfresco/Share uniquement depuis le proxy authentifiant
- le proxy authentifiant doit supprimer les en-têtes
X-Alfresco-Remote-User
des requêtes entrantes
Pour le premier point, il est possible de mettre en place des règles de filtrage du trafic entrant avec l’utilisation d’un firewall directement sur le serveur Alfresco, ou via le Remote Address Filter de Tomcat. Uniquement le proxy authentifiant doit être autorisé à accéder à Tomcat. Cependant, ceci ne vous protégera pas des attaques locales (si l’attaquant à accès au serveur, il shunte toutes ces protections).
Pour le second point, cela dépend du proxy authentifiant utilisé. Par exemple avec Apache HTTPD, il est possible de supprimer l’en-tête des requêtes entrantes avec la directive RequestHeader
suivante :
1 |
RequestHeader unset "X-Alfresco-Remote-User" early |
Pour aller plus loin, il est également possible de mettre en place un mécanisme d’authentification mutuelle en TLS entre le proxy authentifiant et Tomcat. Le proxy authentifiant se connecte en TLS et présente un certificat client à Tomcat, celui-ci est configuré pour valider le certificat présenté. Alfresco va ensuite vérifier que ce certificat correspond à l’utilisateur indiqué par le paramètre external.authentication.proxyUserName
(ce même paramètre que nous avions laissé vide dans l’exemple de configuration d’Alfresco ci-dessus). Si tout est ok, la requête continue son chemin et Alfresco utilise toujours l’en-tête HTTP pour authentifier l’utilisateur.
L’utilisation d’un certificat client permet une authentification mutuelle entre le proxy authentifiant et Tomcat. Il est ainsi possible d’établir une relation de confiance entre ces deux applications et de garantir l’authenticité de l’en-tête HTTP. Sans le certificat client, pas d’accès possible à Tomcat.
Si mon explication est suffisamment claire, et si vous connaissez un peu Alfresco, vous aurez remarqué qu’il s’agit du même principe déjà utilisé entre Alfresco et Solr (même si la mise en œuvre est différente, le principe reste identique).
# Mon Webdav ne fonctionne plus !
Depuis le début de l’article, je pars du principe que nous souhaitons authentifier un utilisateur dans un contexte purement web (en HTTP) et souhaitant accéder à l’interface Share. Cependant Alfresco propose bien plus de services que l’interface Share : Webdav, différentes API (REST, CMIS), l’édition en ligne avec MS Office (AOS), etc. Ces différents services reposent généralement sur leur propre mécanisme d’authentification, et il est rare que le mécanisme d’authentification mis en place au niveau du proxy authentifiant soit utilisable directement.
Comme exemple parlant, prenons le cas de l’accès au partage Webdav d’Alfresco avec une authentification CAS au niveau du proxy authentifiant. À ma connaissance, aucun client Webdav ne sait gérer l’authentification CAS. Sans action particulière, il ne sera plus possible d’utiliser l’accès Webdav via le proxy authentifiant.
Autre exemple : Share offre la possibilité de créer un lien public vers un document. Toutes les personnes connaissant ce lien peuvent avoir accès au document sans authentification ni compte dans Alfresco … sauf que le proxy authentifiant va jouer son rôle et va imposer une authentification, même pour accéder à une ressource considérée comme publique au niveau de Share.
Pour retrouver le comportement standard d’Alfresco et de Share il sera nécessaire de configurer le proxy authentifiant pour ajouter des exceptions. Le but est de laisser passer certaines URL sans nécessité d’avoir un utilisateur authentifié. Cette configuration étant réalisée au niveau du proxy authentifiant, on déborde du périmètre de billet et sa mise en place est laissé comme exercice au lecteur (ce billet étant déjà bien trop long).
28 avril 2022 at 8 h 56 min
Bonjour,
Merci beaucoup pour ce tutoriel intéressant.
J’ai suivi toutes les étapes et ça marche bien avec ma configuration locale, par contre en volant faire la même chose avec un vrai serveur LemonLdap je tombe sur des erreur causé par le fait de recevoir l’identifiant chiffré en base64.
Authorization: base xxxxx
Avez-vous une idée de comment indiquer à Alfresco qu’il va recevoir l’identifiant en base64 et pas en clair.
Merci d’avance de me répondre, ça fait plusieurs semaines que je suis bloqué sur ce sujet.
12 juillet 2022 at 15 h 03 min
Bonjour Yazid,
Avec un Alfresco standard, il s’attend à recevoir le login dans un header HTTP, et il faut que ce login ne soit pas transformé. Ce qui exclue donc tout ce qui est transformation en base64, ou chiffrement/signature ou tout autre méthode qui altérerait ce login.
Trois pistes envisageables (il en existe sûrement d’autres…) :
– modifier la configuration de LemonLdap pour envoyer le login sans modification
– ajouter un intermédiaire qui décode le base64 et ré-injecte le login décodé dans un autre header HTTP
– faire un petit développement spécifique dans Alfresco pour décoder le login au format base64
Laurent
7 décembre 2023 at 14 h 56 min
Bonjour,
j’utilise alfresco 7, et j’essaye de mettre en place un websso OIDC Apache en frontal d’alfresco. Tout fonctionne nickel, sauf que l’appel alfresco/api/-default-/public/authentication/versions/1/tickets me renvoie un 405.
Je n’arrive pas à récupérer le ticket. Alors que je suis bien authentifié selon alfresco.
avez vous une idée ?
merci par avance,
8 décembre 2023 at 14 h 24 min
Bonjour Christophe,
La réponse 405 Method Not Allowed indique que l’url alfresco/api/-default-/public/authentication/versions/1/tickets n’est pas appelée correctement.
La documentation (https://docs.alfresco.com/content-services/latest/develop/rest-api-guide/install/#authenticating-to-get-a-ticket) décrit comment cette URL doit être exploitée pour obtenir un ticket. Elle n’a par ailleurs pas de lien avec l’authentification externe.
Damien