Spring, Rest et cross domain
De quoi on parle ?
Le cross domain (croisement de domaine) est un principe qui consiste à faire communiquer deux domaines entre eux. Par exemple, le domaine source-domain.fr souhaite envoyer des données au domaine cible-domain.com et inversement.
Les requêtes HTTP dîtes cross-domain visent donc à récupérer des ressources (scripts, images, feuilles de styles, etc..) localisées sur un serveur d’un domaine différent. Historiquement ces requêtes étaient désactivés pour des raisons évidente de sécurité, et elles étaient donc soumises à la politique de même origine concernant les domaines.
Ce principe prévalait évidement pour les requêtes de type Ajax notamment à travers l’object XmlHttpRequest. La W3C a souhaité faire évoluer ce mécanisme et à mis en place le principe de Cross Origin Resource Sharing (CORS) qui consiste a offrir la possibilité aux serveur de contrôler les accès cross domain.
Le standard de partage de ressources d’origines croisées fonctionne grâce à l’ajout d’entêtes HTTP qui permettent aux serveurs de décrire l’ensemble des origines permises. C’est ensuite le navigateur qui lit cette information et en fait l’usage adéquat. Le navigateur fait d’abord une pré requête afin dé vérifier les options possibles à travers une requête HTTP OPTIONS. Une fois l’approbation du server effectué, la “vraie” requête est effectuée (“POST”,”GET”,etc…).
Les différents headers HTTP à ajouter coté serveur sont les suivants:
Access-Control-Allow-Origin
Le serveur indique quels domaines sont autorisés
1 |
Access-Control-Allow-Origin: <origin> | * |
Access-Control-Allow-Headers
Le serveur indique quels headers sont autorisés
1 |
Access-Control-Allow-Headers: POST, DELETE, HEAD, GET |
Access-Control-Expose-Headers
Le serveur indique la liste des headers auxquels le navigateur peut accéder
1 |
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header |
Access-Control-Allow-Methods
Le serveur indique les méthodes HTTP autorisées pour accéder à la ressource.
1 |
Access-Control-Allow-Methods: <method> | * |
Nous allons voir comment ajouter ces différents informations dans Spring.
L’environnement technique
Les composants techniques sont les suivants:
- Java 1.7
- Spring MVC v4.0.3
- Spring Boot v1.1.0
Rien ne vaut l’exemple
A) Exemple avec un filter (spring < 4.2) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package hello; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; public class CORSFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request= (HttpServletRequest) req; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); response.setHeader("Access-Control-Expose-Headers", "x-requested-with"); chain.doFilter(req, res); } } |
Puis d’ajouter notre filtre au ServletContext:
1 2 3 4 5 6 7 |
@Configuration public class ServletConfigurer implements ServletContextInitializer { @Override public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException { servletContext.addFilter("corsFilter", new CORSFilter()); } } |
B) Exemple avec l’annotation @CrossOrigin (spring >= 4.2)
Depuis la version 4.2, Spring à introduit une nouvelle annotation facilitant la gestion du cross origine: @CrossOrigin. Elle peut être placé sur la classe Controller (ajoutant ainsi automatiquement les autorisations sur la route principale), sur les méthodes ou les deux afin de pouvoir surcharger le comportement par défaut.
Par défaut, l’annotation autorise toutes les origines pour la route ainsi que les méthodes http GET,HEAD et POST. Placée sur une classe controller, elle permet de définir la politique de même origine pour une route particulière et sera appliqué à toutes les sous routes.
1 2 3 4 5 6 7 8 9 10 11 |
@CrossOrigin @RestController @RequestMapping("/books") public class BookController { @RequestMapping("/books/{id}") public Book get(@PathVariable Long id) { } @RequestMapping(method = RequestMethod.DELETE, value = "/books/{id}") public void delete(@PathVariable Long id) { } } |
Il est bien sur possible de surcharger la politique sur une des méthodes de la classe Controller, afin d’effectuer une surcharge sur la configuration, par exemple si l’on souhaite autoriser une méthode en particulier et en exclure une autre.
1 2 3 4 5 6 7 8 9 10 11 |
@RestController @RequestMapping("/books") public class BookController { @CrossOrigin @RequestMapping("/books/{id}") public Book get(@PathVariable Long id) { } @RequestMapping(method = RequestMethod.DELETE, value = "/books/{id}") public void delete(@PathVariable Long id) { } } |
Dans l’exemple ci dessus, seule la méthode GET /books/:id autorise une politique de même origine contraitement à la méthode DELETE /books/:id
Il est possible de configurer de manière globale la configuration du cross origin qui s’appliquera à l’ensemble des routes de l’application, evitant ainsi d’avoir à poser l’annotation @CrossOrigin sur l’ensemble des Controller:
1 2 3 4 5 6 7 8 |
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } } |
Il est evidemment possible d’etre beaucoup plus précis:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://redfroggy.fr") .allowedMethods("PUT", "DELETE", "PATCH") .allowedHeaders("appKey", "Authorization") .exposedHeaders("Authorization"); } } |
C) @CrossOrigin + Spring Boot ?
Le support CORS à été ajouté dans la version 1.3 de Spring Boot. Il est toujours possible d’utiliser l’annotation @CrossOrigin en complément mais la configuration globale est en revanche légèrement différente:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Configuration public class MyConfiguration { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**"); } }; } } |