Spring Security 6 : configurer un serveur de ressources OAuth2

Guide pratique pour configurer un Resource Server OAuth2 avec Spring Security 6. Validation JWT, configuration issuer, gestion des scopes et intégration Keycloak.

Configuration OAuth2 Resource Server avec Spring Security 6

Un serveur de ressources OAuth2 protège les API en validant les tokens JWT émis par un serveur d'autorisation externe comme Keycloak, Auth0 ou Okta. Contrairement à l'authentification JWT personnalisée où l'application génère ses propres tokens, le Resource Server délègue entièrement la gestion des identités à un Identity Provider (IdP) dédié.

Resource Server vs JWT custom

Un Resource Server ne génère jamais de tokens. Il les valide uniquement. Cette séparation des responsabilités renforce la sécurité et simplifie l'architecture en centralisant la gestion des identités.

Architecture OAuth2 Resource Server

Le flux OAuth2 implique trois acteurs principaux. Le client (application frontend ou mobile) obtient un token d'accès auprès du serveur d'autorisation. Il inclut ensuite ce token dans chaque requête vers le Resource Server. Le Resource Server valide le token en vérifiant sa signature avec la clé publique du serveur d'autorisation.

Cette architecture présente plusieurs avantages. Le Resource Server reste stateless car toutes les informations nécessaires sont contenues dans le token JWT. La validation s'effectue sans appel réseau vers l'IdP grâce au mécanisme de signature asymétrique. Plusieurs API peuvent partager le même serveur d'autorisation, simplifiant la gestion des utilisateurs.

text
┌──────────────┐     1. Login      ┌─────────────────────┐
│   Client     │ ───────────────►  │  Authorization      │
│   (SPA/App)  │                   │  Server (Keycloak)  │
│              │ ◄─────────────────│                     │
└──────────────┘   2. JWT Token    └─────────────────────┘
       │                                    │
       │ 3. Request + Bearer Token          │
       ▼                                    ▼
┌──────────────────────┐           ┌─────────────────────┐
│   Resource Server    │ ◄─────── │   JWKS Endpoint     │
│   (Spring Boot API)  │  4. Public Keys (cached)       │
└──────────────────────┘           └─────────────────────┘

Le Resource Server télécharge les clés publiques au démarrage et les met en cache, évitant ainsi les appels réseau à chaque requête.

Dépendances Maven pour OAuth2 Resource Server

La configuration d'un Resource Server nécessite deux dépendances spécifiques. Le starter oauth2-resource-server fournit l'infrastructure de base, tandis que oauth2-jose contient les classes pour décoder et valider les JWT.

xml
<!-- pom.xml -->
<dependencies>
    <!-- Support OAuth2 Resource Server -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>

    <!-- Spring Web pour les endpoints REST -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Validation des DTOs -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

Aucune dépendance supplémentaire comme JJWT n'est nécessaire. Spring Security utilise la bibliothèque Nimbus JOSE+JWT incluse dans spring-security-oauth2-jose pour la manipulation des tokens.

Configuration de base avec issuer-uri

La configuration minimale d'un Resource Server tient en deux lignes. Spring Security découvre automatiquement les endpoints de l'IdP via le protocole OpenID Connect Discovery.

yaml
# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          # URI du serveur d'autorisation
          # Spring récupère automatiquement les clés publiques via /.well-known/openid-configuration
          issuer-uri: https://keycloak.example.com/realms/myrealm

À partir de l'issuer-uri, Spring Security effectue une requête vers {issuer-uri}/.well-known/openid-configuration pour obtenir les métadonnées du serveur d'autorisation. Il extrait ensuite l'URL du JWKS (JSON Web Key Set) contenant les clés publiques utilisées pour valider les signatures des tokens.

SecurityConfig.javajava
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            // Désactive CSRF pour les API stateless
            .csrf(csrf -> csrf.disable())

            // Règles d'autorisation
            .authorizeHttpRequests(auth -> auth
                // Endpoints publics
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/actuator/health").permitAll()
                // Toutes les autres requêtes nécessitent un token valide
                .anyRequest().authenticated()
            )

            // Active la validation JWT OAuth2
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(Customizer.withDefaults())
            )

            // Mode stateless obligatoire pour les Resource Servers
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )

            .build();
    }
}

Cette configuration suffit pour protéger une API. Spring Security valide automatiquement la signature du token, vérifie les timestamps (exp, nbf, iat) et contrôle que le claim iss correspond à l'issuer-uri configuré.

