Contactez-nous

Gestion des mots de passe (encodage avec `PasswordEncoder` - bcrypt)

Apprenez pourquoi et comment encoder les mots de passe de manière sécurisée dans Spring Boot en utilisant l'interface PasswordEncoder et l'algorithme bcrypt.

Le danger des mots de passe en clair : Une nécessité absolue d'encodage

Stocker les mots de passe des utilisateurs en clair (texte brut) dans une base de données est l'une des pires erreurs de sécurité qu'une application puisse commettre. En cas de violation de la base de données (piratage, accès non autorisé, fuite accidentelle), tous les mots de passe seraient immédiatement exposés. Les attaquants pourraient alors non seulement accéder aux comptes de votre application, mais aussi potentiellement réutiliser ces identifiants sur d'autres sites, car de nombreux utilisateurs réutilisent malheureusement leurs mots de passe.

Pour protéger les utilisateurs et l'application, il est impératif de ne jamais stocker les mots de passe sous leur forme originale. A la place, nous utilisons des techniques de hachage cryptographique (souvent appelé encodage dans ce contexte, bien que le terme "hachage" soit plus précis) pour transformer le mot de passe en une chaîne de caractères apparemment aléatoire et de longueur fixe (le hash), qui ne peut pas être facilement inversée pour retrouver le mot de passe original.

Les principes du hachage sécurisé : One-way, Salting, Adaptive

Un bon algorithme de hachage de mot de passe doit posséder plusieurs caractéristiques clés :

  • Fonction à sens unique (One-way) : Il doit être extrêmement difficile (computationnellement infaisable) de retrouver le mot de passe original à partir de son hash.
  • Salage (Salting) : Pour chaque mot de passe, une chaîne aléatoire unique (le "sel" ou "salt") doit être générée et combinée avec le mot de passe avant le hachage. Ce sel est ensuite stocké avec le hash résultant. Le salage garantit que deux utilisateurs ayant le même mot de passe auront des hashes différents, rendant les attaques par tables arc-en-ciel (rainbow tables) inefficaces.
  • Adaptatif / Lent (Adaptive / Slow) : L'algorithme doit être intentionnellement lent et nécessiter une quantité significative de ressources (CPU, mémoire). De plus, il doit permettre d'augmenter ce "coût" (work factor) au fil du temps, à mesure que la puissance de calcul augmente. Cela rend les attaques par force brute (essayer toutes les combinaisons possibles) beaucoup plus coûteuses et longues pour les attaquants.

Des algorithmes comme MD5 ou SHA-1 sont aujourd'hui considérés comme totalement obsolètes et dangereux pour le hachage de mots de passe car ils sont trop rapides et vulnérables.

Spring Security et l'interface `PasswordEncoder`

Spring Security fournit une abstraction centrale pour gérer l'encodage et la vérification des mots de passe : l'interface PasswordEncoder (dans org.springframework.security.crypto.password). Cette interface définit deux méthodes principales :

  • String encode(CharSequence rawPassword) : Prend un mot de passe en clair (rawPassword) en entrée et retourne sa représentation hachée et salée sous forme de chaîne de caractères.
  • boolean matches(CharSequence rawPassword, String encodedPassword) : Prend un mot de passe en clair (soumis par l'utilisateur lors de la connexion) et un mot de passe déjà encodé (récupéré depuis la base de données) et retourne true si le mot de passe soumis correspond au mot de passe encodé, false sinon. Cette méthode gère l'extraction du sel depuis le hash stocké et la comparaison sécurisée.

En utilisant cette interface, votre code applicatif est découplé de l'implémentation spécifique de l'algorithme de hachage utilisé.

BCryptPasswordEncoder : L'implémentation recommandée

Spring Security recommande et utilise par défaut (dans de nombreuses configurations) l'implémentation BCryptPasswordEncoder. BCrypt est un algorithme de hachage de mot de passe largement reconnu et éprouvé, spécialement conçu pour cet usage. Il intègre nativement :

  • Un salage robuste : Il génère automatiquement un sel aléatoire pour chaque mot de passe encodé et stocke ce sel directement dans la chaîne de caractères du hash résultant (le format du hash bcrypt inclut la version de l'algorithme, le coût et le sel).
  • Un facteur de coût adaptatif (work factor / strength) : Vous pouvez configurer la "force" (le coût computationnel) de l'algorithme (un entier généralement entre 10 et 15). Une valeur plus élevée rend le hachage plus lent et donc plus résistant à la force brute, mais consomme aussi plus de ressources serveur. La valeur par défaut est souvent 10.

Le format typique d'un hash BCrypt ressemble à : $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy ($2a$: version bcrypt, 10: coût, N9qo8uLOickgx2ZMRZoMye: sel, le reste: hash).

Configuration : Définir le bean `PasswordEncoder`

Pour utiliser un encodeur de mot de passe dans votre application Spring Boot avec Spring Security, vous devez déclarer un bean de type PasswordEncoder dans votre configuration Spring. C'est ce bean qui sera ensuite automatiquement utilisé par les composants de Spring Security (comme DaoAuthenticationProvider) pour vérifier les mots de passe lors de l'authentification, et que vous injecterez dans vos services pour encoder les mots de passe lors de l'enregistrement ou de la mise à jour.

La manière la plus simple est de définir un bean BCryptPasswordEncoder dans une classe de configuration (par exemple, votre classe SecurityConfig) :

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {

    // ... autre configuration de sécurité (SecurityFilterChain, etc.) ...

    @Bean
    public PasswordEncoder passwordEncoder() {
        // Retourne une instance de BCryptPasswordEncoder.
        // Vous pouvez optionnellement spécifier la force (ex: new BCryptPasswordEncoder(12))
        // mais la valeur par défaut (10) est généralement un bon point de départ.
        return new BCryptPasswordEncoder();
    }

}

Il est crucial de n'avoir qu'un seul bean de type PasswordEncoder principal dans votre contexte d'application pour éviter les ambiguïtés.

Utilisation : Encoder lors de l'enregistrement

Lorsque vous créez un nouvel utilisateur ou qu'un utilisateur change son mot de passe, vous devez utiliser le bean PasswordEncoder injecté pour encoder le mot de passe fourni en clair avant de le sauvegarder dans la base de données.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public User createUser(UserData userData) {
        User newUser = new User();
        newUser.setUsername(userData.getUsername());
        
        // Encoder le mot de passe avant de le sauvegarder !
        String encodedPassword = passwordEncoder.encode(userData.getRawPassword());
        newUser.setPassword(encodedPassword); 
        
        newUser.setActive(true);
        // ... définir les rôles, etc. ...

        return userRepository.save(newUser);
    }

    public void changePassword(User user, String newRawPassword) {
        String encodedPassword = passwordEncoder.encode(newRawPassword);
        user.setPassword(encodedPassword);
        userRepository.save(user);
    }
}

