Spring Security 6 : Authentification JWT complète
Guide pratique pour implémenter une authentification JWT avec Spring Security 6. Configuration, génération de tokens, validation et bonnes pratiques.

L'authentification JWT (JSON Web Token) représente la norme pour sécuriser les API REST modernes. Spring Security 6 introduit une nouvelle approche fonctionnelle de configuration qui simplifie l'implémentation tout en renforçant la sécurité. Ce guide couvre l'ensemble du processus, de la configuration initiale à la protection des endpoints.
Ce tutoriel utilise Spring Boot 3.2+ et Spring Security 6.2+. Les concepts restent valables pour les versions ultérieures, avec des ajustements mineurs de syntaxe.
Architecture JWT dans Spring Security
L'authentification JWT repose sur un principe stateless : le serveur ne stocke aucune session. Chaque requête contient un token signé qui prouve l'identité de l'utilisateur. Cette architecture permet une scalabilité horizontale sans partage de session entre instances.
Le flux d'authentification JWT se décompose en plusieurs étapes. L'utilisateur s'authentifie avec ses identifiants, reçoit un token JWT signé, puis inclut ce token dans chaque requête ultérieure. Le serveur valide la signature et extrait les informations utilisateur du token.
// Représentation conceptuelle du flux d'authentification
public class JwtAuthenticationFlow {
// 1. Authentification initiale : POST /api/auth/login
// → Vérification identifiants en base
// → Génération du token JWT signé
// → Retour du token au client
// 2. Requêtes authentifiées : GET /api/protected
// → Header: Authorization: Bearer <token>
// → Extraction et validation du token
// → Création du SecurityContext
// → Accès à la ressource protégée
}Cette approche élimine les problèmes de sticky sessions et simplifie le déploiement en environnement distribué.
Configuration des dépendances Maven
Le projet nécessite les dépendances Spring Security et une bibliothèque JWT. JJWT (Java JWT) offre une API fluide et bien maintenue pour la manipulation des tokens.
<!-- pom.xml -->
<dependencies>
<!-- Spring Security pour la gestion de l'authentification -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Web pour les endpoints REST -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JJWT : génération et validation des tokens -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Data JPA pour la persistance utilisateur -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>La séparation en trois modules JJWT (api, impl, jackson) suit le principe d'encapsulation : seule l'API est visible au compile-time, l'implémentation reste un détail runtime.
Service de génération et validation JWT
Le service JWT centralise toutes les opérations sur les tokens : génération, extraction des claims et validation. Une clé secrète sécurisée signe chaque token, garantissant son intégrité.
@Service
public class JwtService {
// Clé secrète injectée depuis application.yml
@Value("${app.jwt.secret}")
private String secretKey;
// Durée de validité du token (24 heures par défaut)
@Value("${app.jwt.expiration:86400000}")
private long jwtExpiration;
// Génère un token JWT pour un utilisateur authentifié
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
// Génère un token avec des claims personnalisés
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return Jwts.builder()
.claims(extraClaims) // Claims additionnels (rôles, permissions)
.subject(userDetails.getUsername()) // Identifiant principal
.issuedAt(new Date()) // Date de création
.expiration(new Date(System.currentTimeMillis() + jwtExpiration))
.signWith(getSigningKey(), Jwts.SIG.HS256) // Signature HMAC-SHA256
.compact();
}
// Extrait le username (subject) du token
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// Extrait un claim spécifique via une fonction d'extraction
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
// Valide le token : signature correcte et non expiré
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
// Vérifie si le token a expiré
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
// Extrait la date d'expiration
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// Parse le token et extrait tous les claims
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey()) // Vérifie la signature
.build()
.parseSignedClaims(token) // Parse le token signé
.getPayload(); // Retourne les claims
}
// Génère la clé de signature depuis la clé secrète encodée en Base64
private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}La clé secrète doit être suffisamment longue (256 bits minimum pour HS256) et stockée de manière sécurisée, jamais dans le code source.
Utilisez une variable d'environnement ou un gestionnaire de secrets pour la clé JWT. Une clé compromise permet de forger des tokens valides pour n'importe quel utilisateur.
Configuration de l'entité User
L'entité utilisateur implémente UserDetails de Spring Security, permettant une intégration directe avec le système d'authentification.
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
// Rôle stocké comme enum pour la sécurité de type
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
// Implémentation UserDetails : retourne les autorités de l'utilisateur
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}
// Le username correspond à l'email dans cette implémentation
@Override
public String getUsername() {
return email;
}
// Compte toujours actif (à adapter selon les besoins)
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// Getters et setters omis pour la concision
}public enum Role {
USER, // Utilisateur standard
ADMIN // Administrateur avec privilèges étendus
}L'enum Role limite les valeurs possibles et facilite la vérification des autorisations dans les expressions SpEL.
Filtre d'authentification JWT
Le filtre JWT intercepte chaque requête pour extraire et valider le token. Il s'insère dans la chaîne de filtres Spring Security avant le filtre d'authentification standard.
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
// Récupère le header Authorization
final String authHeader = request.getHeader("Authorization");
// Vérifie la présence du préfixe Bearer
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
// Extrait le token (sans le préfixe "Bearer ")
final String jwt = authHeader.substring(7);
try {
// Extrait le username du token
final String userEmail = jwtService.extractUsername(jwt);
// Vérifie que l'utilisateur n'est pas déjà authentifié
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// Charge les détails utilisateur depuis la base
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
// Valide le token (signature + expiration + correspondance user)
if (jwtService.isTokenValid(jwt, userDetails)) {
// Crée l'objet d'authentification
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
// Ajoute les détails de la requête
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// Définit l'authentification dans le contexte de sécurité
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
} catch (ExpiredJwtException e) {
// Token expiré : l'utilisateur devra se réauthentifier
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Token expired");
return;
} catch (JwtException e) {
// Token invalide : signature incorrecte ou format malformé
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid token");
return;
}
// Continue la chaîne de filtres
filterChain.doFilter(request, response);
}
}OncePerRequestFilter garantit que le filtre s'exécute une seule fois par requête, même en cas de forward ou include.
Prêt à réussir tes entretiens Spring Boot ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Configuration Spring Security 6
Spring Security 6 utilise une approche fonctionnelle avec des lambdas pour configurer la chaîne de sécurité. Cette configuration définit les règles d'accès et intègre le filtre JWT.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Active @PreAuthorize et @PostAuthorize
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// Désactive CSRF car l'API est stateless (pas de cookies de session)
.csrf(csrf -> csrf.disable())
// Configure les règles d'autorisation
.authorizeHttpRequests(auth -> auth
// Endpoints publics : authentification et inscription
.requestMatchers("/api/auth/**").permitAll()
// Documentation API accessible sans authentification
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
// Endpoints admin réservés aux administrateurs
.requestMatchers("/api/admin/**").hasRole("ADMIN")
// Toutes les autres requêtes nécessitent une authentification
.anyRequest().authenticated()
)
// Mode stateless : pas de session HTTP côté serveur
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// Fournisseur d'authentification personnalisé
.authenticationProvider(authenticationProvider)
// Insère le filtre JWT avant le filtre d'authentification standard
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {
private final UserRepository userRepository;
// Service de chargement des utilisateurs pour Spring Security
@Bean
public UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
}
// Fournisseur d'authentification avec encoder de mot de passe
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// Gestionnaire d'authentification exposé comme bean
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception {
return config.getAuthenticationManager();
}
// Encoder BCrypt pour le hashage sécurisé des mots de passe
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}La désactivation de CSRF est sécurisée pour les API stateless car l'authentification repose sur un header explicite, non sur un cookie automatiquement envoyé par le navigateur.
Contrôleur d'authentification
Le contrôleur expose les endpoints d'inscription et de connexion. Ces endpoints sont publics et retournent le token JWT après une authentification réussie.
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthenticationController {
private final AuthenticationService authService;
// POST /api/auth/register - Inscription d'un nouvel utilisateur
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse> register(
@Valid @RequestBody RegisterRequest request
) {
return ResponseEntity.ok(authService.register(request));
}
// POST /api/auth/login - Connexion utilisateur existant
@PostMapping("/login")
public ResponseEntity<AuthenticationResponse> login(
@Valid @RequestBody AuthenticationRequest request
) {
return ResponseEntity.ok(authService.authenticate(request));
}
// POST /api/auth/refresh - Rafraîchissement du token (optionnel)
@PostMapping("/refresh")
public ResponseEntity<AuthenticationResponse> refresh(
@RequestHeader("Authorization") String authHeader
) {
return ResponseEntity.ok(authService.refreshToken(authHeader));
}
}public record AuthenticationRequest(
@NotBlank(message = "Email requis")
@Email(message = "Format email invalide")
String email,
@NotBlank(message = "Mot de passe requis")
String password
) {}public record RegisterRequest(
@NotBlank(message = "Prénom requis")
String firstName,
@NotBlank(message = "Nom requis")
String lastName,
@NotBlank(message = "Email requis")
@Email(message = "Format email invalide")
String email,
@NotBlank(message = "Mot de passe requis")
@Size(min = 8, message = "Le mot de passe doit contenir au moins 8 caractères")
String password
) {}public record AuthenticationResponse(
String token,
String type,
long expiresIn
) {
public AuthenticationResponse(String token, long expiresIn) {
this(token, "Bearer", expiresIn);
}
}Les records Java simplifient la définition des DTOs tout en garantissant l'immuabilité.
Service d'authentification
Le service orchestre la logique d'inscription et d'authentification, en déléguant au JwtService pour la génération des tokens.
@Service
@RequiredArgsConstructor
public class AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
@Value("${app.jwt.expiration:86400000}")
private long jwtExpiration;
// Inscription d'un nouvel utilisateur
@Transactional
public AuthenticationResponse register(RegisterRequest request) {
// Vérifie que l'email n'est pas déjà utilisé
if (userRepository.existsByEmail(request.email())) {
throw new EmailAlreadyExistsException("Email déjà enregistré");
}
// Crée l'entité utilisateur avec mot de passe hashé
User user = new User();
user.setFirstName(request.firstName());
user.setLastName(request.lastName());
user.setEmail(request.email());
user.setPassword(passwordEncoder.encode(request.password()));
user.setRole(Role.USER);
// Persiste l'utilisateur
userRepository.save(user);
// Génère et retourne le token JWT
String jwtToken = jwtService.generateToken(user);
return new AuthenticationResponse(jwtToken, jwtExpiration);
}
// Authentification d'un utilisateur existant
public AuthenticationResponse authenticate(AuthenticationRequest request) {
// Délègue la vérification au AuthenticationManager
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.email(),
request.password()
)
);
// Charge l'utilisateur (l'authentification a réussi)
User user = userRepository.findByEmail(request.email())
.orElseThrow(() -> new UsernameNotFoundException("Utilisateur non trouvé"));
// Génère le token avec claims additionnels
Map<String, Object> claims = new HashMap<>();
claims.put("role", user.getRole().name());
claims.put("userId", user.getId());
String jwtToken = jwtService.generateToken(claims, user);
return new AuthenticationResponse(jwtToken, jwtExpiration);
}
// Rafraîchissement du token (prolonge la session)
public AuthenticationResponse refreshToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new InvalidTokenException("Token invalide");
}
String oldToken = authHeader.substring(7);
String userEmail = jwtService.extractUsername(oldToken);
User user = userRepository.findByEmail(userEmail)
.orElseThrow(() -> new UsernameNotFoundException("Utilisateur non trouvé"));
// Génère un nouveau token
String newToken = jwtService.generateToken(user);
return new AuthenticationResponse(newToken, jwtExpiration);
}
}L'AuthenticationManager centralise la vérification des identifiants, permettant de changer facilement de stratégie d'authentification.
Pour une sécurité renforcée, implémentez un système de refresh token avec une durée de vie plus longue, stocké en base de données et révocable.
Protection des endpoints avec annotations
La sécurité au niveau des méthodes permet un contrôle granulaire des autorisations directement dans le code métier.
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// Accessible à tous les utilisateurs authentifiés
@GetMapping("/me")
public ResponseEntity<UserDTO> getCurrentUser(
@AuthenticationPrincipal User currentUser
) {
return ResponseEntity.ok(UserDTO.from(currentUser));
}
// Seul l'utilisateur concerné ou un admin peut modifier le profil
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request
) {
return ResponseEntity.ok(userService.updateUser(id, request));
}
// Réservé aux administrateurs
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ResponseEntity<List<UserDTO>> getAllUsers() {
return ResponseEntity.ok(userService.findAll());
}
// Suppression avec vérification post-exécution
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasRole('ADMIN')") // Toutes les méthodes requièrent ADMIN
public class AdminController {
private final AdminService adminService;
@GetMapping("/dashboard")
public ResponseEntity<DashboardDTO> getDashboard() {
return ResponseEntity.ok(adminService.getDashboardStats());
}
@PostMapping("/users/{id}/role")
public ResponseEntity<UserDTO> changeUserRole(
@PathVariable Long id,
@RequestParam Role newRole
) {
return ResponseEntity.ok(adminService.changeUserRole(id, newRole));
}
}L'annotation @AuthenticationPrincipal injecte directement l'utilisateur authentifié, évitant l'accès manuel au SecurityContext.
Gestion des erreurs d'authentification
Une gestion centralisée des erreurs assure des réponses cohérentes et informatives en cas d'échec d'authentification.
@RestControllerAdvice
public class SecurityExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(SecurityExceptionHandler.class);
// Erreur d'authentification (identifiants incorrects)
@ExceptionHandler(BadCredentialsException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleBadCredentials(BadCredentialsException ex) {
return new ErrorResponse(
"INVALID_CREDENTIALS",
"Email ou mot de passe incorrect",
null
);
}
// Accès refusé (authentifié mais non autorisé)
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ErrorResponse handleAccessDenied(AccessDeniedException ex) {
return new ErrorResponse(
"ACCESS_DENIED",
"Accès non autorisé à cette ressource",
null
);
}
// Token JWT expiré
@ExceptionHandler(ExpiredJwtException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleExpiredToken(ExpiredJwtException ex) {
return new ErrorResponse(
"TOKEN_EXPIRED",
"Session expirée, veuillez vous reconnecter",
null
);
}
// Token JWT invalide
@ExceptionHandler(JwtException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleInvalidToken(JwtException ex) {
log.warn("Invalid JWT token: {}", ex.getMessage());
return new ErrorResponse(
"INVALID_TOKEN",
"Token d'authentification invalide",
null
);
}
// Email déjà utilisé lors de l'inscription
@ExceptionHandler(EmailAlreadyExistsException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse handleEmailExists(EmailAlreadyExistsException ex) {
return new ErrorResponse(
"EMAIL_EXISTS",
ex.getMessage(),
null
);
}
}public record ErrorResponse(
String code,
String message,
Map<String, String> details
) {}Les codes d'erreur standardisés facilitent le traitement côté client et le debugging.
Configuration application.yml
La configuration externalisée permet d'adapter les paramètres JWT selon l'environnement.
# application.yml
app:
jwt:
# Clé secrète en Base64 (256 bits minimum pour HS256)
# Générer avec : openssl rand -base64 32
secret: ${JWT_SECRET:votreCleSuperSecreteDe256BitsMinimum}
# Durée de validité en millisecondes (24 heures)
expiration: 86400000
spring:
datasource:
url: jdbc:postgresql://localhost:5432/springjwt
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
jpa:
hibernate:
ddl-auto: validate
show-sql: false# application-dev.yml
app:
jwt:
# Expiration courte pour le développement (1 heure)
expiration: 3600000
spring:
jpa:
show-sql: true
hibernate:
ddl-auto: update
logging:
level:
org.springframework.security: DEBUGEn production, la clé secrète doit provenir d'une variable d'environnement ou d'un service de gestion des secrets comme Vault.
Tests d'intégration
Les tests vérifient le comportement complet du système d'authentification, de l'inscription à l'accès aux ressources protégées.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = Replace.ANY)
class AuthenticationIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void shouldRegisterAndAuthenticateUser() {
// Inscription
RegisterRequest registerRequest = new RegisterRequest(
"John", "Doe", "john@example.com", "password123"
);
ResponseEntity<AuthenticationResponse> registerResponse = restTemplate
.postForEntity("/api/auth/register", registerRequest, AuthenticationResponse.class);
assertThat(registerResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(registerResponse.getBody().token()).isNotBlank();
// Connexion avec les mêmes identifiants
AuthenticationRequest loginRequest = new AuthenticationRequest(
"john@example.com", "password123"
);
ResponseEntity<AuthenticationResponse> loginResponse = restTemplate
.postForEntity("/api/auth/login", loginRequest, AuthenticationResponse.class);
assertThat(loginResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(loginResponse.getBody().token()).isNotBlank();
}
@Test
void shouldRejectInvalidCredentials() {
AuthenticationRequest request = new AuthenticationRequest(
"unknown@example.com", "wrongpassword"
);
ResponseEntity<ErrorResponse> response = restTemplate
.postForEntity("/api/auth/login", request, ErrorResponse.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
void shouldAccessProtectedResourceWithValidToken() {
// Inscription pour obtenir un token
RegisterRequest registerRequest = new RegisterRequest(
"Jane", "Doe", "jane@example.com", "password123"
);
AuthenticationResponse authResponse = restTemplate
.postForObject("/api/auth/register", registerRequest, AuthenticationResponse.class);
// Accès à la ressource protégée avec le token
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(authResponse.token());
ResponseEntity<UserDTO> response = restTemplate.exchange(
"/api/users/me",
HttpMethod.GET,
new HttpEntity<>(headers),
UserDTO.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().email()).isEqualTo("jane@example.com");
}
@Test
void shouldRejectAccessWithoutToken() {
ResponseEntity<Void> response = restTemplate
.getForEntity("/api/users/me", Void.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
}Ces tests couvrent les scénarios principaux : inscription, connexion, accès autorisé et accès refusé.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Conclusion
L'implémentation d'une authentification JWT avec Spring Security 6 suit un pattern bien défini. Le service JWT gère la génération et validation des tokens, le filtre intercepte les requêtes pour établir le contexte de sécurité, et la configuration définit les règles d'accès.
Checklist de déploiement :
- ✅ Clé secrète de 256 bits minimum stockée en variable d'environnement
- ✅ HTTPS obligatoire en production pour protéger les tokens en transit
- ✅ Durée de validité des tokens adaptée au contexte (15 min à 24h)
- ✅ Refresh token pour les sessions longues sans réauthentification
- ✅ Gestion des erreurs standardisée avec codes métier
- ✅ Logging des tentatives d'authentification échouées
- ✅ Tests d'intégration couvrant les scénarios d'authentification
- ✅ Rate limiting sur les endpoints d'authentification
Cette architecture offre une base solide pour sécuriser les API REST, tout en restant extensible pour des besoins plus complexes comme l'authentification multi-facteurs ou l'intégration OAuth2.
Tags
Partager
Articles similaires

Spring Security 6 : configurer un serveur de ressources OAuth2
Guide pratique pour configurer un Resource Server OAuth2 avec Spring Security 6. Validation JWT, configuration issuer, gestion des scopes et intégration Keycloak.

Spring Modulith : architecture modulaire monolithique expliquée
Découvrez Spring Modulith pour construire des monolithes modulaires en Java. Architecture, modules, événements asynchrones et tests avec exemples Spring Boot 3.

Spring Batch 5 en entretien technique : partitioning, chunks et fault tolerance
Préparez vos entretiens Spring Batch 5 : 15 questions essentielles sur le partitioning, chunk-oriented processing, fault tolerance avec exemples de code Java 21.