Démarrage sans IdP

Par défaut, l'application échoue au démarrage si le serveur d'autorisation n'est pas accessible. Pour permettre un démarrage indépendant, configurez jwk-set-uri explicitement au lieu de issuer-uri.

Configuration avancée avec jwk-set-uri

Lorsque le serveur d'autorisation ne supporte pas OpenID Connect Discovery ou que l'application doit démarrer sans dépendance réseau vers l'IdP, la configuration explicite du JWKS est préférable.

yaml
# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          # URL directe vers le JWKS (clés publiques)
          jwk-set-uri: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs
          # Valeur attendue du claim "iss" dans les tokens
          issuer-uri: https://keycloak.example.com/realms/myrealm
          # Audiences autorisées (claim "aud")
          audiences:
            - my-api
            - account

Cette approche offre plus de contrôle. La validation de l'audience empêche l'utilisation de tokens destinés à d'autres services. Un token émis pour l'application frontend-app sera rejeté si l'API n'accepte que les audiences my-api et account.

SecurityConfig.javajava
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuerUri;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())

            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )

            // Configuration JWT personnalisée
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            )

            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )

            .build();
    }

    // Convertisseur personnalisé pour extraire les autorités du token
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
            new JwtGrantedAuthoritiesConverter();
        // Préfixe SCOPE_ pour les autorités (ex: SCOPE_read, SCOPE_write)
        grantedAuthoritiesConverter.setAuthorityPrefix("SCOPE_");
        // Claim contenant les scopes (standard OAuth2)
        grantedAuthoritiesConverter.setAuthoritiesClaimName("scope");

        JwtAuthenticationConverter jwtAuthenticationConverter =
            new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
            grantedAuthoritiesConverter
        );

        return jwtAuthenticationConverter;
    }
}

Le JwtAuthenticationConverter transforme les claims du token en objets GrantedAuthority utilisables dans les expressions de sécurité.

Prêt à réussir tes entretiens Spring Boot ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Extraction des rôles Keycloak

Keycloak structure les rôles différemment du standard OAuth2. Les rôles peuvent se trouver dans realm_access.roles (rôles du realm) ou resource_access.{client}.roles (rôles spécifiques au client). Un convertisseur personnalisé extrait ces informations.

KeycloakJwtAuthenticationConverter.javajava
@Component
public class KeycloakJwtAuthenticationConverter
    implements Converter<Jwt, AbstractAuthenticationToken> {

    private static final String REALM_ACCESS_CLAIM = "realm_access";
    private static final String RESOURCE_ACCESS_CLAIM = "resource_access";
    private static final String ROLES_CLAIM = "roles";

    @Value("${keycloak.client-id}")
    private String clientId;

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        // Combine les rôles du realm et du client
        Collection<GrantedAuthority> authorities = Stream.concat(
            extractRealmRoles(jwt).stream(),
            extractClientRoles(jwt).stream()
        ).collect(Collectors.toSet());

        // Retourne un token d'authentification avec les autorités extraites
        return new JwtAuthenticationToken(jwt, authorities, extractUsername(jwt));
    }

    // Extrait les rôles au niveau du realm
    private Collection<GrantedAuthority> extractRealmRoles(Jwt jwt) {
        Map<String, Object> realmAccess = jwt.getClaim(REALM_ACCESS_CLAIM);
        if (realmAccess == null) {
            return Collections.emptyList();
        }

        @SuppressWarnings("unchecked")
        List<String> roles = (List<String>) realmAccess.get(ROLES_CLAIM);
        if (roles == null) {
            return Collections.emptyList();
        }

        // Préfixe ROLE_ pour compatibilité avec hasRole()
        return roles.stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
            .collect(Collectors.toList());
    }

    // Extrait les rôles spécifiques au client
    private Collection<GrantedAuthority> extractClientRoles(Jwt jwt) {
        Map<String, Object> resourceAccess = jwt.getClaim(RESOURCE_ACCESS_CLAIM);
        if (resourceAccess == null) {
            return Collections.emptyList();
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> clientAccess =
            (Map<String, Object>) resourceAccess.get(clientId);
        if (clientAccess == null) {
            return Collections.emptyList();
        }

        @SuppressWarnings("unchecked")
        List<String> roles = (List<String>) clientAccess.get(ROLES_CLAIM);
        if (roles == null) {
            return Collections.emptyList();
        }

        return roles.stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
            .collect(Collectors.toList());
    }

    // Extrait le nom d'utilisateur du token
    private String extractUsername(Jwt jwt) {
        // Keycloak utilise "preferred_username" par défaut
        String username = jwt.getClaimAsString("preferred_username");
        if (username != null) {
            return username;
        }
        // Fallback sur le subject (généralement l'UUID de l'utilisateur)
        return jwt.getSubject();
    }
}

