Authentification Kerberos avec SpringSecurity
Introduction
J’ai récemment implémenté une authentification (automatique) avec Kerberos. Ce protocole permet d’authentifier automatiquement un utilisateur depuis sa session utilisateur (session windows par exemple). Via Kerberos, on authentifie donc un utilisateur sans lui demander son mot de passe ce qui est un plus (il n’a pas à se re-logguer : on utilise le contexte d’authentification de Windows) en se basant sur le fait que s’il a ouvert une session utilisateur en son nom … alors tout est OK!
En implémentant ça, j’ai eu pas mal de soucis dont j’ai eu du mal à trouver des solutions dans les ressources disponibles aujourd’hui, d’où l’idée de ce petit article.
Attention : je ne suis pas expert Kerberos, donc certains termes/certaines assertions ne sont peut être pas correcte, j’exprime ici la manière dont j’en ai compris le fonctionnement.
Comment marche Kerberos :
Kerberos expliqué à un enfant de 5 ans (en anglais) : http://www.roguelynn.com/words/explain-like-im-5-kerberos/
Résumé en quelques lignes : Kerberos est un protocol d’authentification par ticket: le ticket est généré par un système tiers (le KDC – Key Distribution Center), injecté dans un header HTTP, lut par votre navigateur. Ce ticket peut ensuite être utilisé par votre application qui va le faire valider par le KDC et peut ensuite s’assurer de la validitée de l’authentification en récupérant le contexte d’authentification de votre session utilisateur (votre nom d’utilisateur entre autre) depuis ce ticket.
Implémentation avec Spring Security kerberos
Site du projet : http://projects.spring.io/spring-security-kerberos/
Configuration de la sécurité
Pour commencer, vous avez besoin de deux composants permettant d’initier une négociation Kerberos, et de configurer ceux-ci dans la configuration Spring Security. Un exemple ici en Spring Config :
@Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; /** * créé un bean SpnegoAuthenticationProcessingFilter (filtre de négociation Kerberos) * @return */ @Bean public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter() { SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter(); filter.setAuthenticationManager(this.authenticationManager); return filter; } /** * créé un bean SpnegoEntryPoint (point d'entré de la négociation Kerberos) * @return */ @Bean public SpnegoEntryPoint spnegoEntryPoint() { return new SpnegoEntryPoint("/login"); } /** * @inheritDoc */ @Override protected void configure(HttpSecurity http) throws Exception { //configuration Kerberos http.exceptionHandling().authenticationEntryPoint(spnegoEntryPoint()); http.addFilterBefore(spnegoAuthenticationProcessingFilter(), BasicAuthenticationFilter.class); //... autre conf ... } }
Ceci permettra, sur la page de login (ici /login) de déclencher une négociation Kerberos. Le navigateur va alors lire le ticket Kerberos et le mettre à disposition pour le service d’authentification Kerberos.
Configuration Kerberos
Pour le ticket Kerberos soit validé, il faut fournir un ensemble de configuration, c’est là que c’est le plus compliqué et le plus spécifique à votre configuration Kerberos. Il vous faudra l’aide de vos administrateur du KDC (Key Distribution Center : vos administrateur Microsoft Active Directory pour une session windows) pour avoir un keytab et un krb5.ini. Ceux-ci permettent de faire le lien entre votre application et le KDC.
Pour vous aider à vous retrouver dans tous ça :
- Le service principal doit être de la forme : HTTP/hostname@realm : exemple HTTP/myhost.exemple.com@PC.EXEMPLE.COM. Attention, le hostname doit être celui utilisé dans le navigateur pour accéder à votre application.
- Le keytab doit être généré pour le service principal que vous utilisez, il permet à l’host de votre application de s’authentifier auprès du KDC
- Votre krb5.ini doit contenir le realm utilisé et le lien entre celui-ci et le KDC (un exemple plus bas), il permet à votre application de savoir où se situe le KDC
Une fois que vous avez tous ceci, il vous faut configurer Kerberos, voici un petit exemple via Spring Config :
@Configuration public class KerberosConfiguration { @Resource private UserDetailsService userDetailsService;//votre UserDetailService permettant de charger votre utilisateur via son userName @Value("${kerberos.service_principal}") private String servicePrincipal; @Value("${kerberos.keytab_location}") private String keytabLocation; @Value("${kerberos.krb5_ini}") private String krb5iniLocation; /** * créé un bean KerberosServiceAuthenticationProvider pour l'authentification Kerberos * @return */ @Bean public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() { KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider(); provider.setTicketValidator(sunJaasKerberosTicketValidator()); provider.setUserDetailsService(this.userDetailsService); return provider; } /** * créé un bean de type SunJaasKerberosTicketValidator : c'est lui qui validera le ticket Kerberos (le header) * @return */ @Bean public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() { System.setProperty("java.security.krb5.conf", krb5iniLocation); SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator(); ticketValidator.setServicePrincipal(servicePrincipal); ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation)); return ticketValidator; } }
Et voici un exemple de krb5.ini :
[libdefaults] dns_lookup_realm = true ticket_lifetime = 24h renew_lifetime = 7d forwardable = true rdns = false default_realm = PC.EXEMPLE.COM default_ccache_name = KEYRING:persistent:%{uid} permitted_enctypes = aes128-cts aes256-cts arcfour-hmac-md5 [realms] PC.EXEMPLE.COM = { kdc = MYKDC.PC.EXEMPLE.COM admin_server = MYKDC.PC.EXEMPLE.COM default_domain = PC.EXEMPLE.COM } [domain_realm] .pc.exemple.com = PC.EXEMPLE.COM pc.exemple.com = PC.EXEMPLE.COM [logging] kdc = FILE:/var/log/krb5/krb5kdc.log admin_server = FILE:/var/log/krb5/kadmind.log default = SYSLOG:NOTICE:DAEMON
Votre user service
Grâce à kerberos, le navigateur va directement passer le contexte d’authentification de votre session Windows au KDC qui le validera. Ensuite, vous aurez une authentification Spring Security valide … mais sans autres informations sur votre utilisateur que son login Windows.
Pour aller plus loin, il vous faudra implémenter votre propre UserDetailService qui ira, depuis le login de l’utilisateur, charger l’utilisateur depuis l’AD
/un LDAP/ une base. A vous de voir en fonction de l’endroit où vous stockez les informations de l’utilisateur, et surtout, ses droits!
Le problème des load balancer
Avec Kerberos, l’intégration d’un load balancer se trouve compliqué. En effet, la négociation Kerberos s’effectue depuis le navigateur et depuis le host de votre application. Si l’URL de votre application est http://loadbalancer.exemple.com et que vos hosts sont host1.exemple.com et host2.exemple.com alors vous aurez un ticket Kerberos sur un principal HTTP/loadbalancer.exemple.com@PC.EXEMPLE.COM et pas un ticket sur le principal HTTP/host1.exemple.com@PC.EXEMPLE.COM.
Pour résoudre ce problème, il faut sur chaque host derrière le loadbalancer utiliser le même principal et le même keytab qui sont celui du loadbalancer et pas celui de l’host. C’est la seule manière que j’ai trouvé de contourner le problème (même si certain articles expliquent qu’il faut générer un keytab avec deux principal : un pour l’host et un pour le loadbalancer je n’ai pas réussit à faire fonctionner cette configuration)à.
Voici deux articles sur le sujet :
- https://ssimo.org/blog/id_019.html
- https://devcentral.f5.com/questions/apm-how-to-create-a-keytab-file-with-multiple-spns