Contactez-nous

Sécurisation avec OAuth2 (Resource Server)

Apprenez à configurer votre API Spring Boot comme un Resource Server OAuth2 pour valider les tokens d'accès (JWT/Opaque) émis par un Authorization Server.

OAuth2 et la séparation des rôles : Le Resource Server

OAuth2 est un framework d'autorisation standard de l'industrie, largement utilisé pour déléguer l'accès à des ressources protégées. Il définit plusieurs rôles clés, notamment :

  • Resource Owner : L'utilisateur final qui possède les données.
  • Client : L'application (frontend, mobile, service tiers) qui souhaite accéder aux ressources au nom du Resource Owner.
  • Authorization Server (AS) : Le serveur qui authentifie le Resource Owner, obtient son consentement, et émet des Access Tokens au Client.
  • Resource Server (RS) : Le serveur qui héberge les ressources protégées (votre API REST) et qui accepte ou rejette les requêtes basées sur les Access Tokens présentés par le Client.

Dans ce chapitre, nous nous concentrons sur la configuration de votre application Spring Boot pour qu'elle agisse en tant que Resource Server. Son rôle principal n'est pas d'authentifier directement les utilisateurs finaux (c'est le travail de l'AS), mais de valider les Access Tokens qu'elle reçoit et, si le token est valide, d'extraire les informations d'authentification et d'autorisation pour traiter la requête.

Cette approche est très courante dans les architectures microservices ou lorsque vous utilisez des fournisseurs d'identité externes (IdP) comme Keycloak, Okta, Auth0, ou même votre propre serveur d'autorisation basé sur Spring Authorization Server.

Dépendances et auto-configuration

Pour configurer votre application comme un Resource Server OAuth2, vous avez principalement besoin du starter suivant :



    org.springframework.boot
    spring-boot-starter-oauth2-resource-server



// build.gradle (Gradle)
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'

// Implique également implementation 'org.springframework.boot:spring-boot-starter-security'

Lorsque ce starter est présent, Spring Boot active l'auto-configuration pour un Resource Server. Il active également Spring Security avec une configuration par défaut qui s'attend à recevoir un Bearer Token (généralement un JWT) dans l'en-tête Authorization de chaque requête.

Validation des Tokens : JWT vs Introspection

Le Resource Server doit valider les tokens reçus. Deux stratégies principales existent :

  1. Validation locale de JWT (JSON Web Token) : Si l'Authorization Server émet des JWT signés, le Resource Server peut valider le token localement. Il doit :
    • Vérifier la signature du JWT en utilisant la clé publique de l'Authorization Server.
    • Valider les claims standard du JWT (issuer iss, audience aud, expiration exp).
    C'est l'approche la plus performante et la plus courante car elle n'implique pas d'appel réseau vers l'Authorization Server pour chaque requête.
  2. Introspection de Token Opaque : Si l'Authorization Server émet des tokens opaques (des chaînes aléatoires sans signification intrinsèque), le Resource Server doit contacter un endpoint d'introspection sur l'Authorization Server pour chaque requête reçue. L'AS répondra si le token est valide et fournira les métadonnées associées (utilisateur, scopes, etc.). Cette approche est moins performante car elle nécessite un appel réseau à chaque fois, mais elle permet une révocation immédiate des tokens côté AS.

Spring Boot facilite la configuration des deux approches via les propriétés de l'application.

Configuration pour la validation de JWT (Recommandé)

C'est la méthode privilégiée lorsque l'AS émet des JWT. La configuration la plus simple consiste à fournir l'URI de l'émetteur (issuer) de l'Authorization Server. Spring Security utilise cette URI pour découvrir automatiquement le point de terminaison JWK Set URI (qui expose les clés publiques de l'AS) via la spécification OpenID Connect Discovery.

# application.properties

# URI de l'émetteur (Authorization Server)
# Doit pointer vers l'endpoint OpenID Connect Discovery ou l'issuer de base
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://mon-auth-server.com/auth/realms/mon-realm

# Optionnel: Spécifier l'audience attendue (si l'AS inclut 'aud' dans le JWT)
# spring.security.oauth2.resourceserver.jwt.audiences=api://ma-super-api,account
# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          # URI de l'émetteur
          issuer-uri: https://mon-auth-server.com/auth/realms/mon-realm
          # Audiences attendues (liste)
          # audiences:
          #  - api://ma-super-api
          #  - account

Si la découverte automatique n'est pas possible ou souhaitée, vous pouvez fournir directement l'URI du JWK Set :

# Alternative: Spécifier directement l'URI JWK Set
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://mon-auth-server.com/auth/realms/mon-realm/protocol/openid-connect/certs

Avec cette configuration, Spring Security validera automatiquement la signature, l'issuer, l'audience (si configurée) et l'expiration de tout JWT présenté dans l'en-tête Authorization: Bearer ....

Configuration pour l'introspection de Token Opaque

Si vous travaillez avec des tokens opaques, vous devez configurer l'URI de l'endpoint d'introspection et les informations d'identification (client ID/secret) que le Resource Server utilisera pour s'authentifier auprès de l'Authorization Server lors de l'appel d'introspection.

