Contactez-nous

Utilisation de API Keys

Découvrez comment implémenter l'authentification par clé d'API dans Spring Boot et Spring Security pour identifier et autoriser les applications clientes accédant à vos API.

Qu'est-ce qu'une clé d'API et pourquoi l'utiliser ?

Une clé d'API (API Key) est un jeton unique, généralement une chaîne de caractères alphanumériques, utilisé pour identifier et authentifier une application ou un projet client qui souhaite consommer une API. Contrairement aux mécanismes comme JWT ou OAuth2 qui authentifient souvent des *utilisateurs* finaux, une clé d'API identifie l'application appelante elle-même.

Les clés d'API sont couramment utilisées dans plusieurs scénarios :

  • Identification de projet : Pour savoir quelle application ou quel service externe utilise votre API.
  • Suivi de l'utilisation et quotas : Permet de mesurer la consommation de l'API par chaque client et d'appliquer des limites de taux (rate limiting) ou des quotas d'appels.
  • Contrôle d'accès simple : Fournir un niveau de base pour autoriser ou refuser l'accès à certaines parties d'une API pour des applications clientes spécifiques.
  • Communication inter-services (simplifiée) : Dans des architectures internes où une authentification utilisateur complexe n'est pas nécessaire entre microservices de confiance.

Il est crucial de comprendre que les clés d'API offrent un niveau de sécurité relativement basique. Elles ne sont généralement pas considérées comme suffisantes pour protéger des données utilisateur sensibles ou pour des scénarios nécessitant une authentification utilisateur forte et une délégation d'autorisation fine (comme le ferait OAuth2).

Fonctionnement de l'authentification par clé d'API

Le mécanisme est fondamentalement simple :

  1. Le serveur (fournisseur de l'API) génère une clé unique pour chaque application cliente autorisée.
  2. Cette clé est communiquée de manière sécurisée à l'application cliente.
  3. Lorsqu'elle effectue un appel à l'API, l'application cliente inclut cette clé dans sa requête HTTP. La méthode la plus courante et recommandée est d'utiliser un en-tête HTTP personnalisé, par exemple `X-API-KEY: VOTRE_CLE_UNIQUE_ICI`. Moins sécurisé, mais parfois vu, l'ajout en tant que paramètre de requête (ex: `?apiKey=VOTRE_CLE`).
  4. Côté serveur, un mécanisme (souvent un filtre Spring Security) intercepte la requête entrante.
  5. Le filtre extrait la clé d'API de la requête.
  6. Il vérifie ensuite si la clé extraite est valide, généralement en la comparant à une liste de clés autorisées stockées de manière sécurisée (base de données, fichier de configuration sécurisé, service de gestion de secrets).
  7. Si la clé est valide, la requête est autorisée à continuer son traitement. Souvent, une forme d'identité représentant l'application cliente est placée dans le contexte de sécurité.
  8. Si la clé est invalide ou manquante, le serveur rejette la requête, typiquement avec un code de statut HTTP `401 Unauthorized` ou `403 Forbidden`.

La simplicité est l'avantage principal, mais elle implique aussi que toute personne ou application en possession d'une clé valide peut potentiellement usurper l'identité de l'application cliente légitime.

Implémentation avec Spring Security

Pour intégrer l'authentification par clé d'API dans Spring Security, on crée généralement un filtre personnalisé qui s'insère dans la chaîne de filtres de sécurité.

1. Création du Filtre d'Authentification : Ce filtre va extraire la clé et tenter d'authentifier la requête. Une approche courante est d'étendre `OncePerRequestFilter` pour garantir une exécution unique par requête.

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.util.StringUtils;
// Autres imports nécessaires (HttpServletRequest, etc.)
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;

// Supposons un service qui valide la clé
public interface ApiKeyValidationService {
    Optional getApplicationNameForKey(String apiKey);
}

public class ApiKeyAuthFilter extends OncePerRequestFilter {

    private final String apiKeyHeaderName; // Ex: "X-API-KEY"
    private final ApiKeyValidationService validationService;

    public ApiKeyAuthFilter(String apiKeyHeaderName, ApiKeyValidationService validationService) {
        this.apiKeyHeaderName = apiKeyHeaderName;
        this.validationService = validationService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 
            throws ServletException, IOException {

        String apiKey = request.getHeader(apiKeyHeaderName);

        if (StringUtils.hasText(apiKey)) {
            // Valider la clé via le service
            Optional applicationNameOpt = validationService.getApplicationNameForKey(apiKey);

            if (applicationNameOpt.isPresent()) {
                // Clé valide : créer un objet Authentication
                String applicationName = applicationNameOpt.get();
                // On utilise ici le nom de l'application comme principal
                // On peut attribuer des autorités/rôles spécifiques à la clé si nécessaire
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        applicationName, // Principal
                        null, // Credentials (non pertinent ici)
                        AuthorityUtils.createAuthorityList("ROLE_API_CLIENT") // Exemple d'autorité
                );
                // Placer l'authentification dans le contexte de sécurité
                SecurityContextHolder.getContext().setAuthentication(authentication);
                logger.debug("API Key authentication successful for application: " + applicationName);
            } else {
                // Clé invalide
                logger.warn("Invalid API Key received: " + apiKey.substring(0, Math.min(apiKey.length(), 8)) + "...");
                // Optionnel : renvoyer une erreur 401/403 ici directement ou laisser 
                // la chaîne de sécurité continuer et échouer plus tard
                // response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                // response.getWriter().write("Invalid API Key");
                // return;
            }
        }
        
        // Continuer la chaîne de filtres
        filterChain.doFilter(request, response);
    }
}

2. Service de Validation : L'interface `ApiKeyValidationService` doit être implémentée pour vérifier la clé. Cette implémentation peut consulter une base de données, un fichier de propriétés sécurisé, ou un service externe.

@Service
public class SimpleApiKeyValidationService implements ApiKeyValidationService {
    // !!! ATTENTION : Ceci est un exemple TRES SIMPLIFIE et NON SECURISE !!!
    // !!! NE JAMAIS STOCKER DE CLES EN CLAIR DANS LE CODE !!!
    // Préférez une base de données avec clés hachées ou un gestionnaire de secrets.
    private final Map validApiKeys = Map.of(
        "cle_secrete_app1", "Application_1",
        "cle_tres_sure_app2", "Application_2"
    );

    @Override
    public Optional getApplicationNameForKey(String apiKey) {
        // Idéalement, comparez un hash de la clé reçue avec des hashs stockés
        return Optional.ofNullable(validApiKeys.get(apiKey));
    }
}

3. Configuration de la Chaîne de Sécurité : Intégrez le filtre dans votre configuration `SecurityFilterChain`.

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Autowired
    private ApiKeyValidationService apiKeyValidationService;

    @Value("${api.key.header.name:X-API-KEY}") // Nom de l'en-tête depuis les propriétés
    private String apiKeyHeaderName;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        ApiKeyAuthFilter apiKeyAuthFilter = new ApiKeyAuthFilter(apiKeyHeaderName, apiKeyValidationService);

        http
            .csrf(csrf -> csrf.disable()) // Souvent désactivé pour les API
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // API Stateless
            .authorizeHttpRequests(authorize -> authorize
                // Appliquer l'authentification par clé API à certains chemins
                .requestMatchers("/api/secure/**").authenticated() 
                // D'autres chemins peuvent avoir d'autres règles
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().denyAll() // Refuser tout le reste par défaut
            )
            // Ajouter notre filtre AVANT un filtre standard comme BasicAuthenticationFilter
            .addFilterBefore(apiKeyAuthFilter, BasicAuthenticationFilter.class);
            // Si vous n'utilisez que l'API Key, vous pourriez le mettre avant UsernamePasswordAuthenticationFilter

        return http.build();
    }
}