L'intégration dans la configuration de sécurité s'effectue via le paramètre jwtAuthenticationConverter.

SecurityConfig.javajava
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final KeycloakJwtAuthenticationConverter keycloakConverter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())

            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                // Protection par rôle Keycloak
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )

            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    // Utilise le convertisseur Keycloak
                    .jwtAuthenticationConverter(keycloakConverter)
                )
            )

            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )

            .build();
    }
}

Les rôles extraits sont maintenant utilisables avec les annotations @PreAuthorize("hasRole('ADMIN')") ou dans les expressions requestMatchers().hasRole().

Validation personnalisée des tokens

Des validations supplémentaires peuvent être nécessaires selon les besoins métier : vérification de claims personnalisés, validation de l'audience, ou contrôle de la durée de vie maximale.

CustomJwtValidator.javajava
@Component
public class CustomJwtValidator implements OAuth2TokenValidator<Jwt> {

    private static final OAuth2Error AUDIENCE_ERROR =
        new OAuth2Error("invalid_token", "Token audience is not valid", null);

    private static final OAuth2Error CUSTOM_CLAIM_ERROR =
        new OAuth2Error("invalid_token", "Required custom claim missing", null);

    @Value("${app.jwt.required-audience}")
    private String requiredAudience;

    @Value("${app.jwt.required-tenant-claim:tenant_id}")
    private String tenantClaimName;

    @Override
    public OAuth2TokenValidatorResult validate(Jwt jwt) {
        List<OAuth2Error> errors = new ArrayList<>();

        // Validation de l'audience
        if (!validateAudience(jwt)) {
            errors.add(AUDIENCE_ERROR);
        }

        // Validation d'un claim métier personnalisé
        if (!validateTenantClaim(jwt)) {
            errors.add(CUSTOM_CLAIM_ERROR);
        }

        if (!errors.isEmpty()) {
            return OAuth2TokenValidatorResult.failure(errors);
        }

        return OAuth2TokenValidatorResult.success();
    }

    private boolean validateAudience(Jwt jwt) {
        List<String> audiences = jwt.getAudience();
        return audiences != null && audiences.contains(requiredAudience);
    }

    private boolean validateTenantClaim(Jwt jwt) {
        // Vérifie la présence d'un claim métier requis
        String tenantId = jwt.getClaimAsString(tenantClaimName);
        return tenantId != null && !tenantId.isBlank();
    }
}

Le validateur personnalisé s'intègre dans la configuration du JwtDecoder.

JwtConfig.javajava
@Configuration
@RequiredArgsConstructor
public class JwtConfig {

    private final CustomJwtValidator customValidator;

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuerUri;

    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    private String jwkSetUri;

    @Bean
    public JwtDecoder jwtDecoder() {
        // Crée le décodeur avec l'URL du JWKS
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder
            .withJwkSetUri(jwkSetUri)
            .build();

        // Combine les validateurs par défaut avec le validateur personnalisé
        OAuth2TokenValidator<Jwt> defaultValidators =
            JwtValidators.createDefaultWithIssuer(issuerUri);

        OAuth2TokenValidator<Jwt> combinedValidator =
            new DelegatingOAuth2TokenValidator<>(defaultValidators, customValidator);

        jwtDecoder.setJwtValidator(combinedValidator);

        return jwtDecoder;
    }
}
Performance des validations

Les validations synchrones bloquent le thread de traitement. Pour des validations nécessitant des appels externes (base de données, services tiers), préférez un filtre asynchrone ou une vérification en aval.

Accès aux informations du token dans les contrôleurs

Spring Security fournit plusieurs méthodes pour accéder aux informations du token JWT dans les contrôleurs.

UserController.javajava
@RestController
@RequestMapping("/api/users")
public class UserController {