Utilisation : Vérification lors de l'authentification (Automatique)

La bonne nouvelle est que vous n'avez généralement pas besoin d'appeler explicitement la méthode matches() pour vérifier le mot de passe lors du processus d'authentification standard (par exemple, via un formulaire de login ou HTTP Basic).

Lorsque Spring Security traite une tentative d'authentification, le composant responsable (souvent DaoAuthenticationProvider, qui utilise votre UserDetailsService pour charger les détails de l'utilisateur par nom d'utilisateur) récupère automatiquement le bean PasswordEncoder que vous avez défini. Il utilise ensuite ce bean pour :

  1. Hasher le mot de passe soumis par l'utilisateur (en utilisant le même algorithme et la même force, mais avec un sel potentiellement différent à chaque fois).
  2. Comparer ce hash résultant avec le hash (qui contient le sel original) stocké dans la base de données pour cet utilisateur, en utilisant la méthode passwordEncoder.matches(submittedPassword, storedEncodedPassword).

Si matches() retourne true, l'authentification réussit (si le compte est valide par ailleurs). Tout ce processus est géré pour vous par Spring Security, à condition que vous ayez correctement configuré le bean PasswordEncoder.

Note sur `DelegatingPasswordEncoder`

Spring Security fournit également DelegatingPasswordEncoder. C'est souvent l'encodeur configuré par défaut par Spring Boot si vous ne définissez pas votre propre bean. Il permet de supporter plusieurs algorithmes d'encodage simultanément en préfixant le hash stocké avec un identifiant d'algorithme (ex: `{bcrypt}$2a$10$...`, `{noop}password` pour aucun encodage - utile pour les tests uniquement !, `{sha256}...`). Lors de la vérification (matches()), il délègue à l'encodeur correspondant au préfixe trouvé. Lors de l'encodage (encode()), il utilise l'encodeur désigné comme "défaut" (généralement bcrypt). C'est utile pour migrer progressivement d'anciens algorithmes vers bcrypt sans invalider les mots de passe existants.

Vous pouvez le configurer via PasswordEncoderFactories.createDelegatingPasswordEncoder().

Conclusion : Une étape non négociable pour la sécurité

La gestion sécurisée des mots de passe via un hachage robuste et salé est une pratique de sécurité fondamentale et non négociable. Spring Security, à travers son interface PasswordEncoder et son implémentation recommandée BCryptPasswordEncoder, simplifie grandement l'implémentation de cette pratique essentielle. En définissant un bean PasswordEncoder et en l'utilisant systématiquement pour encoder les mots de passe avant leur stockage, tout en laissant Spring Security gérer la vérification lors de l'authentification, vous renforcez considérablement la sécurité de votre application Spring Boot et protégez les données sensibles de vos utilisateurs.