Retour aux articles

Implémentation avancée de Spring Security 6 avec JWT pour API REST Spring Boot

Implémentation avancée de Spring Security 6 avec JWT pour API REST Spring Boot | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular
```html

Implémentation avancée de Spring Security 6 avec JWT pour une API REST Spring Boot

Spring Security 6 offre une architecture moderne et flexible pour sécuriser des API REST. L’approche basée sur JWT (JSON Web Token) permet d’éviter la dépendance à une session serveur, tout en offrant un contrôle fin sur l’authentification, l’autorisation et la gestion du cycle de vie des jetons. Cette démarche décrit une implémentation avancée, adaptée aux systèmes où la robustesse, la traçabilité et la sécurité de bout en bout sont prioritaires.

Principes clés

Les objectifs d’une implémentation JWT avancée sont les suivants :

  • Statelessness : chaque requête transporte un jeton, sans session serveur.
  • Validation stricte : signature, expiration, audience, issuer et structure.
  • Contrôle d’accès déclaratif : annotations et règles centralisées.
  • Journalisation et traçabilité : suivi des événements d’accès.
  • Rotation et révocation : stratégies pratiques (selon contraintes).

Pré-requis Maven/Gradle

Le projet repose sur Spring Boot 3.x et Spring Security 6.x. Les dépendances typiques incluent Spring Security, Web, et un parseur JWT (ex. Nimbus JOSE + JWT).

Exemple Gradle

dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'com.nimbusds:nimbus-jose-jwt:9.37.2' }

Configuration : SecurityFilterChain et modèle sans WebSecurityConfigurerAdapter

Spring Security 6 impose l’usage de SecurityFilterChain et de la configuration via beans. La logique de filtre (JWT) s’insère avant l’authentification standard.

Exemple de configuration

csrf.disable()) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/public/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .build(); } @Bean AuthenticationProvider authenticationProvider(JwtAuthenticationProvider provider) { return provider; } } ]]>

Génération et validation JWT

Une implémentation avancée segmente la responsabilité :

  • Service JWT : création des claims et signature.
  • Validator : vérification des claims, expiration et contraintes.
  • Filtre : extraction du jeton depuis l’en-tête Authorization.

Extraction du token depuis Authorization

Service de génération

roles) { Instant now = Instant.now(); Instant expiry = now.plusSeconds(3600); Map claims = new HashMap<>(); claims.put("roles", roles); JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256).build(); JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder() .issuer("my-company") .audience(List.of("my-api")) .subject(subject) .claims(claims) .issueTime(Date.from(now)) .expirationTime(Date.from(expiry)) .build(); SignedJWT signedJWT = new SignedJWT(header, jwtClaims); try { signedJWT.sign(new MACSigner(this.signingKey)); return signedJWT.serialize(); } catch (JOSEException e) { throw new IllegalStateException("JWT signing failed", e); } } } ]]>

Filtrage : JwtAuthenticationFilter

Le filtre doit :

  • Lire l’en-tête Bearer.
  • Valider le jeton (signature + expiration + issuer/audience).
  • Construire un Authentication (principal + autorités).
  • Renseigner SecurityContext si valide.

Exemple de filtre

Validation stricte des claims

Une validation avancée évite les “faux positifs” en imposant l’issuer, l’audience, la signature, et en vérifiant le format des rôles.

Validator

aud = claimsSet.getAudience() == null ? List.of() : claimsSet.getAudience(); if (!"my-company".equals(issuer) || !aud.contains("my-api")) { throw new JwtValidationException("Invalid issuer/audience"); } Date exp = claimsSet.getExpirationTime(); if (exp == null || exp.before(new Date())) { throw new JwtValidationException("Token expired"); } String subject = claimsSet.getSubject(); Object rolesObj = claimsSet.getClaims().get("roles"); List roles = rolesObj instanceof List list ? list.stream().map(String::valueOf).toList() : List.of(); return new JwtClaims(subject, roles); } catch (ParseException | JOSEException e) { throw new JwtValidationException("JWT validation failed", e); } } } ]]>

Autorisation : méthode et règles fines

Spring Security permet une autorisation via hasRole côté configuration, mais aussi via des annotations pour des cas d’usage plus complexes.

Exemple @PreAuthorize

stats() { return Map.of("status", "ok"); } } ]]>

Gestion des erreurs et réponses cohérentes

Une API REST professionnelle doit retourner des erreurs structurées. Le filtre peut court-circuiter la chaîne avec un 401 Unauthorized et un corps JSON normalisé.

Exemple de payload d’erreur

Rotation des clés, stratégies de révocation et sécurité avancée

Pour une posture “production-grade”, une simple expiration peut ne pas suffire. Plusieurs stratégies existent :

  • Rotation des clés : publier un nouveau secret et gérer une période de transition.
  • Revocation contrôlée : blacklisting token id (jti) en base/redis.
  • Durées courtes : access tokens courts + refresh tokens protégés.
  • Contrôle des usages : vérifier claims tels que audience et issuer.

Ajout de claim jti (recommandé)

Testabilité et bonnes pratiques

Les tests doivent valider :

  • Requêtes sans token : 401 selon endpoint.
  • Token expiré : 401 et corps d’erreur normalisé.
  • Token signé incorrectement : 401.
  • Accès admin : 403 si rôles non conformes.
  • Accès autorisé : 200 et SecurityContext correctement renseigné.

Exemple de test MockMvc (squelette)

Conclusion

Une implémentation avancée de Spring Security 6 avec JWT repose sur une séparation claire des responsabilités (génération, validation, filtrage), une validation stricte des claims, et une gestion rigoureuse des erreurs. En complément, la rotation de clés et/ou une stratégie de révocation (jti) améliore significativement la sécurité. Cette architecture permet de sécuriser durablement une API REST à grande échelle.

Recommandation : privilégier des access tokens courts, des refresh tokens soigneusement protégés, et une validation de claims alignée sur l’API cible.

À 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