Spring Webflux et CORS

Spring Webflux et CORS

1. Vue d'ensemble

Dans unprevious post, nous avons appris la spécification CORS (Cross-Origin Resource Sharing) et comment l'utiliser dans Spring.

Dans ce tutoriel rapide,we’ll set up a similar CORS configuration using Spring’s 5 WebFlux framework.

Tout d'abord, nous allons voir comment nous pouvons activer le mécanisme sur les API basées sur les annotations.

Ensuite, nous analyserons comment l'activer sur l'ensemble du projet en tant que configuration globale, ou en utilisant unWebFilter spécial.

2. Activation de CORS sur des éléments annotés

Spring fournit l'annotation@CrossOrigin pour activer les requêtes CORS sur les classes de contrôleur et / ou les méthodes de gestionnaire.

2.1. Utilisation de@CrossOrigin sur une méthode de gestionnaire de requêtes

Ajoutons cette annotation à notre méthode de requête mappée:

@CrossOrigin
@PutMapping("/cors-enabled-endpoint")
public Mono corsEnabledEndpoint() {
    // ...
}

Nous utiliserons unWebTestClient (comme nous l’avons expliqué dans la section «4. Test 'dethis post) pour analyser la réponse que nous obtenons de ce point de terminaison:

ResponseSpec response = webTestClient.put()
  .uri("/cors-enabled-endpoint")
  .header("Origin", "http://any-origin.com")
  .exchange();

response.expectHeader()
  .valueEquals("Access-Control-Allow-Origin", "*");

De plus, nous pouvons essayer une requête de contrôle en amont pour nous assurer que la configuration CORS fonctionne comme prévu:

ResponseSpec response = webTestClient.options()
  .uri("/cors-enabled-endpoint")
  .header("Origin", "http://any-origin.com")
  .header("Access-Control-Request-Method", "PUT")
  .exchange();

response.expectHeader()
  .valueEquals("Access-Control-Allow-Origin", "*");
response.expectHeader()
  .valueEquals("Access-Control-Allow-Methods", "PUT");
response.expectHeader()
  .exists("Access-Control-Max-Age");

L'annotation@CrossOrigin a la configuration par défaut suivante:

  • Autorise toutes les origines (cela explique la valeur '*' dans l'en-tête de la réponse)

  • Permet tous les en-têtes

  • Toutes les méthodes HTTP mappées par la méthode du gestionnaire sont autorisées.

  • Les informations d'identification ne sont pas activées

  • La valeur "max-age" est de 1800 secondes (30 minutes)

Cependant, l’une de ces valeurs peut être remplacée à l’aide des paramètres de l’annotation.

2.2. Utilisation de@CrossOrigin sur le contrôleur

Cette annotation est également prise en charge au niveau de la classe et affectera toutes ses méthodes.

Dans le cas où la configuration au niveau de la classe ne convient pas à toutes nos méthodes, nous pouvons annoter les deux éléments pour obtenir le résultat souhaité:

@CrossOrigin(value = { "http://allowed-origin.com" },
  allowedHeaders = { "example-Allowed" },
  maxAge = 900
)
@RestController
public class CorsOnClassController {

    @PutMapping("/cors-enabled-endpoint")
    public Mono corsEnabledEndpoint() {
        // ...
    }

    @CrossOrigin({ "http://another-allowed-origin.com" })
    @PutMapping("/endpoint-with-extra-origin-allowed")
    public Mono corsEnabledWithExtraAllowedOrigin() {
        // ...
    }

    // ...
}

3. Activation de CORS sur la configuration globale

Nous pouvons également définir une configuration CORS globale en remplaçant la méthodeaddCorsMappings() d'une implémentationWebFluxConfigurer.

De plus, l'implémentation a besoin de l'annotation@EnableWebFlux pour importer la configuration Spring WebFlux:

@Configuration
@EnableWebFlux
public class CorsGlobalConfiguration implements WebFluxConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {
        corsRegistry.addMapping("/**")
          .allowedOrigins("http://allowed-origin.com")
          .allowedMethods("PUT")
          .maxAge(3600);
    }
}

En conséquence, nous activons le traitement des demandes d’origine croisée pour ce modèle de chemin particulier.

La configuration par défaut est similaire à celle de@CrossOrigin, mais avec uniquement les méthodesGET,HEAD etPOST autorisées.

Nous pouvons également combiner cette configuration avec une configuration locale:

  • Pour les attributs à valeurs multiples, la configuration CORS résultante sera l’ajout de chaque spécification.

  • D'autre part, les valeurs locales auront la priorité sur les valeurs globales pour les valeurs à valeur unique

L'utilisation de cette approche n'est toutefois pas efficace pour les paramètres finaux fonctionnels.

4. Activation de CORS avec unWebFilter

La meilleure façon d'activer CORS sur les points de terminaison fonctionnels est d'utiliser unWebFilter.

Comme nous l'avons vuin this post, nous pouvons utiliserWebFilters pour modifier les demandes et les réponses, tout en conservant la mise en œuvre du point de terminaison intacte.

Spring fournit lesCorsWebFilter intégrés afin de gérer facilement les configurations d'origine croisée:

@Bean
CorsWebFilter corsWebFilter() {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowedOrigins(Arrays.asList("http://allowed-origin.com"));
    corsConfig.setMaxAge(8000L);
    corsConfig.addAllowedMethod("PUT");
    corsConfig.addAllowedHeader("example-Allowed");

    UrlBasedCorsConfigurationSource source =
      new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);

    return new CorsWebFilter(source);
}

Ceci est également efficace pour les gestionnaires annotés, mais il ne peut pas être combiné avec une configuration@CrossOrigin plus fine.

Nous devons garder à l’esprit que leCorsConfiguration n’a pas de configuration par défaut.

Ainsi, à moins d'indiquer tous les attributs pertinents, la mise en œuvre de CORS sera assez restrictive.

Un moyen simple de définir les valeurs par défaut consiste à utiliser la méthodeapplyPermitDefaultValues() sur l'objet.

5. Conclusion

En conclusion, nous avons appris avec de très brefs exemples sur la manière d'activer CORS sur notre service basé sur Webflux.

Nous avons vu différentes approches. Il ne nous reste donc plus qu'à analyser celle qui convient le mieux à nos besoins.

Nous pouvons trouver de nombreux exemples dansour Github repo, ainsi que des cas de test où nous analysons la plupart des cas limites concernant ce sujet.