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!
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