Authentification dans Spring Boot : Tutoriel Intermédiaire (25 min)
Pourquoi Authentification dans Spring Boot ?
L'authentification est un élément fondamental de toute application web ou d'API moderne, permettant à l'utilisateur de prouver son identité avant d'accéder aux ressources protégées. Dans le monde professionnel, une application sans authentification peut être facilement piratée, conduisant à des dommages financiers et réputationnels importants.
Un cas d'usage concret :Imaginez que vous développez un blog privé où les utilisateurs peuvent poster des articles. Vous ne voulez pas que tout le monde puisse écrire sur votre blog sans autorisation. L'authentification est donc essentielle pour protéger cette ressource et assurer que seuls les membres autorisés peuvent publier.
Prerequis
Pour suivre ce tutoriel, vous aurez besoin des éléments suivants :
- Connaissance de base de Java et Spring Boot
- Une IDE comme IntelliJ IDEA ou Eclipse
- Maven installé (version 3.5+)
- Un navigateur web pour tester l'application
- Un compte GitHub (facultatif) pour cloner le code
Concepts fondamentaux
1. Authentification vs Authorization
Authentification : Cette étape vérifie qui est l'utilisateur en utilisant des informations d'identification comme un nom d'utilisateur et un mot de passe.
Authorization : Après une authentification réussie, cette étape détermine quelles actions ou ressources sont disponibles pour l'utilisateur authentifié.
// Authentification
public boolean authenticate(String username, String password) {
// Code pour vérifier les informations d'identification
}
// Authorization
public boolean isAuthorized(User user, String action) {
// Code pour déterminer si l'utilisateur a le droit de l'action
}
2. Spring Security
Spring Security est une bibliothèque Java qui fournit des fonctionnalités d'authentification et d'autorisation robustes.
// Ajouter la dépendance dans pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3. UserDetails et UserDetailsService
UserDetails : Cette interface représente un utilisateur authentifié.
UserDetailsService : Cette interface permet de récupérer des détails d'utilisateur.
// Implémentation de UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Code pour charger les détails de l'utilisateur
}
}
4. AuthenticationManager
L'AuthenticationManager est responsable de la gestion des requêtes d'authentification.
// Configuration de AuthenticationManager
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
Mise en pratique : Projet fil rouge
Nous allons créer un mini-projet simple qui permet à l'utilisateur de s'inscrire, se connecter et accéder à une ressource protégée.
Étape 1 : Création du projet Spring Boot
Créer un nouveau projet Spring Boot via Spring Initializr.
- Project : Maven Project
- Language : Java
- Spring Boot : La dernière version stable
- Project Metadata
- Group : fr.example
- Artifact : auth-springboot-demo
- Name : auth-springboot-demo
- Description : Authentification Spring Boot Demo
- Package name : fr.example.authspringbootdemo
- Packaging : Jar
- Java : 11 ou plus récent
- Dependencies :
- Spring Web
- Spring Security
Étape 2 : Configuration de la sécurité
Créer un fichier SecurityConfig.java pour configurer la sécurité.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll() // Ressources publiques
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Étape 3 : Création des controllers
Créer un controller pour les routes publiques et protégées.
@RestController
@RequestMapping("/api")
public class AuthController {
@GetMapping("/public/hello")
public String helloPublic() {
return "Hello, Public User!";
}
@GetMapping("/protected/hello")
@PreAuthorize("hasRole('USER')")
public String helloProtected() {
return "Hello, Protected User!";
}
}
Étape 4 : Création des services
Créer un service pour gérer les utilisateurs.
@Service
public class UserService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Implémentation de chargement d'utilisateur
}
public User save(User user) {
// Implémentation de sauvegarde d'utilisateur
}
}
Étape 5 : Création des entités et repositories
Créer une entité User et un repository pour persister les utilisateurs.
@Entity
public class User extends UserDetailsImpl implements Serializable {
// Attributs, getters et setters
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
Étape 6 : Création de la classe d'entrée
Créer une classe UserDetailsImpl pour implémenter UserDetails.
public class UserDetailsImpl implements UserDetails {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// Implémentation des rôles
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Étape 7 : Création de l'interface d'utilisateur
Créer une interface utilisateur pour afficher le formulaire de connexion.
@Controller
public class LoginController {
@GetMapping("/login")
public String showLoginForm() {
return "login";
}
@PostMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password) {
// Implémentation de la connexion
}
}
Étape 8 : Création des vues
Créer les fichiers HTML pour les vues (ex: login.html).
<!-- login.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form th:action="@{/login}" method="post">
<input type="text" name="username" placeholder="Username" required />
<input type="password" name="password" placeholder="Password" required />
<button type="submit">Login</button>
</form>
</body>
</html>
Étape 9 : Configuration de la base de données
Configurer une base de données (ex: H2) dans application.properties.
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
Étape 10 : Exécution de l'application
Exécuter l'application via votre IDE ou en utilisant la commande Maven.
mvn spring-boot:run
Erreurs fréquentes et debugging
Erreur 1 : BadCredentialsException
Message d'erreur : org.springframework.security.authentication.BadCredentialsException: Bad credentials
Code incorrect :
@Autowired
private AuthenticationManager authenticationManager;
public void authenticate(String username, String password) {
UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, password);
authenticationManager.authenticate(authReq);
}
Code correct :
@Autowired
private AuthenticationManager authenticationManager;
public boolean authenticate(String username, String password) {
UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, password);
try {
authenticationManager.authenticate(authReq);
return true;
} catch (BadCredentialsException e) {
return false;
}
}
Erreur 2 : AccessDeniedException
Message d'erreur : org.springframework.security.access.AccessDeniedException: Access is denied
Code incorrect :
@GetMapping("/protected/hello")
@PreAuthorize("hasRole('ADMIN')")
public String helloProtected() {
return "Hello, Protected User!";
}
Code correct :
@GetMapping("/protected/hello")
@PreAuthorize("hasRole('USER')")
public String helloProtected() {
return "Hello, Protected User!";
}
Erreur 3 : NullPointerException
Message d'erreur : java.lang.NullPointerException
Code incorrect :
@Autowired
private UserRepository userRepository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username).orElse(null);
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), AuthorityUtils.createAuthorityList("USER"));
}
Code correct :
@Autowired
private UserRepository userRepository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), AuthorityUtils.createAuthorityList("USER"));
}
Pour aller plus loin
Intégration avec OAuth2 : Apprendre comment intégrer OAuth2 pour une authentification externe (Google, Facebook).
Token-based authentication : Utiliser des tokens JWT pour la gestion de l'authentification.
Custom authentication provider : Créer un fournisseur d'authentification personnalisé pour intégrer avec une base de données tierce ou un service externe.
Défi pratique
Défi : Ajouter une fonctionnalité d'inscription utilisateur pour votre application. Les utilisateurs devraient pouvoir s'inscrire en fournissant un nom d'utilisateur, un mot de passe et une adresse e-mail.