    // Injection directe du JWT via @AuthenticationPrincipal
    @GetMapping("/me")
    public ResponseEntity<UserInfoResponse> getCurrentUser(
        @AuthenticationPrincipal Jwt jwt
    ) {
        String userId = jwt.getSubject();
        String email = jwt.getClaimAsString("email");
        String username = jwt.getClaimAsString("preferred_username");
        List<String> roles = extractRoles(jwt);

        return ResponseEntity.ok(new UserInfoResponse(
            userId, email, username, roles
        ));
    }

    // Alternative avec JwtAuthenticationToken pour accéder aux autorités
    @GetMapping("/profile")
    public ResponseEntity<ProfileResponse> getProfile(
        JwtAuthenticationToken authentication
    ) {
        Jwt jwt = authentication.getToken();
        Collection<String> authorities = authentication.getAuthorities()
            .stream()
            .map(GrantedAuthority::getAuthority)
            .toList();

        return ResponseEntity.ok(new ProfileResponse(
            jwt.getSubject(),
            jwt.getClaimAsString("name"),
            authorities
        ));
    }

    // Accès aux scopes pour une logique métier conditionnelle
    @GetMapping("/data")
    @PreAuthorize("hasAuthority('SCOPE_read')")
    public ResponseEntity<DataResponse> getData(
        @AuthenticationPrincipal Jwt jwt
    ) {
        boolean canWrite = hasScope(jwt, "write");

        // Logique métier adaptée selon les permissions
        DataResponse response = buildDataResponse(jwt.getSubject(), canWrite);
        return ResponseEntity.ok(response);
    }

    private List<String> extractRoles(Jwt jwt) {
        Map<String, Object> realmAccess = jwt.getClaim("realm_access");
        if (realmAccess == null) {
            return Collections.emptyList();
        }
        @SuppressWarnings("unchecked")
        List<String> roles = (List<String>) realmAccess.get("roles");
        return roles != null ? roles : Collections.emptyList();
    }

    private boolean hasScope(Jwt jwt, String scope) {
        String scopes = jwt.getClaimAsString("scope");
        return scopes != null && scopes.contains(scope);
    }
}
UserInfoResponse.javajava
public record UserInfoResponse(
    String userId,
    String email,
    String username,
    List<String> roles
) {}

// ProfileResponse.java
public record ProfileResponse(
    String userId,
    String name,
    Collection<String> authorities
) {}

L'annotation @AuthenticationPrincipal évite de récupérer manuellement l'authentification depuis le SecurityContextHolder.

Gestion des erreurs d'authentification OAuth2

Une gestion d'erreurs spécifique améliore l'expérience développeur des consommateurs de l'API.

OAuth2SecurityExceptionHandler.javajava
@RestControllerAdvice
public class OAuth2SecurityExceptionHandler {

    private static final Logger log =
        LoggerFactory.getLogger(OAuth2SecurityExceptionHandler.class);

