Authentification Kerberos avec SpringSecurity

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 :

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.