Implémentation de l'authentification JWT sécurisée dans une API REST Spring Boot 3 avec Spring Security
Cet article explique comment implémenter une authentification par JSON Web Token (JWT) pour une API REST développée avec Spring Boot 3 et Spring Security. L'approche présentée privilégie la sécurité, la séparation des responsabilités et la conformité aux bonnes pratiques : chiffrement des secrets, stratégie de rafraîchissement (refresh tokens) et validation côté serveur.
Principes et architecture
On adopte un modèle stateless : le serveur n'enregistre pas la session de l'utilisateur. Après authentification (login), l'API émet un JWT signé. Le client envoie ce jeton dans l'en-tête Authorization: Bearer <token> pour les requêtes suivantes. Le serveur valide la signature et les revendications (claims) avant d'autoriser l'accès.
Points clés :
Signature : utiliser HS256 pour des clés secrètes bien protégées ou RS256/ES256 pour une paire clé publique/privée permettant la rotation et la vérification coté ressource.
Expiration : définir un TTL court pour l'access token (par ex. 15 minutes) et utiliser un refresh token stocké de façon sécurisée côté serveur pour renouveler l'access token.
Protection des clés : stocker les secrets dans des variables d'environnement, Vault, ou un store sécurisé, jamais dans le code source.
Configuration Spring Security (extrait)
La configuration suivante illustre la création d'un SecurityFilterChain stateless et l'insertion d'un filtre JWT avant le filtre d'authentification standard.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(daoAuthenticationProvider())
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
Provider et encodage de mot de passe
Utiliser PasswordEncoder (BCrypt recommandé) et un DaoAuthenticationProvider avec un UserDetailsService pour l'authentification par login/password avant émission du JWT.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(customUserDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
Génération et validation du JWT
Un utilitaire de token doit encapsuler la génération, la validation, l'extraction des claims et la gestion de l'expiration. Exemple simplifié utilisant JJWT (io.jsonwebtoken) et une clé secrète HMAC :
public String generateToken(UserDetails user) {
Date now = new Date();
Date exp = new Date(now.getTime() + ACCESS_TOKEN_VALIDITY_MS);
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getAuthorities())
.setIssuedAt(now)
.setExpiration(exp)
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
public boolean validateToken(String token) {
Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token);
return true;
}
Pour RS256, signer avec la clé privée et valider avec la clé publique (préférable en production pour permettre la vérification sans exposer la clé privée).
Filtre JWT (extrait)
Le filtre extrait le token de l'en-tête, le valide et construit une Authentication dans le SecurityContext si le jeton est valide.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException {
String header = req.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.validateToken(token)) {
String username = jwtUtil.extractUsername(token);
UserDetails user = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(req, res);
}
}
Stratégie de refresh token
Le refresh token doit :
- Avoir une durée de vie plus longue que l'access token.
- Être stocké côté serveur (base de données) pour permettre le révocation et la rotation.
- Être transporté via cookie HttpOnly sécurisé ou stocké de façon sécurisée côté client selon le contexte.
Flux typique :
1) /api/auth/login : authentifie, émet access token et refresh token (le refresh peut être renvoyé dans le corps ou via cookie HttpOnly). 2) /api/auth/refresh : reçoit le refresh token, vérifie son état en base, émet un nouvel access token (et éventuellement un nouveau refresh token en rotation).
Bonnes pratiques de sécurité
Ne jamais versionner les clés secrètes ou les fichiers keystore. Préférer les secrets manager. Signer les tokens avec RS/ES pour séparer signature et vérification. Limiter la durée de vie des tokens, implémenter la rotation des refresh tokens et la possibilité de les révoquer. Surveiller et logger les tentatives d'authentification échouées et les usages anormaux.
Tests et mise en production
Automatiser les tests unitaires et d'intégration pour :
- Vérifier la génération de token et la lecture des claims.
- Valider le filtre d'authentification et les règles d'autorisation.
- Simuler la rotation et la révocation des refresh tokens.
Lors du déploiement, configurer HTTPS obligatoire, headers de sécurité (CSP, HSTS), et assurer une rotation régulière des clés. Documenter les endpoints d'authentification et les schémas JWT pour les consommateurs de l'API.
Check-list rapide
- Utiliser HTTPS en production.
- Stocker les secrets dans un gestionnaire sécurisé.
- Préférer RS/ES pour la signature quand c'est possible.
- TTL court pour les access tokens et gestion des refresh tokens côté serveur.
- Logger et surveiller l'activité d'authentification.
Avec cette architecture et ces composants, l'API REST Spring Boot 3 dispose d'une authentification JWT robuste, évolutive et compatible avec les exigences de sécurité modernes.
À propos de l'auteur
Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.
Contact : latygueyesamba@gmail.com | Dakar, Sénégal