Skip to content

Gestion d'utilisateurs

Pour gérer les utilisateurs il faudra:

  • Une classe Utilisateur
  • Une classe Role
  • Un service et un repository pour gérer les utilisateurs (ajouter, modifier, etc)
  • Un service qui implémente UserDetailsService, généralement on le nomme CustomUserDetailsService; Cela permet l'authentification.

Classes Utilisateur et Role

Attention

Évitez de nommer votre classe pour les utilisateurs User. Il existe déjà une classe Java qui porte ce nom et on devra s'en servir. Cela risque de vous créer des problèmes d'assignation de type dans le code.

  • Votre classe utilisateur peut contenir tous les renseignements que vous voulez sur votre utilisateur. On pourra s'en servir comme profil.
  • Assurez-vous d'avoir un attribut qui pourra servir à identifier et retrouver l'utilisateur autre que son ID. Par exemple le courriel ou un pseudo. On s'en servira pour la connexion.
  • La classe Role devrait contenir un identifiant et le nom du rôle.
  • Lorsque vous nommez les rôles dans la base de données il faut suivre la nomenclature suivante
    • Utilisez des lettres majuscules pour nommer vos rôles
    • Utilisez toujours le préfixe "ROLE_" devant le nom de votre rôle
    • Exemple: ROLE_ADMIN pour les administrateurs
  • Considérez les besoins de votre application. Un utilisateur peut-il avoir plusieurs rôles ou un seul?
    • Il faudra planifier les relations en tenant compte de cela.

Service et Repository

Ces deux éléments permettront de faire les manipulations nécessaires avec la base de données.

  • Ajouter un uitilisateur
  • Modifier ses informations de profil
  • Modifier son mot de passe
  • Modifier le/les rôle/s qui lui sont associés.
  • Etc.

Ce ne sont pas ces éléments qui permettront l'authentification de l'utilisateur

CustomUserDetailsService

  • Cette classe nous permettra de récupérer les informations de l'utilisateur, récupérer ses rôles et de générer l'objet de type User qui sera authentifé auprès de l'applicaion.
  • Elle doit implémenter l'interface UserDetailsService
  • Elle doit surcharger la méthode loadByUsername(String username) de l'interface
  • Le paramètre username de cette méthode, est le nom d'utilisateur qui sera entré dans le formulaire de connexion.
  • C'est grâce à cette méthode qu'on pourra authentifier un utilisateur

Voici un exemple avec une classe utilisateur qui aurait la possibilité d'avoir plusieurs rôles.

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;
    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Utilisateur utilisateur = userRepository.findFirstByUsername(username);

        if (utilisateur != null) {
            // Cet utilisateur est celui qui sera conservé dans l'authentification
            User authUser = new User(
                    username,
                    utilisateur.getPassword(),
                    utilisateur.getRoles().stream().map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList())
            );
            return authUser;
        } else {
            throw new UsernameNotFoundException("Nom d'utilisateur ou mot de passe invalide");
        }
    }
}

Ajouts à la classe de configuration de sécurité

Il faudra ajouter deux éléments à la classe ConfigurationSecurite

  • Une configuration pour un PasswordEncoder

@Bean
public static PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
Ceci configure un encodeur de mot de passe utilisant l'algorithme BCrypt. On pourra l'utiliser dans toute l'application.

  • Un gestionnaire d'authentification
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
    return configuration.getAuthenticationManager();
}

Ceci crée le gestionnaire d'authentification en utilisant le service CustomUserDetailsService créé au préalable.

Pour la création d'utilisateurs

Lors de la création d'utilisateurs il sera important de s'assurer qu'on enconde le mot de passe avant de l'envoyer vers la base de données.

Pour nous aider, il est de bonne pratique de créer une classe de type DTO (data transfer object). Cette classe représentera le formulaire d'inscription.

À partir de cet objet, on pourra alors créer l'utilisateur en lui donnant le ou les rôles de base ainsi que son mot de passe encodé.

Dans le service d'utilisateurs on devra avoir une référence au passwordEncoder de l'application afin de pouvoir l'utiliser pour encoder le mot de passe initial.

public class UtilisateurService {
    private final UtilisateurRepository utilisateurRepository;
    private final RoleService roleService;
    private final PasswordEncoder passwordEncoder;

    public UtilisateurService(UtilisateurRepository utilisateurRepository, RoleService roleService, PasswordEncoder passwordEncoder) {
        this.utilisateurRepository = utilisateurRepository;
        this.roleService = roleService;
        this.passwordEncoder = passwordEncoder;
    }

    public Utilisateur addUser(UtilisateurDTO user) {
        // Création d'un utilisateur avec un `builder`
        Utilisateur utilisateur = Utilisateur.builder()
                .email(user.getEmail())
                .nom(user.getNom())
                .prenom(user.getPrenom())
                .username(user.getUsername())
                .role(roleService.findByNom("ROLE_USER"))
                .password(passwordEncoder.encode(user.getPassword()))
                .build();
        return utilisateurRepository.save(utilisateur);
    }
}

Authentifier un utilisateur

Spring s'occupe de gérer la connexion et la déconnexion des utilisateurs pour nous. Il n'est en aucun cas nécessaire de créer nous même la session d'un utilisateur lorsqu'il se connecte.

Spring utilise les propriétés loginProcessingUrl et logoutUrl configurées dans le SecurityFilterChain pour faire la gestion de sessions.

Pour authentifier un utilisateur:

  • Créer un formulaire de connexion
  • Le formulaire doit avoir un champ username et un champ password
  • La méthode du formulaire doit être post et l'action doit pointer vers loginProcessingUrl
  • Le formulaire doit posséder un jeton CSRF. On peut l'implémenter de deux façons
    • Utiliser la balise <form:form>. Cette balise ajoute automatiquement un jeton CSRF au formulaire.
    • Ajouter manuellement le jeton CSRF

Si on choisit d'utiliser la balise <form:form> il faudra passer un objet à la propriété modelAttribute. On peut donc créer une classe représentant le formulaire.

Pour déconnecter l'utilisateur

  • Il suffit d'envoyer une requête post à l'url définie dans logoutUrl.
  • La meilleure pratique pour cela est d'utiliser un formulaire avec un jeton CSRF pour envoyer la requête.
  • Le seul <input> du formualire sera celui du jeton. On ne peut donc pas utiliser la balise <form:form>. Il faudra le configurer manuellement et ajouter le jeton nous même.