Contexte
Printemps 2025, l'Université de Rouen Normandie a procédé à une montée de version de son service d'authentification CAS en 7.2.2
Cette page wiki vient en complément de nos pages Retour de l'URN sur mise en place de CAS 6.0.4, Retour de l'URN sur mise en place de CAS 6.4.1 et Retour de l'URN sur mise en place de CAS 6.6.9, Retour de l'URN sur mise en place de CAS 7.0.6
Si la migration s'est plutôt bien déroulée, nous avons du faire quelques ajustements... cette page tente de les retranscrire.
Environnement technique
On est resté sur l'environnement technique bookworm, tomcat10, zulu21 tel que décrit dans Retour de l'URN sur mise en place de CAS 7.0.6
esup-otp-cas
Une version d'esup-otp-cas est disponible et compatible avec CAS 7.2.
Il faut aussi penser à ajuster cas.properties pour permettre à esup-otp-api de fonctionner dans la page CAS, on a ainsi :
cas.http-web-request.header.content-security-policy=script-src 'self' 'unsafe-inline' 'unsafe-eval' https://esup-otp-api.univ-rouen.fr https://webstats.univ-rouen.fr; object-src 'none'; worker-src 'self' blob: 'unsafe-inline'
SSO Sessions Actuator Endpoint
Les configurations pour accéder aux actuator changent :
management.endpoints.web.exposure.include=ssoSessions management.endpoint.ssoSessions.access=UNRESTRICTED cas.monitor.endpoints.endpoint.ssoSessions.access[0]=IP_ADDRESS cas.monitor.endpoints.endpoint.ssoSessions.required-ip-addresses[0]=192\\.168\\.0\\.1
Longueurs des clefs de chiffrement
Les clefs de chiffrement demandent à être ajustés : cf les logs de CAS, leurs tailles sont à augmenter.
Consommation CPU et RAM
Depuis maintenant un certain nombre d'années, on constate que chaque montée de version majeure de Apereo CAS amène une consommation plus importante des ressources système.
La 7.2 n'échappe malheureusement pas à cette règle.
Problème rencontré avec la persistance du cookie TGC (remember-me)
Nous avons constaté un changement de comportement concernant le cookie TGC (Ticket Granting Cookie), responsable du maintien de la session CAS entre le navigateur et le serveur.
En version 7.0, l’interface HTML proposait une case à cocher (<input type="checkbox">) pour l’option "Se souvenir de moi" (remember-me). Cela avait pour effet que, si l’utilisateur ne cochait pas cette case, aucun paramètre rememberme n’était transmis à CAS, et donc la session CAS était associée à un cookie de session navigateur (supprimé à la fermeture du navigateur).
À partir de la version 7.1, l’UI CAS a été modifiée : la case à cocher a été remplacée par un bouton de type "switch" (toggle), qui agit en réalité sur un champ caché (<input type="hidden" name="rememberme">). Résultat : le paramètre rememberme est toujours transmis côté serveur, même lorsqu’il est désactivé.
Or, dans le code de CAS (de la 7.1 à 7.2.2 comprise), la décision de rendre le TGC persistant repose uniquement sur la présence du paramètre rememberme, sans considération de sa valeur. Cela a pour effet de rendre le TGC persistant (cookie à durée de vie fixée), même lorsque l’utilisateur n’a pas demandé à rester connecté. Ce comportement peut poser problème dans des environnements où le navigateur est partagé (ex. : tablettes pour examens), car la session persiste même après fermeture/reprise du navigateur.
Nous avons corrigé ce comportement localement en surchargeant la méthode concernée pour que la persistance du TGC soit conditionnée à la valeur du paramètre rememberme, et non à sa seule présence.
Pull Request
Un pull request a été proposée à Apereo pour corriger cela de manière générique ici : https://github.com/apereo/cas/pull/6872
Modification dans le cas-overlay
Le déploiement par cas-overlay permet de surcharger des classes java.
Exceptionnellement et dans l'attente (et l'espoir) que le problème soit pris en compte et corrigé dans la version officielle d'Apereo CAS, nous avons donc patché localement la classe java.
- copie de classe java modifiée dans l'overlay dans src/main/java/org/apereo/cas/web/support/gen/CookieRetrievingCookieGenerator.java
- ajout des dépendances dans l'overlay pour la compilation :
/* pour override de CookieRetrievingCookieGenerator */ implementation "org.apereo.cas:cas-server-core-cookie-api" implementation "org.apereo.cas:cas-server-core-authentication-api" implementation "org.apereo.cas:cas-server-core-util-api" implementation "org.apereo.cas:cas-server-core-web-api"
- puis redéploiement et redémarrage pour prise en compte.
Attributs LDAP persistants sur l'ensemble de la session CAS
Depuis un certain nombre de temps (cf entre autre https://groups.google.com/a/apereo.org/g/cas-user/c/0HjUqWsM0oE/m/9mq9VPxaAwAJ ) , tout le long de la session CAS, les attributs calculés lors de l'authentification sont conservés et renvoyés dans les tickets CAS.
Ces attributs sont en effet stockés en base (dans mongidb chez nous) dans l'objet qui correspond au TGT et qui suit l'utilisateur tout le long de la sessions.
Le processus d'authentification standard fonctionne ainsi :
Lors de la création du TGT, les attributs LDAP sont récupérés et persistés avec le TGT (notamment dans MongoDB, Hazelcast, etc.).
Tous les Service Tickets (ST) générés pendant la session utiliseront par défaut ces attributs "figés" du TGT, sauf configuration contraire.
- Rappel : la durée de vie du TGT peut-être très longue, notamment si vous proposez un rememberMe de quelques jours, semaines, voire mois !!
Cela signifie que :
Les attributs ne sont pas rafraîchis à chaque identification sur un service ;
Cela peut poser problème dans les scénarios où des attributs sont utilisés dynamiquemen par des applicatifs cassifiés utilisant directement les attributs renvoyés par CAS (comme
memberOfpour un calcul de rôles par exemple).
La configuration suivante devrait (ou a du dans une version antérieure) faire en sorte que les attributs soient rafraichis à chaque fois que CAS en a besoin, mais ce n'est pas (plus ?) le cas, notamment en 7.2.2
cas.authn.attribute-repository.core.expiration-time = 0
Ce problème n'est donc pas nouveau mais nous avons profité de la mise à jour de notre CAS pour le traiter.
Récupération des attributs lors de la récupération/calcul d'un service ticket
Cf https://groups.google.com/a/apereo.org/g/cas-user/c/0HjUqWsM0oE/m/9mq9VPxaAwAJ une solution est de passer par une configuration JSON sur chaque service, via un attributeReleasePolicy avec un CachingPrincipalAttributesRepository dont l'expiration est à 0 et avec ignoreResolvedAttributes=true (ignoreResolvedAttributes à true signifie "ne pas utiliser les attributs du TGT", mais interroger directement le repository).
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy",
"allowedAttributes" : [ "java.util.ArrayList", [ "uid", "mail", "displayName", "eduPersonPrincipalName", "eduPersonAffiliation", "sn", "givenName", "radiusFilterId", "memberOf" ] ],
"principalAttributesRepository" : {
"@class" : "org.apereo.cas.authentication.principal.cache.CachingPrincipalAttributesRepository",
"timeUnit" : "HOURS",
"expiration" : 0,
"attributeRepositoryIds": ["java.util.HashSet", [ "LdaptivePersonAttributeDao" ]],
"ignoreResolvedAttributes": true,
}
}
Récupération des attributs lors de l'exécution de scripts groovy pour l'interrupt ou/et le mfa
Non documenté dans Apereo CAS, c'est une facilité que nous permet (actuellement en tout cas) spring depuis un script groovy opéré par CAS (le risque étant ici qu'une nouvelle version de CAS n'autorise plus cette possibilité un jour) : on récupère le bean spring qui permet d'interroger le ldap tel que configuré dans CAS, et on calcule des memberOf à jour.
Petite astuce supplémentaire ici, si cette récupération échoue, on fait juste un log et on utilise l'attribut memberOf issu du TGT :
class SampleGroovyEventResolver {
def String run(service, registeredService, authentication, httpRequest, logger, ... args) {
def memberOf = authentication.principal.attributes.memberOf
// memberOf calculé via le TGT éventuellement ancien, on refait une requête ldap ici...
try {
def ctx = org.apereo.cas.util.spring.ApplicationContextProvider.getApplicationContext()
def dao = ctx.getBeansOfType(org.apereo.cas.authentication.principal.attribute.PersonAttributeDao.class)
def attrRepo = dao.get("cachingAttributeRepository")
memberOf = attrRepo.getPerson(authentication.principal.id).getAttributeValues("memberOf")
} catch(Exception ex) {
logger.error("pb retrieving memberof from ldap, use memberof from cas attributes : " + ex)
}
if ('cn=from.grouper.admin,ou=groups,dc=univ-rouen,dc=fr' in memberOf) {
return "mfa-esupotp"
}
...
Via ces configurations, notez qu'il est évident que le ldap sera sollicité plus régulièrement qu'il ne l'était auparavant.