# application.properties
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://mon-auth-server.com/auth/realms/mon-realm/protocol/openid-connect/token/introspect
spring.security.oauth2.resourceserver.opaquetoken.client-id=mon-resource-server-client
spring.security.oauth2.resourceserver.opaquetoken.client-secret=mon_secret_client
# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          introspection-uri: https://mon-auth-server.com/auth/realms/mon-realm/protocol/openid-connect/token/introspect
          client-id: mon-resource-server-client
          client-secret: mon_secret_client

Dans ce mode, pour chaque requête entrante, Spring Security contactera l'introspection-uri pour valider le token.

Fonctionnement interne et extraction des autorités

Une fois le starter et les propriétés configurés, Spring Security met en place les éléments nécessaires :

  1. Un filtre (BearerTokenAuthenticationFilter) intercepte les requêtes, cherche un token dans l'en-tête Authorization: Bearer ....
  2. Le token est passé à un AuthenticationProvider approprié (JwtAuthenticationProvider pour JWT, OpaqueTokenAuthenticationProvider pour l'introspection).
  3. Ce provider effectue la validation (locale pour JWT, distante pour introspection).
  4. Si le token est valide, le provider crée un objet Authentication (souvent JwtAuthenticationToken pour JWT) contenant les détails extraits du token (comme le `subject` ou d'autres claims).
  5. Le provider tente également d'extraire les autorités (rôles/permissions) à partir des claims du token (souvent le claim `scope` ou `scp` par défaut, ou un claim personnalisé comme `roles` ou `groups`). Par défaut, les scopes sont souvent mappés en autorités préfixées par `SCOPE_` (ex: `SCOPE_read`, `SCOPE_write`).
  6. L'objet Authentication avec ses autorités est placé dans le SecurityContextHolder.

Vous pouvez personnaliser la manière dont les autorités sont extraites du JWT en configurant un `JwtAuthenticationConverter` ou plus spécifiquement un `GrantedAuthoritiesConverter`.

// Exemple de configuration Java pour extraire les autorités d'un claim personnalisé 'roles'
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authz -> authz
            .requestMatchers("/api/admin/**").hasRole("ADMIN") // Vérifie ROLE_ADMIN
            .requestMatchers("/api/resource/**").hasAuthority("SCOPE_read") // Vérifie SCOPE_read
            .anyRequest().authenticated()
        )
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt
                // Configuration du converter pour extraire les autorités
                .jwtAuthenticationConverter(jwtAuthenticationConverter())
            )
        );
    return http.build();
}

JwtAuthenticationConverter jwtAuthenticationConverter() {
    // Converter pour extraire les autorités depuis un claim spécifique (ex: 'roles' ou 'groups')
    GrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
    // Si vous n'utilisez pas 'scope' ou 'scp', supprimez le préfixe par défaut
    // ((JwtGrantedAuthoritiesConverter) authoritiesConverter).setAuthorityPrefix(""); 
    // ((JwtGrantedAuthoritiesConverter) authoritiesConverter).setAuthoritiesClaimName("roles"); // Nom du claim

    // Version plus avancée pour mapper des claims spécifiques à des SimpleGrantedAuthority
    // GrantedAuthoritiesConverter authoritiesConverter = source -> {
    //     Collection roles = source.getClaimAsStringList("roles");
    //     if (roles == null) return Collections.emptyList();
    //     return roles.stream()
    //             .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
    //             .collect(Collectors.toList());
    // };
    
    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setGrantedAuthoritiesConverter(authoritiesConverter);
    return converter;
}

Appliquer les règles d'autorisation

Une fois que l'objet Authentication est correctement peuplé avec les autorités extraites du token, vous pouvez appliquer les règles d'autorisation exactement de la même manière que pour n'importe quelle autre méthode d'authentification dans Spring Security :

  • Via HttpSecurity dans la configuration du SecurityFilterChain :
    .requestMatchers("/api/data/**").hasAuthority("SCOPE_data:read")
    .requestMatchers("/api/admin/**":).hasRole("ADMIN")
  • Via les annotations de sécurité au niveau méthode :
    @PreAuthorize("hasAuthority('SCOPE_data:write')")
    @PreAuthorize("hasRole('MANAGER')")

Spring Security utilisera les autorités extraites du token validé pour évaluer ces règles.

Conclusion : Sécurité standard et stateless pour les API

Configurer votre application Spring Boot en tant que Resource Server OAuth2 est la méthode standard et recommandée pour sécuriser les API REST stateless. En s'appuyant sur un Authorization Server externe pour l'émission des tokens (souvent des JWT), le Resource Server se concentre sur la validation efficace de ces tokens et l'application des règles d'autorisation. Spring Security, avec le starter spring-boot-starter-oauth2-resource-server et sa configuration par propriétés ou Java, rend ce processus relativement simple et robuste, s'intégrant parfaitement avec le reste de l'écosystème Spring Security pour le contrôle d'accès.