CORS avec Symfony, jQuery, FOSRestBundle et NelmioCorsBundle

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

TL;DR: Modifier (avant de lire l'ensemble de la chose):

Ce problème est résolu parce que j'ai fait une erreur très stupide dans mon code, pas liée à CORS ou quoi que ce soit.

Si vous voulez lire cette question de toute façon, il suffit de noter qu'il a un travail de la SCRO de configuration, si vous voulez prendre une bonne exemple.

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

(fin de la note)

Situation

j'ai une application Symfony multi-domaines, et la partie arrière met à jour les données avec des services web, pas des formes classiques, pour certaines raisons.

fin: fin.mydomain.dev Domaine Webservices: api.mydomain.dev

j'utilise jQuery pour faire des appels AJAX à ces services web, et si je veux modifier ou créer objets, j'envoie aussi des requêtes AJAX, les objets sont fusionnés avec des entités doctrinales et ont persisté.

je me bats depuis un an pour faire des requêtes GET, PUT, POST et DELETE qui fonctionneraient correctement sur cette application, et juste parce qu'elles sont sur des domaines différents, je suis forcé de me configurer CORS sur mes différents environnements.

Setup

Tous les jQuery AJAX demande ressembler à ceci:

ajaxObject = {
    url: 'http://api.mydomain.dev/' + uri,
    type: method, // Can be GET, PUT, POST or DELETE only
    dataType: 'json',
    xhrFields: {
         withCredentials: true
    },
    crossDomain: true,
    contentType: "application/json",
    jsonp: false,
    data: method === 'GET' ? data : JSON.stringify(data) // Here, "data" is ALWAYS containing a plain object. If empty, it equals to "{}"
};

// ... Add callbacks depending on requests

$.ajax(ajaxObject);

derrière, les routes sont gérées avec Symfony.

CORS configuration, j'utilise NelmioCorsBundle avec cette configuration:

nelmio_cors:
    paths:
       "^/":
          allow_credentials: true
          origin_regex: true
          allow_origin:
              - "^(https?://)?(back|api).mydomain.dev/?"
          allow_headers: ['Origin','Accept','Content-Type']
          allow_methods: ['POST','GET','DELETE','PUT','OPTIONS']
          max_age: 3600
          hosts:
              - "^(https?://)?(back|api).mydomain.dev/?"

le contrôleur utilisé s'étend FOSRestBundle ' S one, a une certaine sécurité (par exemple, ne peut pas poster/mettre/supprimer quand vous n'avez pas le rôle correct), peut mettre à jour des objets et ne renvoie que des données JSON (il y a un écouteur pour cela).

comportement Idéal

Idéalement, I voulez ceci:

  1. Run the jQuery AJAX POST / PUT / DELETE request
  2. il doit envoyer une demande D'OPTIONS avec tous les en-têtes CORS
  3. NelmioCorsBundle devrait renvoyer les en-têtes CORS corrects acceptant la requête à effectuer, même avant d'exécuter n'importe quel contrôleur dans l'application (fait par l'écouteur de requêtes du bundle)
  4. si elle est acceptée, la requête HTTP appropriée est envoyée au contrôleur avec toutes les données de la requête sous forme de chaîne JSON sérialisée (dans le request payload) , et Symfony le récupère et interprète la chaîne JSON correcte comme un objet array
  5. le controller obtient alors les données, fait son travail, et renvoie un application/json réponse.

Problème

mais ici, j'ai essayé les combinaisons de maaaaany, et je n'arrive pas à les faire marcher.

Le point n ° 3 et 4 échec, totalement ou partiellement.

Essayé solutions de contournement

  1. Quand Je ne pas sérialisez le dataJSON.stringify (voir Setup part ci-dessus), la requête D'OPTIONS est envoyée, mais FOSRestBundle envoie un BadRequestHttpException en disant Invalid json message received, et c'est tout à fait normal car la "demande de charge utile" (comme dans les outils de développement de Chrome) est le classique application/x-www-form-urlencoded le contenu, même si j'ai spécifié contentType: "application/json" dans la requête AJAX de jQuery, alors qu'il devrait s'agir D'un JSON sérialisé chaîne.

  2. cependant, si je sérialisez le data var, Le "request payload" est valide, mais la requête OPTIONS n'est pas envoyée, ce qui fait que toute la requête est rejetée en raison de l'absence d'acceptation de la part de la CORS.

  3. Si je remplace contentType: "application/json"application/x-www-form-urlencoded ou multipart/form-data, et ne sérialisez pas les données, alors la charge utile request est valide, mais la requête OPTIONS n'est pas envoyée. Il devrait être normal, comme expliqué en jQuery docscontentType paramètre:

    Remarque: pour les requêtes inter-domaines, le fait de définir le type de contenu à autre chose que application/x-www-form-urlencoded, multipart/form-data, ou text/plain va déclencher le navigateur pour envoyer une requête d'OPTIONS pré-Light au serveur.

    mais alors, comment envoyer une demande correcte CORS?

Questions

  • Où ce problème vient-il?
  • Est-ce un problème avec mon installation? Avec jQuery?
  • avec AJAX lui-même?
  • Quelles peuvent être les différentes solutions pour résoudre ce fichu problème?

d'édition (après les commentaires)

2015-06-29 17: 39

Conditions:

en AJAX, le contentType est réglé sur application/json. data l'option est définie à une chaîne JSON sérialisée.

contrôle en amont En-têtes de REQUÊTE

OPTIONS http://api.mydomain.dev/fr/object/1 HTTP/1.1
Access-Control-Request-Method: POST
Origin: http://back.mydomain.dev
Access-Control-Request-Headers: accept, content-type
Referer: http://back.mydomain.dev/...

headers de réponse avant le vol

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600
Access-Control-Allow-Origin: http://back.mydomain.dev

POST en-têtes de REQUÊTE (après contrôle en amont)

POST http://api.mydomain.dev/fr/object/1 HTTP/1.1
Origin: http://back.mydomain.dev
Content-Type: application/json
Referer: http://back.mydomain.dev/...
Cookie: mydomainPortal=v5gjedn8lsagt0uucrhshn7ck1
Accept: application/json, text/javascript, */*; q=0.01

Demande de charge utile (raw): {"json":{"id":1,"name":"object"}}

doit être noté que cela génère une erreur javascript:

XMLHttpRequest ne peut pas charger <!--168 http://api.mydomain.dev/fr/object / 1. Aucun en-tête' Access-Control-Allow-Origin ' n'est présent sur la ressource demandée. Origine 'http://back.mydomain.dev ' est donc interdit d'accès.

POST headers de réponse (after preflight)

HTTP/1.1 200 OK
Date: Mon, 29 Jun 2015 15:35:07 GMT
Server: Apache/2.4.12 (Unix) OpenSSL/1.0.2c Phusion_Passenger/5.0.11
Keep-Alive: timeout=5, max=91
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

2015-06-29 17: 39

j'ai aussi essayé d'utiliser JavaScript vanille pour faire la XMLHttpRequest, le problème est toujours le même:

var xhr = new XMLHttpRequest;
xhr.open('POST', 'http://api.mydomain.dev/fr/objects/1', true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.withCredentials = true; // Sends cookie
xhr.onreadystatechange = function(e) {
    // A simple callback
    console.info(JSON.parse(e.target.response));
};
// Now send the request with the serialized payload:
xhr.send('{"id":1,"name":"Updated test object"}');

ensuite, le navigateur envoie une requête D'OPTIONS:

OPTIONS http://api.mydomain.dev/fr/objects/1 HTTP/1.1
Connection: keep-alive
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: http://back.mydomain.dev

qui renvoie la bonne réponse les en-têtes:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: origin, accept, content-type
Access-Control-Max-Age: 3600
Access-Control-Allow-Origin: http://back.mydomain.dev

mais ensuite, le navigateur envoie la requête POST sans en-tête " Access-Control -*".

20
demandé sur Alex Rock 2015-06-29 16:48:35

1 réponses

en Fait, mon installation a bien fonctionné.

Absolument parfaitement, si je peux me permettre.

C'était juste une sorte de... Stupidité.

exit; était caché dans un fichier PHP.

désolé pour la gêne. Je suppose que c'est une sorte de record dans un texte rapport qualité/sur.

Edit: j'espère toujours que ce numéro peut être un bon exemple d'un projet Symfony entièrement compatible CORS.

12
répondu Alex Rock 2015-07-01 10:01:46