Spring Security 6 et OAuth 2.0 : implémentation d'un système d'authentification pour entreprises

Introduction à Spring Security 6 et OAuth 2.0

Spring Security 6, la version majeure récente du framework, offre une prise en charge complète pour OAuth 2.1 et OpenID Connect 1.0. Ce guide pratique détaillle la mise en place d'un serveur d'autorisation OAuth 2.0 et d'un serveur de ressources pour sécuriser les API.

Conception de l'architecture du projet


┌──────────────────┐      ┌──────────────────┐      ┌──────────────────┐
│  Application     │──────▶│  Serveur         │──────▶│  Serveur de      │
│  Cliente         │◀──────│  d'Autorisation  │◀──────│  Ressources      │
│  (Frontend)      │       │  OAuth 2.0       │       │  (API REST)      │
└──────────────────┘       └──────────────────┘       └──────────────────┘
         │                          │                          │
         └──────────────────────────┴──────────────────────────┘
                    Flux d'authentification par JWT

Configuration du serveur d'autorisation

2.1 Dépendences nécessaires


<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-authorization-server</artifactId>
        <version>1.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2.2 Configuration de base du serveur d'autorisation


@Configuration
@EnableWebSecurity
public class ConfigServeurAutorisation {
    @Bean @Order(1)
    public SecurityFilterChain filtreChaineAutorisation(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
        return http.build();
    }

    @Bean @Order(2)
    public SecurityFilterChain filtreChaineSecuriteParDefaut(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth.requestMatchers("/connexion").permitAll().anyRequest().authenticated())
            .formLogin(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public RegisteredClientRepository entrepotClientsEnregistres() {
        RegisteredClient clientTest = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("mon-client").clientSecret("{noop}mon-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .redirectUri("http://127.0.0.1:8080/connexion/oauth2/code/mon-client")
            .scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).scope("api.lecture").scope("api.ecriture")
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
        return new InMemoryRegisteredClientRepository(clientTest);
    }

    @Bean
    public JWKSource<SecurityContext> sourceJWK() {
        RSAKey cleRSA = genererCleRSA();
        return (selecteur, contexte) -> selecteur.select(new JWKSet(cleRSA));
    }

    private static RSAKey genererCleRSA() {
        try {
            KeyPair paireCles = KeyPairGenerator.getInstance("RSA").generateKeyPair();
            return new RSAKey.Builder((RSAPublicKey) paireCles.getPublic())
                .privateKey((RSAPrivateKey) paireCles.getPrivate()).keyID(UUID.randomUUID().toString()).build();
        } catch (Exception e) { throw new IllegalStateException(e); }
    }
}

Configuration du serveur de ressources

3.1 Fichier de configuration


spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9000
          jwk-set-uri: http://localhost:9000/.well-known/jwks.json

3.2 Définition de la sécurité pour les ressources


@Configuration
@EnableWebSecurity
public class ConfigServeurRessources {
    @Bean
    public SecurityFilterChain filtreChaineRessources(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/public/**").permitAll()
            .requestMatchers("/api/admin/**").hasRole("ADMINISTRATEUR")
            .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }
}

3.3 Exemplle d'API protégée


@RestController
@RequestMapping("/api")
public class ControleurAPI {
    @GetMapping("/utilisateur/profil")
    public Map<String, Object> profilUtilisateur(@AuthenticationPrincipal Jwt jeton) {
        return Map.of("nom", jeton.getSubject(), "permissions", jeton.getClaimAsStringList("scope"));
    }

    @GetMapping("/admin/donnees")
    @PreAuthorize("hasRole('ADMINISTRATEUR')")
    public String donneesAdmin() { return "Données réservées aux administrateurs"; }
}

Test du flux client

4.1 Obtention d'un jeton d'accès


curl -X POST http://localhost:9000/oauth2/token -H "Content-Type: application/x-www-form-urlencoded" -u "mon-client:mon-secret" -d "grant_type=client_credentials" -d "scope=api.lecture"

# Réponse attendue : {"access_token":"eyJ...","token_type":"Bearer","expires_in":300}

4.2 Appel d'une API sécurisée


curl -H "Authorization: Bearer eyJ..." http://localhost:8081/api/utilisateur/profil
# Réponse attendue : {"nom":"admin","permissions":["openid","profile","api.lecture"]}

Recommandations techniques

  • HTTPS obligatoire : Utiliser TLS dans tous les environnements de production.
  • Durée de validité des jetons : Limiter les access_token à 5-15 minutes.
  • Utilisation de PKCE : Implémenter PKCE pour les applications mobiles et SPA.
  • Gestion des clés : Stocker les clés cryptographiques via des solutions comme HashiCorp Vault.

Étiquettes: Spring Security OAuth 2.0 JWT REST API Java

Publié le 6 juillet à 00h40