Vous êtes désormais en mesure de vous connecter à votre application web grâce aux 2 utilisateurs codés en dur dans la classe SpringSecurityConfig. C’est très bien, mais découvrons comment aller plus loin !
En effet, les projets que vous mènerez vont généralement demander d’utiliser des utilisateurs enregistrés en base de données. Alors découvrons comment faire !
Mettez en place votre couche d’accès aux données
Comme première étape, il est nécessaire d’avoir une base de données et une couche d’accès aux données dans notre application. Pour simplifier ce cours, nous allons utiliser une base de données H2.
Commencez par ajouter les 2 dépendances suivantes dans le fichier pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Puis configurons l’accès à la base de données en ajoutant les lignes suivantes dans le fichier applications.properties que l’on trouve dans le répertoire src/main/resources :
spring.datasource.url=jdbc:h2:mem:userdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.defer-datasource-initialization=true
spring.sql.init.mode=always
Dans le répertoire src/main/resources, ajoutons un fichier data.sql qui permet d’initialiser la base de données :
DROP TABLE IF EXISTS dbuser;
CREATE TABLE dbuser (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(250) NOT NULL,
password VARCHAR(250) NOT NULL,
role VARCHAR(250) NOT NULL
);
INSERT INTO dbuser (username, password, role) VALUES ('dbuser', '$2y$10$.qkbukzzX21D.bqbI.B2R.tvWP90o/Y16QRWVLodw51BHft7ZWbc.', 'USER'),
('dbadmin', '$2y$10$kp1V7UYDEWn17WSK16UcmOnFd1mPFVF6UkLrOOCGtf24HOYt8p1iC', 'ADMIN');
Le mot de passe de l’utilisateur dbuser est user.
Le mot de passe de l’utilisateur dbadmin est admin.
Créons maintenant une entité nommée DBUser dans une classe du même nom dans un nouveau package com.openclassrooms.model. Cette entité aura 4 attributs : id, username, password et role.
package com.openclassrooms.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class DBUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
private String role;
// Getters and setters …
}
La prochaine étape est de créer une interface que je vais nommer DBUserRepository et qui étend JpaRepository afin de pouvoir interagir avec la base de données. Cette interface sera dans un nouveau package com.openclassrooms.repository :
package com.openclassrooms.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.openclassrooms.model.DBUser;
public interface DBUserRepository extends JpaRepository<DBUser, Integer> {
public DBUser findByUsername(String username);
}
À noter que le prototype findByUsername permet de définir une requête dérivée. Si jamais ce code vous est obscure, je vous encourage de nouveau à aller suivre ce cours : “Utilisez Spring Data pour interagir avec vos bases de données”.
Voilà, notre couche d’accès aux données est prête ! Voyons maintenant comment configurer Spring Security.
Configurez Spring Security
Pour cette étape, je vous propose de me suivre dans la démonstration suivante :
Intéressant, n’est-ce pas ? Reprenons maintenant le code :
Tout d’abord, nous avons créé une classe CustomUserDetailsService dans le package com.openclassrooms.configuration :
package com.openclassrooms.configuration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.openclassrooms.model.DBUser;
import com.openclassrooms.repository.DBUserRepository;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private DBUserRepository dbUserRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DBUser user = dbUserRepository.findByUsername(username);
return new User(user.getUsername(), user.getPassword(), getGrantedAuthorities(user.getRole()));
}
private List<GrantedAuthority> getGrantedAuthorities(String role) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
return authorities;
}
}
La classe est annotée @Service pour être détectée par Spring et elle implémente l’interface UserDetailsService. Ainsi, nous sommes contraints d’implémenter la méthodeloadUserByUsername(String username)
. Cette méthode est capitale car elle sera automatiquement appelé par Spring Security lors de l’authentification de l’utilisateur. Elle renvoie un objet UserDetails, autrement dit la représentation d’un utilisateur.
Pour construire cet objet j’ai :
Récupéré l’utilisateur en base de données avec la méthode
findByUserName()
.Construit les droits de l’utilisateur avec la méthode
getGrantedAuthorities()
.Construit un objet User (du package org.springframework.security.core.userdetails). Cette classe implémente l’interface
UserDetails
et est donc un retour valide pour notre méthode.
La dernière étape a été de modifier la configuration de la sécurité. Dans la classe SpringSecurityConfig, j’ai ajouté la méthode :
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
return authenticationManagerBuilder.build();
}
Cette méthode permet d’indiquer à Spring Security d’utiliser la classe CustomUserDetailsService pour authentifier des utilisateurs. D’ailleurs, toujours dans la classe SpringSecurityConfig, n’oubliez pas d’injecter une instance de CustomUserDetailService en attribut de classe :
@Autowired
private CustomUserDetailsService customUserDetailsService;
Il ne vous reste plus qu’à tester en vous connectant avec l’utilisateur dbuser ou dbadmin. Notez d’ailleurs que les rôles leur sont bien appliqués !
En résumé
La création d’une classe implémentant l’interface UserDetailsService permet d’authentifier des utilisateurs sur la base des informations contenues dans une base de données.
La configuration de Spring Security doit prendre en compte cette classe via un AuthenticationManager.
Dans la prochaine partie, nous allons créer un formulaire de connexion en utilisant OAuth. OAuth vous permet de vous connecter via Facebook, Google et autres. Retrouvons-nous juste après le quiz, pour comprendre tous les avantages de OAuth.