Accueil > Non classé > CAS et Grails, sans sarCASmes !

CAS et Grails, sans sarCASmes !

Grails.orgCAS

Propos liminaire

Cet article traite de la cassification d’une application Grails. Ce néologisme pas très catholique, plus souvent employé dans la langue Shakespearienne, est synonyme d’intégration d’une application avec le système d’authentification centralisée Open Source le plus stylé au monde, j’ai nommé : CAS.

CAS, mais pourquoi faire ?

Nota : si vous connaissez déjà bien CAS et que seule l’intégration avec Grails vous intéresse, vous pouvez passer à la partie suivante, je ne vous en voudrai pas ;-) .

CAS est un système d’authentification qui permet d’authentifier des utilisateurs de manière sécurisée. La quasi-totalité des applications d’un système d’information nécessitent une authentification des utilisateurs.

Sans système d’authentification centralisé, chaque application se retrouve à développer ses propres pages de login, et gère l’authentification à sa sauce. Pour éviter d’avoir à créer/gérer / stocker les informations d’authentification dans chacune des applications, la solution consiste bien souvent à créer un référentiel commun, par exemple un LDAP, un Active Directory, ou encore une base de données commune.

Cette approche est alléchante, mais présente plusieurs problèmes :

  • les développements ne sont pas factorisés, il faut redévelopper les pages de login / la mécanique d’accès au référentiel commun pour chaque webapp,
  • la mécanique d’authentification n’est pas unifiée, les designs sont différents, et les utilisateurs peuvent être rapidement perdus,
  • la sécurité de l’ensemble du SI repose sur la sécurité de chacune des webapp. Chaque webapp a potentiellement accès aux informations d’authentification de tous les utilisateurs. Il suffit d’une faille à un endroit pour que tout le système soit compromis.
  • Les utilisateurs doivent s’authentifier sur chacune des applications web.
  • De même, ils doivent se déconnecter manuellement sur chacune des applications.

CAS répond parfaitement à ces problématiques :

  • Lorsqu’ils doivent s’authentifier sur une application, les utilisateurs sont automatiquement redirigés vers CAS. Il n’y a donc un seul point d’entrée pour l’authentification.
  • Les utilisateurs ne sont pas perdus, ils reconnaissent la page de login CAS, et savent aussi immédiatement identifier s’ils sont sur la “vraie” page d’authentification étant donné que l’url est unique (moins de risques de phishing).
  • Seul CAS a accès aux informations d’authentification. De part son caractère Open Source, sa maturité, et sa large adoption, CAS est nettement moins exposé à de potentielles failles de sécurité que vos applications développées en interne. Oui, vos applications contiennent des failles de sécurité. De part la nature du développement logiciel et sa complexité, il n’y a aucune raison pour que vous soyez mystérieusement épargné.
  • CAS bénéficie d’un fort soutient de la communauté, et s’intègre avec de très nombreuses solutions. Il y a de fortes chances pour que les développements pour intégrer CAS à vos webapps soient réduits au minimum. Les nombreuses possibilités d’intégration de CAS peuvent être résumées par cette simple citation littéraire :

    Tu pars du Nord Ouest pour arriver au Sud Est, sans toucher la Corse ! Et tu CAS, et tu CAS !

  • CAS permet d’activer le SSO (Single Sign-On, aka Authentification Unique) : l’utilisateur s’authentifie une fois, puis peut avoir accès à l’ensemble des applications du SI sans jamais rentrer de nouveaux ses informations d’authentification.
  • Mieux, CAS gère le Single Sign-Out. Il est ainsi possible pour un utilisateur de se déconnecter de l’ensemble des applications en une seule action.
  • Enfin, CAS permet aussi la délégation d’authentification, par exemple pour permettre à une application d’utiliser votre identité pour contacter les web services d’une autre application. Je n’en parlerai pas dans cet article, mais peut-être ultérieurement, c’est un sujet passionnant.

Ya quoi sous la carCASse ?

carcasse

Sans trop entrer dans les détails du protocole CAS, en voici les principes généraux :

  • Lorsque l’utilisateur s’authentifie auprès de CAS, il obtient un TGT (Ticket Granting Ticket). Ce ticket n’est jamais présenté aux applications, il permet à CAS d’identifier l’utilisateur.
  • Lorsque l’utilisateur souhaite utiliser un service (une application), il demande à CAS un ST (Service Ticket) pour le service donné, en fournissant son TGT ainsi que le nom du service.
  • L’utilisateur présente ensuite son ST à l’application. Celle-ci contacte CAS en lui fournissant le ST et son nom de service. CAS retourne l’identité de l’utilisateur.
  • Dès lors, l’application peut placer l’identité de l’utilisateur dans la session qu’elle maintient avec celui-ci.
  • Afin d’être compatible avec une utilisation web, ce protocole se traduit en pratique par un stockage du TGT dans un cookie accessible uniquement à CAS, et l’utilisation de redirections HTTP avec des URL du type “/cas/login?service=nomService” pour demander un ST et “/myapp/login?ST=leTicket” pour authentifier l’utilisateur au sein d’une application.

