Accueil > Non classé > Authentification (in)digest(e) avec Spring et des mots de passe chiffrés

Authentification (in)digest(e) avec Spring et des mots de passe chiffrés

Dans mon projet actuel, j’ai mis en place un ensemble de web services basés sur Rest. On a décidé de les sécuriser avec le protocole HTTP Digest fonctionnant sur la même base que HTTP Basic. L’idée était d’utiliser un processus qui permette de ne pas faire circuler le mot de passe en clair au moment de la connexion.


Spring-security nous offre une implémentation de ce protocole. Parfait, il n’y a plus qu’à l’utiliser de la manière suivante :

  1. on rajoute les deux beans suivants à notre config DigestAuthenticationFilter et DigestAuthenticationEntryPoint ;
  2. dans la balise <http>, on rajoute un <http-basic> pour indiquer que l’on veut une connexion basée sur HTTP basic ;
  3. toujours dans la balise <http>, on rajoute <custom-filter ref=”digestFilter” after=”BASIC_AUTH_FILTER” /> pour activer l’authentification en Http Digest

Problème

Tout ça c’est bien beau, mais voila… Pour que ce système fonctionne, il faut stocker les mots de passe en clair dans la base de données. Pas génial, hein ? En effet, si on regarde la doc de Spring, il est clairement écrit que l’authentification Digest ne fonctionnera pas avec des mots de passe déjà chiffrés.

Pour comprendre le pourquoi du comment, on regarde comment ça marche.

Côté client

On commence par regarder la spec du protocole. Rien de bien compliqué :

  1. On essaye d’accéder à une page sécurisée
  2. Le serveur répond avec un header de cette forme :
    WWW-Authenticate: Digest realm="Secure Area",
    qop="auth,auth-int",
    nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
    opaque="5ccc069c403ebaf9f0171e9517f40e41"
  3. Et le client ré-essaye d’accéder à la page sécurisé avec un header de cette forme :
    Authorization: Digest username="Robert",
    realm="Secure Area",
    nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
    uri="/user/list.html",
    qop=auth,
    nc=00000001,
    cnonce="0a4f113b",
    response="6629fae49393a05397450978507c4ef1",
    opaque="5ccc069c403ebaf9f0171e9517f40e41"

Les parties intéressantes sont celles qui sont en gras, oublions le reste :). Si on regarde comment est calculé la réponse que doit renvoyer le client, ça donne ça :

A1 = username:realm:password
A2 = method:digestURI
HA1 = MD5( A1 )
HA2 = MD5( A2 )
response = MD5( HA1:nonce:HA2 )

Côté serveur

Note : Pour cette explication, je me base sur la version 3.1.0 de spring-security et je simplifie un peu :)

On regarde les sources de la classe DigestAuthenticationFilter. Le fonctionnement est simple :

  1. Ligne 124 : récupération des données du header, dont le champ response ;
  2. Ligne 144 : récupération de l’utilisateur (selon ce qui est spécifié dans la balise <authentication-manager> de la conf spring) ;
  3. Ligne 154 : calcul du champ response à partir du login / password récupéré en base de données (selon le même principe que pour la partie cliente) ;
  4. Ligne 157 : vérification de la concordance du champ response calculé et reçu.

Voyons voir ce qui se passe ligne 154 :

1
serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());

Cette méthode effectue un simple :

1
2
3
String calculateServerDigest(String password, String httpMethod) {
return DigestAuthUtils.generateDigest(passwordAlreadyEncoded, username, realm, password, httpMethod, uri, qop, nonce, nc, cnonce);
}

On remarque au passage l’attribut passwordAlreadyEncoded qui pourrait être intéressant. Allons voir à quoi il sert :

1
2
3
4
5
if (passwordAlreadyEncoded) {
a1Md5 = password;
} else {
a1Md5 = DigestAuthUtils.encodePasswordInA1Format(username, realm, password);
}

Parfait! Si on passe la valeur de passwordAlreadyEncoded à Vrai, il suffit alors de stocker le password dans la base de données dans le même format que la valeur HA1 vue dans le fonctionnement du protocole côté client, à savoir :

encodedPassword = MD5 (username:realm:password)

Note : Le champ realm est le texte qui sera affiché dans la popup sur les navigateurs.

Conclusion

Finalement, il y a bien un système prévu dans spring-security pour utiliser des mots de passe chiffrés avec un HTTP Digest. Pour cela il suffit de passer à Vrai l’attribut passwordAlreadyEncoded de la classe DigestAuthenticationFilter et de stocker les mots de passe sous la forme suivante :

encodedPassword = MD5 (username:realm:password)

Exemple

Mon contexte spring ressemble donc à ça :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<http entry-point-ref="digestEntryPoint">
...
<http-basic />
<custom-filter ref="digestFilter" after="BASIC_AUTH_FILTER" />
</http>

<beans:bean id="digestFilter" class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<beans:property name="userDetailsService" ref="userService" />
<beans:property name="authenticationEntryPoint" ref="digestEntryPoint" />
<beans:property name="passwordAlreadyEncoded" value="true" />
</beans:bean>
<beans:bean id="digestEntryPoint" class="org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<beans:property name="realmName" value="Http Digest Authentication" />
<beans:property name="key" value="realmsalt" />
</beans:bean>

Et pour l’utilisateur test avec le mot de passe password, on aurait :

MD5(test:Authentication via Digest Authentication:password) = ac52aaa7c55e4c2406d2f32c958d7914
Share
  1. Pas encore de commentaire
  1. Pas encore de trackbacks