同じオリジンのAPIと非同期で通信、という場合は xhr で普通にこなしてますが、別ドメインのAPIと非同期で、となると割とつまづくポイントがあったのでメモ。
こちらのサイトが大変分りやすかったです。
- CORSではまったこと http://inside.pixiv.net/entry/2014/12/16/181804
xhr2 を使う
特に何かしなくても、ブラウザがxhr2対応しているものであればxhr2を使うことになります。
逆にいうと、xhr2 対応していないブラウザだとダメ。
とりあえず普通に実装する
特にクロスドメインであることを気にかけず、クライアントサイド、サーバサイドそれぞれをまずは実装します。
Origin 許可する
サーバサイドでアクセス元となるOriginを意識して設定する必要があります。
- サーバサイドのレスポンスヘッダの追加
- クライアントサイドはモダンブラウザなら特別な対応は不要
サーバサイドの対応
Access-Control-Allow-Origin レスポンスヘッダを追加します。
セットする値は許可するアクセス元Originになります。(プロトコル + サブドメイン + ドメイン)
どこからアクセスされてもOKな場合は "*" になります。
クライアント側の環境によってはうまく処理されないケースがあるので、Access-Control-Allow-Headers レポンスヘッダも一緒に追加します。
セットする値はクライアントサイドの実装環境によって、Content-Type の外にも X-Requested-With や Accept や Origin などをセットしたほうがいい時があるみたいです。
以下はHTTP/SSL での foo.unknown.jp と bar.unknown.com という Origin からのアクセスだけを許可する場合の apache 側の設定例です。
SetEnvIf Origin "^https?://(foo¥.unknown¥.jp|bar¥.unknown¥.com)$" ORIGIN=$0
Header append Access-Control-Allow-Origin %{ORIGIN}e env=ORIGIN
Header append Access-Control-Allow-Headers "Content-Type"
Access-Control-Allow-Origin のホワイトリスト対応の件
上の例のような、特定複数のOriginだけ許可したい、という場合に例えばサーバ側から
# 注意: これはうまくいかない
Access-Control-Allow-Origin: "http://foo.unknown.jp http://bar.unknown.com"
となるように返しても、 Access-Control-Allow-Origin ヘッダは1つしか値を受け付けないという内容のエラーメッセージが表示 されます。
複数許可したい、でも "*" にするわけにはいかないという場合、サーバ側でオンデマンドに許可するOriginを変更することで回避できます。
クライアントサイド
とくに意識せずxhrリクエストを投げます。以下は jQuery での例。
$.ajax({
url: 'https://foo.unknown.jp/api/test.json',
type: 'get',
dataType: 'json',
success: function(result, textStatus, xhr) {
call_back_function.call(self, result, textStatus, xhr);
},
error: function(xhr, textStatus, error) {
call_back_error_function.call(self, xhr, textStatus, error);
}
});
Cookie 使う場合は更に Credentials を有効にする
Cookie をやり取りしたい場合は、Credentials を有効にする必要があります。
- サーバサイドのレスポンスヘッダの追加
- クライアントサイドからのリクエストで credential を有効にする
サーバサイドの対応
Access-Control-Allow-Credentials レスポンスヘッダを追加します。
値は true にします。
Header append Access-Control-Allow-Credentials true
※ Access-Control-Allow-Origin が "*" になっていると使えないようです。
クライアントサイドの対応
リクエスト時にCredentialを付けて送る必要があります。
以下はjQueryでの設定例で、xhrFields プロパティを設定しています。
$.ajax({
url: 'https://foo.unknown.jp/api/test.json',
type: 'get',
dataType: 'json',
xhrFields: {
withCredentials: true
},
success: function(result, textStatus, xhr) {
call_back_function.call(self, result, textStatus, xhr);
},
error: function(xhr, textStatus, error) {
call_back_error_function.call(self, xhr, textStatus, error);
}
});
Chrome とFirefoxはここまでの設定で大体動くようになりました。
IEはまだもう少し対応が必要です。
IE 対応するなら利用ライブラリのバージョンを確認
IE9以前はXMLHttpRequest対応していない独自オブジェクトXDomainRequestを使うため、jQueryなどライブラリを使用している場合は使用バージョンでどう対応しているのか確認します。
ここがサポートブラウザに影響してくるかもしれません。
IE でCookie使うなら P3P コンパクトポリシー
IEのセキュリティレベルの設定で、「すべてのCookieを受け付ける」以外にしている場合は、
P3Pコンパクトポリシーの付いているサイトでないとCookieを送信しないようです。
Cookieを発行しているリソースプロバイダが Set-Cookie レスポンスヘッダを返す時に、一緒に P3P ヘッダへコンパクトポリシーをセットして返すようにすれば、セキュリティを最低レベルにする必要がなくなります。
# Cookieの発行サーバで以下をレスポンスヘッダにつける
P3P: CP="NON CUR OUR NOR ONL UNI"
P3Pコンパクトポリシーの設定値
P3Pコンパクトポリシーが設定してあれば、設定値は何でも動いてしまうようです。
とはいえP3Pの仕様を参考にCookie発行サイトがどのようなポリシーなのか、3文字で表すトークンを組み合わせて作ってみると良いと思います。
そんなに難しいことはないですし、間違えてしまっても動きますし。
IE のセキュリティ設定
P3Pコンパクトポリシーに対応している外部サイトの場合には、ユーザがブラウザの設定を変更することなく CORS が使えました。
P3Pコンパクトポリシーが付いていない外部サイトにCORSする場合は、以下のセキュリティ設定の変更をすれば使えるようになりました。実用することはほぼないと思いますが何かの時の参考までに。
- インターネットオプション -- セキュリティ設定 -- インターネットゾーンのセキュリティレベルの[レベルのカスタマイズ] -- その他 -- ドメイン間でのデータソースのアクセス を有効にする
- インターネットオプション -- プライバシー設定 -- レベルを一番低い「すべてのCookieを受け入れる」にする
このメモを書いたときの手元の環境
参考までに。
サーバサイド
- apache 2.2.27 / 2.4.12
クライアントサイド
- jQuery 2.1.3
- Chrome 41.0.2272.76
- Firefox 36.0.1
- IE 11.0.9600
参考サイト
- CORSではまったこと http://inside.pixiv.net/entry/2014/12/16/181804
- jQuery Documentation http://api.jquery.com/jQuery.ajax/
- Access-Control-Allow-Origin - Mozilla Developer Network https://developer.mozilla.org/ja/docs/HTTP_access_control
- W3C P3P 1.0 仕様書 http://www.iajapan.org/trans2japanese/w3c/rec-p3p-20020416j.html
- IBM P3P ヘッダー内のデフォルト・コンパクト・ポリシー http://www-01.ibm.com/support/knowledgecenter/SSPREK_6.1.0/com.ibm.itame.doc_6.1/am61_webseal_admin168.htm?lang=ja
- apache 設定ファイル内での変数フォーマット http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#formats