Aller au contenu

Semaine 6 : JOUR 3 – SPRING SECURITY & JWT

Formation Java / Spring Boot – Sécurisation professionnelle d’API


Objectifs

À l’issue de cette journée, vous sevez capable de :


Lien vers un cours complet sur JWT & Spring Boot Security


PARTIE 1 – INTRODUCTION À LA SÉCURITÉ (synthèse rapide)

1) Pourquoi sécuriser une API ?

Sans sécurité dans le cadre d’une application :

Une API bancaire doit être sécurisée. En fait, on doit sécuriser tous types d’application…


2) Notions fondamentales


3) Fonctionnement global avec JWT (JSON Web Token)

  1. L’utilisateur envoie son login et mot de passe
  2. Le serveur vérifie que les 2 sont ok
  3. Le serveur génère un JWT (un jeton unique)
  4. Le client (navigateur) envoie le JWT (jeton) dans les requêtes suivantes (via l’entête HTTP)
  5. Le serveur (application) valide le token (jeton)

PARTIE 2 – CONFIGURATION SPRING SECURITY

Pour utiliser la sécurité de Spring, il nous faut créer une classe spécifique dédiée à la sécurité et importer une dépendance dans notre pom.xml.

4) Dépendance Maven

Il suffit de le préciser lors de la création du projet Spring Boot.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

5) Configuration de base (Spring Security 6+)

Première étape : sécurité basique.

La classe que l’on peut nommer SecurityConfig a 2 annotations @Configuration et @EnableMethodSecurity. Ensuite, on met en place un filterChain dont le rôle sera d’intercepter les requêtes pour gérer les autorisations selon les urls et les utilisateurs connectés ou pas.

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) 
            throws Exception {

        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/swagger-ui/**").permitAll()
                .requestMatchers("/v3/api-docs/**").permitAll()
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());

        return http.build();
    }
}

PARTIE 3 – AUTHENTIFICATION EN MÉMOIRE

6) Définir des utilisateurs

On utilise le service UserDetailService pour gérer un UserDetails avec une classe User. Vous voyez que c’est finalement intéressant de connaître les Design pattern comme Builder !

@Bean
public UserDetailsService userDetailsService() {
    UserDetails user =
        User.withUsername("user")
            .password(passwordEncoder().encode("password"))
            .roles("USER")
            .build();

    UserDetails admin =
        User.withUsername("admin")
            .password(passwordEncoder().encode("admin"))
            .roles("ADMIN")
            .build();

    return new InMemoryUserDetailsManager(user, admin);
}

7) PasswordEncoder obligatoire

Nous verrons à quoi correspond de PasswordEncoder sachant qu’un mot de passe ne doit pas être enregistré en CLAIR !

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

PARTIE 4 – LIMITER L’ACCÈS PAR LES RÔLES

8) Sécuriser un endpoint

Ici, on ne va autoriser qu’un utilisateur ayant le rôle ADMIN voir les Uses Cases pour déterminer qui peut accéder ou pas aux différentes fonctionnalités. Là, on voit que la méthode supprimerCompte en peut être réalisée que par un administrateur. Le @PreAuthorize suffit et l’on peut définir plusieurs rôles.

@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public void supprimerCompte(@PathVariable Long id) {
    service.supprimerCompte(id);
}

9) Tester via Bruno

Allez dans Bruno et modifier le Header pour faciliter les accès :

Authorization: Basic base64(user:password)

PARTIE 5 – INTRODUCTION AUX JWT


10) Pourquoi JWT ?

Problème du Basic Auth :

Solution :


11) Structure d’un JWT (jeton)

Un JWT contient :

  1. Header
  2. Payload
  3. Signature

Exemple : xxxxx.yyyyy.zzzzz

Voir les cours complets sur JWT :


PARTIE 6 – IMPLÉMENTATION SIMPLE JWT

12) Dépendance JWT

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.11.5</version>
</dependency>

13) Service de génération de token

@Service
public class JwtService {

    private final String SECRET = "cle-secrete-super-longue";

    public String generateToken(String username) {

        return Jwts.builder()
            .setSubject(username)
            .setIssuedAt(new Date())
            .setExpiration(
                new Date(System.currentTimeMillis() + 1000 * 60 * 60))
            .signWith(
                Keys.hmacShaKeyFor(SECRET.getBytes()),
                SignatureAlgorithm.HS256)
            .compact();
    }
}

14) Endpoint d’authentification

Nous allons détailer les différentes classes (vous connaissez déjà les DTO). Ici, on utilise un gestionnaire d'authentification le authenticationManager qui possède une méhtode pour instancier un objet UsernamePasswordAuthenticationToken` dans lequel on passe le username et le password.

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginDTO dto) {

    authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(
            dto.username(), dto.password()));

    String token = jwtService.generateToken(dto.username());

    return ResponseEntity.ok(Map.of("token", token));
}

PARTIE 7 – LE FILTRE JWT


15) Filtre de validation

Un filtre :

Concept simplifié :

String authHeader = request.getHeader("Authorization");

Puis validation.


PARTIE 8 – TEST COMPLET AVEC BRUNO


16) Scénario Bruno

  1. POST /api/auth/login
  2. Récupérer le token
  3. Ajouter header :
Authorization: Bearer <token>
  1. Tester vos endpoints protégés

PARTIE 9 – TRAVAUX PRATIQUES


TP 1 – Sécuriser tous les endpoints de votre application


TP 2 – Ajouter un rôle ADMIN


TP 3 – JWT complet


PARTIE 10 – ERREURS FRÉQUENTES

  1. Oublier PasswordEncoder (du coup, votre mot de passe est en clair)
  2. Laisser CSRF actif en API REST
  3. Ne pas protéger Swagger correctement (juste pédagogique, vous ne l’utiliserez pas)
  4. Mal configurer les rôles
  5. Stocker le token côté serveur !
  6. Utiliser un secret trop court
  7. Ne pas gérer de date d’expiration du token
  8. Tester uniquement avec ADMIN et pas les autres users
  9. Mélanger sécurité et métier
  10. Ignorer les logs

État de votre projet

Il devrait être :


Synthèse

Vous savez désormais :