CORSの動作を確認した
前回、こちらのエントリーにて、XMLHttpRequestが SOP の対象となった場合の動作を確認しました。
今回は、Cross-Origin Resource Sharing (CORS)の仕様したがって、SOPで制限されている クロスオリジンへの XMLHttpRequest(XHR) を可能にします。
CORSについては以下を参照すると良いでしょう。
リクエストは2パターン
リクエストは2パターンあります。条件によってそれぞれ使い分けされます。
それぞれ動作を確認していきます。
シンプルなリクエスト
シンプルなリクエストとは
シンプルなリクエストとは、以下のすべての条件に合うものです。
※HTTP アクセス制御 (CORS)から抜粋
許可されたメソッドは以下に限る:
- GET
- HEAD
- POST
ユーザエージェントが自動的に設定するヘッダ (Connection や User-Agent など) を除き、手動で設定できるヘッダは以下に限る:
- Accept
- Accept-Language
- Content-Language
- Content-Type
Content-Type ヘッダで許可される値は以下に限る:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
実際にやってみる
前回の記事で、「異なるオリジンにXHRをしてみる」というセクションで、異なるオリジンにXHRをしSOP の制御対象となることを確認しましたが、このリクエストを許可するようにします。
前回と同じ環境で、異なるオリジンのXHRが許可されるような条件を加え試します。
前回の環境
- XHR送信側サーバ(Sinatra):
http://test.example.com:4567/
(HTML,JSをレンダリングし、そこからXHRを送信します。) - XHR受信側サーバ(Sinatra):
http://test.example.com:4568/
- リクエストメソッド: POST
- レスポンス:
Failed to load http://test.example.com:4568/: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://test.example.com:4567' is therefore not allowed access.
というエラーがブラウザに表示され、Response が受け取れない
Accept:*/* Accept-Encoding:gzip, deflate, br Accept-Language:ja Connection:keep-alive Content-Length:0 Content-Type:text/plain Host:localhost:4567 Origin:http://test.example.com:4567 Referer:http://test.example.com:4567/tools User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36
加える条件
XHR受信側サーバ(http://test.example.com:4568/
) の Response Header に以下を返すようにします。
Access-Control-Allow-Origin: http://test.example.com:4567/
結果
レスポンスを受け取ることができました。
レスポンスヘッダ
Access-Control-Allow-Origin:http://test.example.com:4567 Connection:keep-alive Content-Length:116 Content-Type:text/html;charset=utf-8 Server:thin X-Content-Type-Options:nosniff X-Frame-Options:SAMEORIGIN X-XSS-Protection:1; mode=block
レスポンスボディ(レスポンスメソッド とUserAgentを返すようにしています。)
POST from [Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36]
Access-Control-Allow-Origin ヘッダ
上記の動きで確認した通り、Access-Control-Allow-Origin
は、指定した Origin のアクセスを許可することができます。
リクエストで送られたOrigin
ヘッダの内容とこの Access-Control-Allow-Origin
が同じであればアクセス許可されるという仕組みです。
(Origin
ヘッダは、XHRで異なるOrigin
へ送信するときに自動的に付与されます。)
どの Origin でもアクセスを許可させる場合は、Access-Control-Allow-Origin: *
という形にもできます。
ただし、これは、後ほど説明する クレデンシャルを含むリクエスト
には使うことができません。
プリフライトリクエスト
以下のようなリクエストのときに、プリフライトリクエストを行います。
GET、HEAD、POST 以外のメソッドを使用した場合。また application/x-www-form-urlencoded、multipart/form-data、または text/plain 以外の Content-Type とともに POST を使用してリクエストを行う場合、例えば application/xml または text/xml を使用して POST で XML のペイロードをサーバーへ送るときは、リクエストでプリフライトを行います。
実際にやってみる
ひとまず、ダメ元で、先ほどシンプルなリクエストで行ったもののリクエストメソッドをPOSTからPUTに変更しただけの状態で試してみます。
デベロッパーツールのConsoleには、以下のようなエラーが表示されました。
xhr.js:18 OPTIONS http://test.example.com:4568/ net::ERR_ABORTED send_request @ xhr.js:18 onclick @ tools:22 tools:1 Failed to load http://test.example.com:4568/: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://test.example.com:4567' is therefore not allowed access. The response had HTTP status code 404. xhr.js:18 XHR failed loading: OPTIONS "http://test.example.com:4568/".
OPTIONSメソッドでアクセスしようとしたが、サーバ側でOPTIONSを受け取る処理を行っていなかったので、404で返ってきているみたいですが、 なぜ、PUTではなく、OPTIONSで送信されたのでしょう。
プリフライトリクエストの流れ
OPTIONSで送信されたのには、以下の理由があります。
HTTP アクセス制御 (CORS)には、こう記述されています。
シンプルなリクエスト (前述) とは異なり、"プリフライト" リクエストは始めに、実際のリクエストを送信しても安全かを確かめるために他ドメインのリソースへ向けて OPTIONS メソッドを使用して HTTP リクエストを送信します。クロスサイトリクエストはユーザーデータに影響を与える可能性があるため、このようにプリフライトを行います。
つまり、流れとしては、
- [クライアント]OPTIONSメソッドを送信
- [サーバ]許可しているメソッドやカスタムヘッダをResponse Headerに含め返します。
- メソッドを許可: Access-Control-Request-Method
カスタムヘッダを許可: Access-Control-Request-Headers (こちらは後ほど詳しく説明します。)
[クライアント]元々送ろうとしていたリクエストを送信する
・・・あとは、通常通り
改めて実際にやってみる
今回は、PUTメソッドを許可するので、サーバ側に以下の設定を加えます。
- OPTIONSメソッドを受け取るようにする
- OPTIONSメソッドを受け取ったら、以下のヘッダ・値を返すようにする
Access-Control-Allow-Origin: http://test.example.com:4567
Access-Control-Allow-Methods: PUT
そして送信!
デベロッパーツールのConsoleには、以下のように、OPTIONSとPUTを送信している様子が見えます。
xhr.js:18 XHR finished loading: OPTIONS "http://test.example.com:4568/". send_request @ xhr.js:18 onclick @ tools:22 XHR finished loading: PUT "http://test.example.com:4568/".
リクエストヘッダ(OPTIONS)
OPTIONS / HTTP/1.1 Host: localhost:4568 Connection: keep-alive Access-Control-Request-Method: PUT Origin: http://test.example.com:4567 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36 Accept: */* Referer: http://test.example.com:4567/tools Accept-Encoding: gzip, deflate, br Accept-Language: ja,en-US;q=0.8,en;q=0.6
レスポンスヘッダ(OPTIONS)
HTTP/1.1 200 OK Content-Type: text/html;charset=utf-8 Access-Control-Allow-Origin: http://test.example.com:4567 Access-Control-Allow-Methods: PUT X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Connection: close Server: thin
リクエストヘッダ(PUT)
PUT / HTTP/1.1 Host: localhost:4568 Connection: keep-alive Content-Length: 0 Accept: */* Origin: http://test.example.com:4567 Accept-Language: ja User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36 Content-Type: text/plain Referer: http://test.example.com:4567/tools Accept-Encoding: gzip, deflate, br
レスポンスヘッダ(PUT)
HTTP/1.1 200 OK Content-Type: text/html;charset=utf-8 Access-Control-Allow-Origin: http://test.example.com:4567 Content-Length: 115 X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Connection: keep-alive Server: thin
うまくリクエストを送ることができました。
クレデンシャルを含むリクエスト
同一オリジンであれば、クレデンシャルを含むリクエストはデフォルトで許可されますが、 異なるオリジンへクレデンシャルを含むリクエストを送るときは、注意が必要です。
ちなみに、クレデンシャルというのは、Cookieの送信、BASIC認証を指します。
実際にやってみる
ひとまず、そのままではできないことを確認しましょう。
こんな感じで、XHRでCookieを含めて送信するようにします。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
デベロッパーツールのConsoleには、以下のようなエラーが表示されました。
tools:1 Failed to load http://test.example.com:4568/: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. Origin 'http://test.example.com:4567' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute. xhr.js:19 XHR failed loading: POST "http://test.example.com:4568/".
なるほど、Access-Control-Allow-Credentials
ヘッダを追加というものをつけて上げる必要があると
というわけで、サーバに以下のヘッダを返すようにします。
Access-Control-Allow-Credentials:true
注意点としては、上記を追加する場合は、Access-Control-Allow-Origin:*
の指定ができません。
必ず、オリジンを指定する必要があります。
改めて実際にやってみる
では、引き続きやってみましょう。
シンプルなリクエスト・プリフライトリクエストが必要なものどちらも行いましたが、今度は、成功しました。
デベロッパーツールのConsole
xhr.js:19 XHR finished loading: POST "http://test.example.com:4568/". send_request @ xhr.js:19 onclick @ tools:21 xhr.js:19 XHR finished loading: OPTIONS "http://test.example.com:4568/". send_request @ xhr.js:19 onclick @ tools:22 XHR finished loading: PUT "http://test.example.com:4568/".
CORSのヘッダについて
CORSのリクエストヘッダ/レスポンスヘッダの詳細については、HTTP アクセス制御 (CORS)に詳しく書いてあるので、ここを読めばバッチリでしょう。