Notez qu’on trouve des ressemblances avec le protocole Kerberos, à ceci prêt que les tickets ne contiennent pas directement l’identité de l’utilisateur.

Et le Single Sign-out ?

  • En pratique, le nom du service est l’URL vers laquelle l’utilisateur va être redirigé une fois que CAS a généré un ST. CAS maintient en mémoire la liste des URL pour lesquelles l’utilisateur a demandé un ST.
  • Lorsque l’utilisateur demande à se déconnecter, CAS envoie une requête POST d’un format particulier à chacune de ces URL. Charge ensuite à chaque application de supprimer la session correspondante.
  • En JEE, cela se traduit par la mise en place d’un servlet filter et d’un listener spécifiques sur chaque application.

Installer CAS sans être CASse-cou

Avant toute chose, il nous faut un CAS. On pourrait se créer un WAR aux petits oignons, en suivant cette procédure. Poil dans la main oblige, on va plutôt le télécharger directement : cas-server-webapp-3.4.2.war. Cette version de CAS a une petite particularité : pour se connecter, il suffit d’entrer un mot de passe identique au login, quelque soit le login.

Déployez le WAR dans un Tomcat, et testez : http://localhost:8181/cas-server-webapp-3.4.2/login. Pourquoi le port 8181 ? Parce qu’on va réserver le 8080 pour le jetty lancé par Grails ;-) .

Login sur CAS

Login sur CAS

Notez que le Single Sign-On ne sera disponible qu’à la condition que vous configuriez le SSL.

CAS toi, pauvre Con..tact !

Je vous propose maintenant de CASsifier une application Grails existante : ZenContact, présentée au cours d’un article précédent.

Vous n’êtes pas sans savoir que Grails, c’est du Spring MVC déguisé (vous aussi, créez votre framework web au dessus de Spring MVC). On peut donc utiliser Spring Security, qui s’intègre bien avec CAS.

Et ça tombe bien, il existe un plugin Grails qui va nous simplifier le travail : le Spring Security Plugin. Le blog Xebia en a d’ailleurs parlé dans un article plutôt complet.

Installez le plugin Acegi :


1
grails install-plugin acegi

Créez les entités pour la sécurité (utilisateurs, rôles, et mapping des url sécurisées) :


1
2
3
4
grails create-auth-domains
   com.excilys.blog.zencontact.User
   com.excilys.blog.zencontact.Role
   com.excilys.blog.zencontact.RequestMap

Je vous propose de créer les utilisateurs, les rôles et les urls protégées dans la base de données mémoire au démarrage de l’application. Il suffit de modifier le fichier $PROJECT_HOME/grails-app/conf/BootStrap.groovy et d’y ajouter le snippet 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
import com.excilys.blog.zencontact.User
import com.excilys.blog.zencontact.Role
import com.excilys.blog.zencontact.RequestMap

// [...]

def roleUser = new Role(authority: 'ROLE_USER', description: 'Utilisateur')
roleUser.save()

def roleContact = new Role(authority: 'ROLE_CONTACT', description: 'Accès aux contacts')
roleContact.save()

def user1 = new User(username: 'piwai', passwd:'NOT_USED', userRealName: 'Piwaï',
    enabled: true, email: 'ano@nymous.com')
user1.addToAuthorities(roleUser)
user1.addToAuthorities(roleContact)
user1.save()

def user2 = new User(username: 'pyricau', passwd:'NOT_USED', userRealName: 'Pierre-Yves Ricau',
    enabled: true, email: 'ano@nymous.com')

user2.addToAuthorities(roleUser)
user2.save()

new RequestMap(url: '/contact/**', configAttribute: 'ROLE_CONTACT').save()

Passons aux choses sérieuses, la configuration du plugin pour utiliser CAS. Modifiez le fichier $PROJECT_HOME/grails-app/conf/SecurityConfig.groovy, et ajoutez-y la conf suivante :


1
2
3
4
5
6
useCAS = true
cas.localhostSecure = false
cas.fullServiceURL = 'http://localhost:8181/cas-server-webapp-3.4.2'
cas.fullLoginURL = "$cas.fullServiceURL/login"
cas.failureURL = '/login/denied'
afterLogoutUrl = '/logout/afterLogout'

Et oui, c’est aussi simple que cela ! La propriété afterLogoutUrl indique la page à afficher une fois que l’utilisateur s’est déconnecté. Il faut donc créer l’action correspondante dans le LogoutController ($PROJECT_HOME/grails-app/controllers/LogoutController.groovy) :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class LogoutController {

  def authenticateService;

  /**
   * Index action. Redirects to the Spring security logout uri.
   */

  def index = {
    redirect(uri: '/j_spring_security_logout')
  }

  def afterLogout = {
    def backFromCasLogoutUrl = grailsApplication.config.grails.serverURL

    def conf = authenticateService.securityConfig.security

    def casLogoutUrl = "$conf.cas.fullServiceURL/logout?url=$backFromCasLogoutUrl"

    [casLogoutUrl:casLogoutUrl]
  }
}