    // Token manquant ou format invalide
    @ExceptionHandler(AuthenticationException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorResponse handleAuthenticationException(AuthenticationException ex) {
        log.warn("Authentication failed: {}", ex.getMessage());

        return new ErrorResponse(
            "UNAUTHORIZED",
            "Authentication required. Provide a valid Bearer token.",
            Map.of("error", ex.getMessage())
        );
    }

    // Accès refusé malgré un token valide
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ErrorResponse handleAccessDeniedException(AccessDeniedException ex) {
        log.warn("Access denied: {}", ex.getMessage());

        return new ErrorResponse(
            "FORBIDDEN",
            "Insufficient permissions for this resource",
            null
        );
    }

    // Erreur spécifique OAuth2 (token expiré, signature invalide, etc.)
    @ExceptionHandler(OAuth2AuthenticationException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ErrorResponse handleOAuth2Exception(OAuth2AuthenticationException ex) {
        OAuth2Error error = ex.getError();
        log.warn("OAuth2 authentication error: {} - {}",
            error.getErrorCode(), error.getDescription());

        String message = switch (error.getErrorCode()) {
            case "invalid_token" -> "Token is invalid or expired";
            case "insufficient_scope" -> "Token does not have required scopes";
            default -> "Authentication failed";
        };

        return new ErrorResponse(
            error.getErrorCode().toUpperCase(),
            message,
            Map.of("details", error.getDescription())
        );
    }
}

// ErrorResponse.java
public record ErrorResponse(
    String code,
    String message,
    Map<String, String> details
) {}

Les codes d'erreur OAuth2 standardisés (invalid_token, insufficient_scope) facilitent le traitement côté client.

Configuration pour environnements multiples

La configuration varie selon l'environnement. En développement, un serveur Keycloak local peut être utilisé, tandis qu'en production, un IdP managé comme Auth0 prend le relais.

yaml
# application.yml (configuration commune)
app:
  jwt:
    required-audience: my-api

---
# application-dev.yml
spring:
  config:
    activate:
      on-profile: dev
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8180/realms/dev-realm

keycloak:
  client-id: my-api-dev

logging:
  level:
    org.springframework.security: DEBUG

---
# application-prod.yml
spring:
  config:
    activate:
      on-profile: prod
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${OAUTH2_ISSUER_URI}
          audiences: ${OAUTH2_AUDIENCES:my-api}

keycloak:
  client-id: ${KEYCLOAK_CLIENT_ID}

logging:
  level:
    org.springframework.security: WARN

En production, les valeurs sensibles proviennent de variables d'environnement ou d'un gestionnaire de secrets.

Tests d'intégration avec JWT mockés

Les tests d'intégration utilisent les utilitaires de Spring Security Test pour simuler des tokens JWT valides.

ResourceServerIntegrationTest.javajava
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class ResourceServerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldRejectRequestWithoutToken() throws Exception {
        mockMvc.perform(get("/api/users/me"))
            .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockJwtAuth(
        claims = @OpenIdClaims(sub = "user-123", preferredUsername = "john.doe"),
        authorities = {"ROLE_USER"}
    )
    void shouldAllowAuthenticatedUser() throws Exception {
        mockMvc.perform(get("/api/users/me"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.userId").value("user-123"))
            .andExpect(jsonPath("$.username").value("john.doe"));
    }

    @Test
    void shouldAllowAccessWithValidJwt() throws Exception {
        mockMvc.perform(get("/api/users/me")
                .with(jwt()
                    .jwt(jwt -> jwt
                        .subject("user-456")
                        .claim("preferred_username", "jane.doe")
                        .claim("email", "jane@example.com")
                    )
                    .authorities(new SimpleGrantedAuthority("ROLE_USER"))
                ))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.email").value("jane@example.com"));
    }

    @Test
    void shouldDenyAccessWithoutAdminRole() throws Exception {
        mockMvc.perform(get("/api/admin/users")
                .with(jwt()
                    .authorities(new SimpleGrantedAuthority("ROLE_USER"))
                ))
            .andExpect(status().isForbidden());
    }

    @Test
    void shouldAllowAdminAccess() throws Exception {
        mockMvc.perform(get("/api/admin/users")
                .with(jwt()
                    .authorities(new SimpleGrantedAuthority("ROLE_ADMIN"))
                ))
            .andExpect(status().isOk());
    }
}

L'utilitaire jwt() de spring-security-test crée des tokens mockés sans nécessiter de serveur d'autorisation.

SecurityTestConfig.javajava
@TestConfiguration
public class SecurityTestConfig {

    // JwtDecoder mocké pour les tests
    @Bean
    @Primary
    public JwtDecoder jwtDecoder() {
        return token -> {
            // Retourne un JWT mocké pour les tests
            return Jwt.withTokenValue(token)
                .header("alg", "RS256")
                .subject("test-user")
                .claim("scope", "read write")
                .build();
        };
    }
}

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Conclusion

La configuration d'un Resource Server OAuth2 avec Spring Security 6 centralise la validation des tokens et simplifie la sécurisation des API. Le serveur d'autorisation gère l'authentification et l'émission des tokens, tandis que l'API se concentre sur la validation et l'extraction des informations d'identité.

Checklist de déploiement :

  • ✅ Configuration issuer-uri ou jwk-set-uri selon les besoins
  • ✅ Validation de l'audience pour éviter l'utilisation de tokens détournés
  • ✅ Convertisseur personnalisé pour extraire les rôles Keycloak
  • ✅ Gestion des erreurs OAuth2 avec codes standardisés
  • ✅ Tests d'intégration avec tokens mockés
  • ✅ HTTPS obligatoire entre le client et le Resource Server
  • ✅ Configuration différenciée par environnement
  • ✅ Logging des tentatives d'authentification échouées

Cette architecture s'intègre naturellement dans un écosystème de microservices où plusieurs API partagent le même Identity Provider.

Tags

#spring security
#oauth2
#resource server
#jwt
#spring boot

Partager

Articles similaires