Pages

mercredi 8 mai 2013

CORS norme W3C alternative à JSONP

Quand on met en place une application web faisant appel à des webservices REST distants on passe généralement par la méthode javascript XMLHttpRequest (XHR) implémentée par le navigateur internet. Pour renforcer la sécurité la plupart des navigateurs interdisent les requêtes dites “cross-domain” pour éviter les attaques de type XSRF (Cross-site request forgery).

Le Cross-origin resource sharing (CORS) permet de résoudre ce problème (normalisation en cours auprès du W3C) en définissant la manière dont le navigateur et le serveur doivent interagir pour déterminer s'il faut ou non autoriser une demande “cross-domain”.


Le principe derrière CORS est d’ajouter des informations dans l’entête HTTP. Les principaux navigateurs ont mis en place ce mécanisme : Firefox >3.x, Chrome >3, Safari >4, IE>10 (support partiel pour IE8 et 9)

CORS est une alternative sensée répondre aux limites de JSONP, technique de communication utilisée pour appeler via Javascript des services REST distant renvoyant des données en JSON. En JSONP (JSON with padding) on injecte un callback dans le JSON et la réponse est directement exécutée en Javascript dans le navigateur.

Côté client regardons comment ce manifeste la mise en place de CORS, au niveau des headers HTTP. On peut distinguer des propriétés comme OriginAccess-Control-Request-Headers, Access-Control-Request-Method



Côté serveur les implémentation JAX-RS (JSR311) ne proposent pas tous une implémentation de la norme CORS qui n’est pas encore finalisée. Vous pouvez utiliser Apache-CXF mais si vous êtes un utilisateur de Jersey vous n’avez pas encore de support. Pour combler ce manque vous allez devoir revoir tous les réponses renvoyées au client. Rassurez vous ceci est très simple

Nous commençons par créer un filter implémentant l’interface com.sun.jersey.spi.container.ContainerResponseFilter

public class ResponseCorsFilter implements ContainerResponseFilter {
    @Override
    public ContainerResponse filter(ContainerRequest req, 
                    ContainerResponse contResp) {
        Response.ResponseBuilder resp = Response.fromResponse(contResp.getResponse());
        resp.header("Access-Control-Allow-Origin", "*")
                .header("Access-Control-Allow-Methods", 
                        "GET, POST, PUT, DELETE, OPTIONS");
        String reqHead = req.getHeaderValue("Access-Control-Request-Headers");
        if(!Strings.isNullOrEmpty(reqHead)){
            resp.header("Access-Control-Allow-Headers", reqHead);
        }
        contResp.setResponse(resp.build());
        return contResp;
    }
}

Au niveau du web.xml il ne nous reste plus qu’à configurer ce filter

   <servlet>
        <description>Jersey Spring Web Application</description>
        <servlet-name>jersey-servlet</servlet-name>
        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
            <param-value>com.myentreprise.rest.ResponseCorsFilter</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>jersey-servlet</servlet-name>
        <url-pattern>/synapi/*</url-pattern>
    </servlet-mapping>


Voila vous n'avez plus qu'à tester.

Si vous utilisez Tomcat supérieur ou égal à la version 7.0.41 vous pouvez utiliser un servlet filter comme ceci est expliqué dans cet article.

Personnellement je suis tombé sur ce problème lorsque je testais les appels REST dans une application AngularJS ($http en erreur) et l'erreur dans la console Javascript était la suivante XMLHttpRequest cannot load http://localhost:8080/test/info. Origin http://localhost:9000 is not allowed by Access-Control-Allow-Origin.


Aucun commentaire:

Enregistrer un commentaire

Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.