web-dev-qa-db-ja.com

Symfony、jQuery、FOSRestBundle、NelmioCorsBundleを使用したCORS

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
TL; DR:編集(全部を読む前に):

この問題は解決されました。コードに非常に愚かな間違いを犯したためです。CORSなどとは関係ありません。
とにかくこの問題を読みたい場合は、動作するCORS構成があることに注意してください。例。

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

(注の終わり)

状況

マルチドメインのSymfonyアプリケーションがあり、バックエンド部分が何らかの理由で従来のフォームではなくWebサービスでデータを更新します。

バックエンド:back.mydomain.dev Webサービスドメイン:api.mydomain.dev

JQueryを使用してこれらのWebサービスをAJAX呼び出し、オブジェクトを変更または作成する場合は、AJAXリクエストを送信すると、オブジェクトがマージされますDoctrineエンティティを使用して、永続化します。

私はこのアプリケーションで適切に機能するGET、PUT、POST、およびDELETEリクエストを作成するために、1年間戦ってきました。それらが異なるドメインにあるという理由だけで、セットアップを余儀なくされています。 [〜#〜] 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できません)、オブジェクトを更新でき、JSONデータのみを返します(そのためのリスナーがあります)。

理想的な行動

理想的には、私はこれが欲しいです:

  1. JQuery AJAX POST/PUT/DELETEリクエストを実行します
  2. すべてのCORSヘッダーを含むOPTIONSリクエストを送信する必要があります
  3. NelmioCorsBundleは、アプリ内でコントローラーを実行する前であっても、実行するリクエストを受け入れる正しいCORSヘッダーを返す必要があります(バンドルのリクエストリスナーによって作成されます)
  4. 受け入れられると、適切なHTTPリクエストがすべてのリクエストデータとともに(リクエストペイロード内の)シリアル化されたJSON文字列としてコントローラーに送信され、Symfonyはそれを取得して、正しいJSON文字列を配列オブジェクトとして解釈します。
  5. 次に、コントローラーはデータを取得して処理を行い、application/json応答を返します。

問題

しかし、ここでは、maaaaanyの組み合わせを試しましたが、うまくいかないようです。

ポイントn°3および4は、完全にまたは部分的に失敗しています。

回避策を試しました

  1. 私がしない場合dataJSON.stringifyでシリアル化します(セットアップを参照) 上記の部分)、OPTIONSリクエストが送信されますが、FOSRestBundleはInvalid json message receivedと言ってBadRequestHttpExceptionを送信します。これは、「リクエストペイロード」( Chromeの開発者ツール)は、jQuery AJAXリクエストでapplication/x-www-form-urlencodedを指定した場合でも、古典的なcontentType: "application/json"コンテンツですが、シリアル化されたJSON文字列である必要があります。

  2. ただし、Idodata varをシリアル化する場合、「リクエストペイロード」は有効ですが、OPTIONSリクエストは送信されず、 CORSが受け入れられないため、リクエスト全体が失敗します。

  3. contentType: "application/json"application/x-www-form-urlencodedまたはmultipart/form-dataに置き換え、データをシリアル化しない場合、要求ペイロードは有効ですが、OPTIONS要求は送信されません。説明されているように、これも正常である必要があります jQueryのドキュメントcontentTypeパラメータの下:

    注:クロスドメインリクエストの場合、コンテンツタイプをapplication/x-www-form-urlencoded、multipart/form-data、または、text/plainがブラウザをトリガーして、プリフライトOPTIONSリクエストをサーバーに送信します。

    では、正しいCORSリクエストを送信する方法は?

質問

  • この問題はどこから来ていますか?
  • これは私のセットアップの問題ですか? jQueryで?
  • AJAX自体?
  • このいまいましい問題を解決するためのさまざまな解決策は何でしょうか?

編集(コメント後)

2015-06-29 17:39

条件:

AJAXでは、contentTypeapplication/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/...

プリフライト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

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

リクエストペイロード(生):{"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

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

これは正しいResponseヘッダーを返します:

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

ただし、ブラウザは「Access-Control- *」ヘッダーなしでPOSTリクエストを送信します。

21
Alex Rock

実際、私のセットアップはうまくいきました。
私が言うなら、絶対に完璧です。

それはある種の…愚かさでした。

exit;はPHPファイルの奥深くに隠されていました。

ご迷惑をおかけして申し訳ありません。 SOのテキスト/品質比のレコードのようなものだと思います。

編集:この問題が完全にCORS互換のSymfonyプロジェクトの適切な例になることを願っています。

14
Alex Rock