使用 Symfony、jQuery、FOSRestBundle 和 NelmioCorsBundle 的 CORS

CORS with Symfony, jQuery, FOSRestBundle and NelmioCorsBundle

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
TL;DR:编辑(阅读全文之前):

This issue is solved because I made a very stupid mistake in my code, not related to CORS or anything.
If you want to read this issue anyway, just note that it has a working CORS configuration, if you might want to take a good example.

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

(注释结束)

情况

我有一个 multi-domain Symfony 应用程序,由于某些原因,back-end 部分使用 web-services 而非经典形式更新数据。

Back-end: back.mydomain.dev Web 服务域:api.mydomain.dev

我正在使用 jQuery 对这些网络服务进行 AJAX 调用,如果我想修改或创建 object,我也会发送 AJAX 请求, objects 与 Doctrine 实体合并并持续存在。

我整整一年都在努力让 GET、PUT、POST 和 DELETE 请求能够在这个应用程序上正常工作,只是因为它们在不同的域中,我被迫设置CORS 在我的不同环境中。

设置

所有 jQuery AJAX 请求如下所示:

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);

后面,路由是用 Symfony 管理的。

对于 CORS 配置,我使用 NelmioCorsBundle 和此配置:

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/?"

使用的控制器是FOSRestBundle的扩展,具有一定的安全性(例如,当你没有正确的角色时,不能POST/PUT/DELETE),可以更新object s 和 returns 只有 JSON 数据(有一个侦听器)。

理想行为

理想情况下,我想要这个:

  1. 运行 jQuery AJAX POST/PUT/DELETE 请求
  2. 它必须发送带有所有 CORS 的 OPTIONS 请求 headers
  3. NelmioCorsBundle 应该 return 正确的 CORS headers 接受要完成的请求,甚至在 运行 应用程序内的任何控制器(由捆绑包的请求侦听器创建)之前
  4. 如果被接受,正确的 HTTP 请求将作为序列化的 JSON 字符串(在请求负载中)将所有请求数据发送到控制器,Symfony 检索它并解释正确的 JSON字符串作为数组 object
  5. 控制器然后获取数据,执行它的操作,然后 return 一个 application/json 响应。

问题

但是在这里,我尝试了 maaaaany 组合,但我似乎无法让它发挥作用。

n°3 和 4 完全或部分失败。

已尝试解决方法

  1. 当我JSON.stringify序列化data时(参见设置 上面的部分),发送了 OPTIONS 请求,但是 FOSRestBundle 发送了一个 BadRequestHttpExceptionInvalid json message received,这是完全正常的,因为 "request payload"(如 Chrome 中所见开发人员工具)是经典的 application/x-www-form-urlencoded 内容,即使我在 jQuery AJAX 请求中指定了 contentType: "application/json",而它应该是序列化的 JSON 字符串。

  2. 但是,如果我序列化data var,"request payload"是有效的,但是OPTIONS请求没有发送,由于缺少 CORS 接受,导致整个请求失败。

  3. 如果我将contentType: "application/json"替换为application/x-www-form-urlencodedmultipart/form-data,并且不序列化数据,则请求有效负载有效,但OPTIONS请求未发送。应该也是正常的,在contentType参数下解释in jQuery's docs

    Note: For cross-domain requests, setting the content type to anything other than application/x-www-form-urlencoded, multipart/form-data, or text/plain will trigger the browser to send a preflight OPTIONS request to the server.

    但是,如何发送正确的 CORS 请求?

问题

编辑(评论后)

2015-06-29 17:39

条件:

在AJAX中,contentType设置为application/jsondata 选项设置为序列化的 JSON 字符串。

预检请求headers

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

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 REQUEST headers(预检后)

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

请求负载(原始): {"json":{"id":1,"name":"object"}}

必须注意,这会引发 javascript 错误:

XMLHttpRequest cannot load http://api.mydomain.dev/fr/object/1. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://back.mydomain.dev' is therefore not allowed access.

POST 响应 header 秒(预检后)

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

我也试过使用 vanilla javascript 来制作 XMLHttpRequest,问题还是一样:

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"}');

然后,浏览器发送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

哪个return是正确的回答header:

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 请求时没有任何 "Access-Control-*" header.

实际上,我的设置运行良好。
绝对完美,如果我可以说的话。

这只是某种...愚蠢。

exit; 被隐藏在 PHP 文件的深处。

抱歉打扰了。我想这是 SO 上 text/quality 比率的记录。

编辑:我仍然希望这个问题能够成为一个完全兼容 CORS 的 Symfony 项目的适当示例。