Ainsi que la vue associée ($PROJECT_HOME/grails-app/view/logout/afterLogout.gsp) :


1
2
3
4
5
6
<meta name='layout' content='main' />
<title>Déconnexion</title>
<div class='body'>
Vous êtes désormais déconnecté de Zencontact.<br />
Pour vous déconnecter du système d'authentification central, <a href="${casLogoutUrl}" >cliquez ici</a>.
</div>
Logout de ZenContact

Logout de ZenContact

Il ne reste plus qu’à exécuter l’application (grails run-app), puis accéder à une page sécurisée. Et vous pouvez même vous déconnecter ! Pour en savoir plus, consultez la configuration du plugin Spring Security.

Bonux : Single Sign-Out sans se décarCASser

Et pour terminer en beauté, on aimerait être automatiquement déconnecté de ZenContact quand on se déconnecte de CAS. C’est très pratique pour se déconnecter instantanément de toutes les applications du système d’information.

En pratique, il s’agit simplement de modifier le web.xml pour y ajouter les listeners et servlet filters et qui vont bien. Cependant, vous aurez beau chercher, vous ne trouverez pas de web.xml dans les sources de votre application. Diantre, il est généré !

La technique la plus simple est encore de modifier le template qui sert à générer le web.xml, en exécutant grails install-templates et en modifiant le fichier $PROJECT_HOME/src/templates/war/web.xml :


1
2
3
4
5
6
7
8
9
10
11
12
13
<filter>
  <filter-name>signOutFilter</filter-name>
  <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<!-- ... -->
<filter-mapping>
  <filter-name>signOutFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ... -->
<listener>
  <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>

Notez enfin que le Single Sign-Out dépend du Single Sign-On, qui nécessite la mise en place du SSL.

Conclusion

Comme d’habitude, les sources sont disponibles sur le SVN du Google Code :

https://excilys.googlecode.com/svn/projects/ymca/tags/grails_cas/

J’espère vous avoir convaincu de la facilité à intégrer une application Grails à CAS, voir même vous avoir incité à découvrir CAS plus en profondeur, si ce n’était pas déjà le cas.

Et j’espère que vous ne me tiendrez pas trop rigueur de mes jeux de mots capilotractés ;-).

Edit 11/08/2010 : un nouveau plugin Grails Spring Security remplace celui présenté dans cet article, pour en savoir plus, lisez donc l’article sur le blog de SpringSource.

Share
  1. worm
    06/05/2010 à 21:05 | #1

    Merci pour ce bel article ! Il tombe à pic, je me cassais les dents sur la configuration ACEGI + CAS depuis hier.
    Vous pouvez peut-être m’éclairer sur un point : dans votre exemple les utilisateurs sont créés dans BootStrap.groovy ; comment pourrait t’on les créer à la volée une fois l’authentification CAS réussie. Est-ce que je suis assez clair ? :)

  2. Pierre-Yves RICAU
    07/05/2010 à 00:24 | #2

    Oui, ça me paraît plutôt clair :-) . Et cela peu avoir du sens, quand on ne souhaite pas devoir importer une base d’utilisateurs ou se brancher sur un service extérieur autre que CAS.

    Le gros avantage de Spring Security, c’est qu’il est particulièrement configurable.

    L’authentification est fournie par un CasAuthenticationProvider. Celui-ci fait appel à un ticketValidator pour valider les tickets CAS et obtenir l’identité de la personne, puis utilise un userDetailsService pour obtenir les UserDetails (par exemple les rôles) de l’utilisateur qui s’authentifie et les placer dans l’Authentication (qui elle même est ensuite placée en session).

    La solution me paraît donc d’utiliser un UserDetailsService custom, qui ne jettera jamais de “UsernameNotFoundException” mais créera l’utilisateur si celui-ci n’est pas dans la base.

    Le lien suivant devrait vous éclairer plus en détail sur la customisation du UserDetailsService avec le plugin Acegi : http://www.grails.org/AcegiSecurity+Plugin+-+Custom+UserDetailsService

    La racine de la documentation du plugin est ici : http://grails.org/plugin/acegi

    Enfin, notez que si certains concepts ne vous paraissent pas clairs dans la doc, le mieux est encore de se référer à la doc officielle du plugin Spring Security, bien plus complète.

  3. worm
    07/05/2010 à 10:41 | #3

    Merci pour les infos ; j’étais arrivé à cette solution de Custom UserDetailsService, mais cela m’a paru compliqué à mettre en oeuvre (je débute complètement avec grails…et les frameworks java en général) et j’espérais qu’il y ait une solution plus simple.
    Mais puisque c’est la bonne voie, je vais voir ça de plus près !

  1. 07/05/2010 à 10:24 | #1