Gestion et stockage sécurisé des clés d'API

La sécurité de votre API repose entièrement sur la confidentialité des clés. Une gestion négligente peut entraîner un accès non autorisé.

  • Stockage Côté Serveur : Ne stockez JAMAIS les clés d'API en clair. La meilleure pratique est de stocker un hash cryptographique (par exemple, SHA-256 ou bcrypt) de la clé dans votre base de données ou fichier de configuration. Lors de la validation, vous hashez la clé reçue et comparez le hash avec ceux stockés. L'utilisation de gestionnaires de secrets (Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) est encore plus recommandée pour le stockage centralisé et sécurisé.
  • Génération : Générez des clés longues et aléatoires, difficiles à deviner. Des UUIDs ou des chaînes aléatoires générées par des bibliothèques cryptographiques sont de bons choix.
  • Transmission Initiale : Communiquez la clé au client de manière sécurisée (par exemple, via une interface d'administration protégée, un canal chiffré).
  • Rotation : Mettez en place une politique de rotation des clés. Permettez aux clients de générer de nouvelles clés et de désactiver les anciennes pour limiter les risques en cas de compromission.
  • Révocation : Ayez un mécanisme pour révoquer immédiatement une clé compromise ou suspecte.
  • Association et Permissions : Associez chaque clé à une application cliente spécifique. Vous pouvez également lier des permissions ou des quotas spécifiques à chaque clé pour un contrôle plus fin.

Considérations de sécurité et bonnes pratiques

Bien qu'utiles, les clés d'API nécessitent une attention particulière en matière de sécurité :

  • Identification vs Authentification Utilisateur : Rappelez-vous qu'une clé API identifie l'application, pas l'utilisateur final. N'utilisez pas les clés API comme seul mécanisme si vous avez besoin d'autorisations basées sur l'utilisateur. Elles peuvent être combinées avec d'autres mécanismes (ex: JWT pour l'utilisateur, API Key pour l'application).
  • Exposition : Les clés peuvent être exposées si elles sont incluses dans le code côté client (JavaScript), laissées dans les logs, ou transmises sur des canaux non sécurisés.
  • HTTPS Obligatoire : Utilisez systématiquement HTTPS pour chiffrer le trafic et protéger la clé pendant son transit.
  • Transmission Sécurisée : Privilégiez l'envoi de la clé dans un en-tête HTTP (`X-API-KEY` ou `Authorization: ApiKey `) plutôt que dans l'URL (paramètre de requête), car les URLs sont souvent loguées plus largement.
  • Rate Limiting : Mettez en place une limitation du nombre d'appels par clé pour prévenir les abus et les attaques par déni de service.
  • Monitoring : Surveillez l'utilisation des clés API pour détecter des activités suspectes ou des compromissions potentielles.
  • Portée Limitée : Si possible, limitez la portée des actions qu'une clé API peut effectuer aux opérations strictement nécessaires pour l'application cliente.