Intégration de l'authentification JWT dans une architecture SSM

Architecture et principe de fonctionnement

L'architecture SSM (Spring, Spring MVC, MyBatis) est une stack technique éprouvée pour le développement d'applications web Java, offrant une exclelente séparation des préoccupations et des performances optimales. L'intégration de JSON Web Tokens (JWT) dans cet écosystème permet de déléguer la gestion de l'état de session au client, rendant l'application sans état (stateless) et parfaitement adaptée aux architectures orientées API REST.

Le flux d'authentification repose sur un mécanisme simple : lors d'une connexion réussie, le serveur génère un jeton signé contenant les revendications (claims) de l'utilisateur. Ce jeton est ensuite inclus dans l'en-tête Authorization des requêtes HTTP ultérieures. Un intercepteur Spring MVC ou un filtre Spring Security intercepte ces requêtes, valide la signature cryptographique et vérifie l'expiration avant d'autoriser l'accès aux contrôleurs protégés.

Configuration des dépendances Maven

Pour manipuler les jetons de manière sécurisée et conforme aux standards récents, il est recommandé d'utiliser la bibliothèque JJWT. Ajoutez les dépendances suivantes à votre fichier pom.xml :

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

Paramétrage de l'application

Définissez la clé secrète et la durée de validité du jeton dans le fichier application.properties. Assurez-vous que la clé secrète est suffisamment longue pour l'algorithme de hachage choisi (au moins 256 bits pour HS256).

security.jwt.secret-key=dGhpc0lzQVZlcnlMb25nU2VjcmV0S2V5Rm9ySG1hY1NoYTI1NkFsZ29yaXRobQ==
security.jwt.expiration-ms=3600000

Composant de gestion des jetons

Créez un service dédié à la création et à la validation des jetons. Cette classe utilise l'API moderne de JJWT pour signer et parser les payloads, en s'appuyant sur des clés symétriques sécurisées.

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.Map;

@Component
public class TokenManager {

    @Value("${security.jwt.secret-key}")
    private String base64Secret;

    @Value("${security.jwt.expiration-ms}")
    private long expirationMs;

    private Key getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(base64Secret);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public String issueToken(String subject, Map<String, Object> customClaims) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + expirationMs);

        return Jwts.builder()
                .setSubject(subject)
                .addClaims(customClaims)
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    public Claims extractClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    public boolean isTokenValid(String token) {
        try {
            extractClaims(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

Implémentation des points de terminaison (Endpoints)

Le contrôleur d'authentification vérifie les identifiants (via un service métier non détaillé ici) et délivre le jeton. Un second contrôleur démontre l'accès à une ressource protégée.

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/auth")
public class AuthenticationEndpoint {

    private final TokenManager tokenManager;

    public AuthenticationEndpoint(TokenManager tokenManager) {
        this.tokenManager = tokenManager;
    }

    @PostMapping("/signin")
    public ResponseEntity<Map<String, String>> signIn(@RequestBody Map<String, String> credentials) {
        String username = credentials.get("username");
        String password = credentials.get("password");
        
        // TODO: Déléguer la validation à un AuthService (vérification en base de données, etc.)
        
        Map<String, Object> claims = new HashMap<>();
        claims.put("role", "ROLE_USER");
        
        String jwt = tokenManager.issueToken(username, claims);
        return ResponseEntity.ok(Collections.singletonMap("access_token", jwt));
    }
}

@RestController
@RequestMapping("/api/resources")
public class SecureResourceEndpoint {

    @GetMapping("/profile")
    public ResponseEntity<String> getProfile(@RequestHeader("Authorization") String authHeader) {
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return ResponseEntity.status(401).body("Jeton manquant ou invalide.");
        }
        
        String token = authHeader.substring(7);
        // Dans une implémentation complète, un HandlerInterceptor validerait le jeton 
        // avant que la requête n'atteigne ce contrôleur.
        
        return ResponseEntity.ok("Accès autorisé au profil utilisateur.");
    }
}

Étiquettes: Spring spring-mvc MyBatis JWT Java

Publié le 20 juillet à 21h34