CORS与Symfony,jQuery,FOSRestBundle和NelmioCorsBundle

Ale*_*ock 20 ajax jquery cors symfony fosrestbundle

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️TL
; DR:编辑(在阅读整篇文章之前):

这个问题解决了,因为我在我的代码中犯了一个非常愚蠢的错误,与CORS或任何东西无关.
如果你想要阅读这个问题,请注意它有一个有效的CORS配置,如果你想要一个很好的例子.

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

(注意结束)

情况

我有一个多域Symfony应用程序,后端部分由于某些原因使用Web服务而不是经典表单更新数据.

后端:back.mydomain.dev Webservices域:api.mydomain.dev

我使用jQuery对这些web服务进行AJAX调用,如果我想修改或创建对象,我也发送AJAX请求,对象与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);
Run Code Online (Sandbox Code Playgroud)

在后面,路线由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/?"
Run Code Online (Sandbox Code Playgroud)

使用的控制器是扩展FOSRestBundle的一个,有一些安全性(例如,当你没有正确的角色时不能POST/PUT/DELETE),可以更新对象并只返回JSON数据(有一个监听器) .

理想的行为

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

  1. 运行jQuery AJAX POST/PUT/DELETE请求
  2. 它必须发送带有所有CORS头的OPTIONS请求
  3. NelmioCorsBundle应该返回正确的CORS标头,接受要完成的请求,甚至在运行app内的任何控制器之前(由bundle的请求监听器做出)
  4. 如果接受,则将正确的HTTP请求发送到控制器,并将所有请求数据作为序列化JSON字符串(在请求有效负载中),并且Symfony检索它并将正确的JSON字符串解释为数组对象
  5. 然后控制器获取数据,执行其操作并返回application/json响应.

问题

但在这里,我尝试了maaaaany组合,我似乎无法使它工作.

n°3和4点完全或部分失效.

尝试过变通方法

  1. 当我没有序列化datawith时JSON.stringify(参见上面的Setup部分),OPTIONS请求被发送,但是FOSRestBundle发送了一个BadRequestHttpException说法Invalid json message received,这是完全正常的,因为"请求有效载荷"(如Chrome的开发者工具中所见)是经典application/x-www-form-urlencoded内容即使我contentType: "application/json"在jQuery AJAX请求中指定,它应该是一个序列化的JSON字符串.

  2. 但是,如果我对var 进行序列化data,则"请求有效负载"有效,但OPTIONS请求不会发送,因为缺少CORS接受而导致整个请求失败.

  3. 如果我contentType: "application/json"application/x-www-form-urlencoded或替换multipart/form-data,并且不对数据进行序列化,则请求有效负载有效,但不发送OPTIONS请求.它也应该是正常的,正如jQuerycontentType参数下的文档中所解释的那样:

    注意:对于跨域请求,将内容类型设置为application/x-www-form-urlencoded,multipart/form-data或text/plain以外的任何内容将触发浏览器向服务器发送预检OPTIONS请求.

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

问题

  • 这个问题来自哪里?
  • 这是我的设置问题吗?用jQuery?
  • 有了AJAX本身吗?
  • 解决这个该死的问题有哪些不同的解决方案?

编辑(评论后)

2015-06-29 17:39

条件:

在AJAX中,contentType设置为application/json.该data选项设置为序列化的JSON字符串.

预检REQUEST标头

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/...
Run Code Online (Sandbox Code Playgroud)

预检RESPONSE标头

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
Run Code Online (Sandbox Code Playgroud)

POST REQUEST标题(预检后)

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
Run Code Online (Sandbox Code Playgroud)

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

必须注意,这会抛出一个javascript错误:

XMLHttpRequest无法加载http://api.mydomain.dev/fr/object/1.请求的资源上不存在"Access-Control-Allow-Origin"标头.因此,不允许来源' http://back.mydomain.dev '访问.

POST RESPONSE标题(预检后)

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
Run Code Online (Sandbox Code Playgroud)

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"}');
Run Code Online (Sandbox Code Playgroud)

然后,浏览器发送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
Run Code Online (Sandbox Code Playgroud)

返回正确的响应标头:

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
Run Code Online (Sandbox Code Playgroud)

但是,浏览器发送POST请求时没有任何"Access-Control-*"标头.

Ale*_*ock 13

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

这只是某种......愚蠢.

一个exit;隐藏在PHP文件的深处.

对不起烦恼.我想这是SO上文本/质量比的记录.

编辑:我仍然希望这个问题可以成为完全兼容CORS的Symfony项目的正确例子.