JavaScript Ajax : XmlHttpRequestのLevel2でSameOriginPolicyを回避する
クロスドメイン制限の回避について
- 今まではXHR(XmlHttpRequest)の仕様によりJSを読み込んでいるHTMLファイルがあるサーバから異なるドメインサーバへのAjaxリクエストが制限されていました。Same Origin Policyと呼ばれているものです。Same Origin Policyの役割としては悪意のあるscriptが個人情報等を他のサイトに転送する事を防ぐためです。このセキリティ制限を回避するために多くの人が代表的なJSONP(JSON with Padding)を利用してサーバサイドでクライアントのコールバック関数をechoしてクライアント側で実行されることにより、クロスドメイン間のAjax通信をそれっぽく動くように対応していたと思います。
- JSONPについては以前記事を書いたので宜しければ参照してください。20秒で理解するJSONP - Yuta.Kikuchiの日記
- JSONPを利用するのは少し面倒でコールバック関数をリクエストされたサーバサイドから返却しないといけなく、またセキュリティ面でも不安で十分に気をつける必要があります。JSONP以外にもリバースProxyを用意する、Flashを経由するとクロスドメインリクエスト可能となりますがこの面倒な事に頭を悩ませるJavascripterも多かったと思うのですが、色々と調べてみるとXHRのLevel2の仕様によりJSONPを使わずとも異なるドメイン間でのAjaxリクエストが可能となりそうです。
制限を回避するために
Access-Control-Allow-Origin
- HTTP access control - MDN にあるようにAccess-Control-Allow-OriginというHttpResponseHeaderを仕込むとクロスドメインを回避する事ができるようです。このHttpResponseHeaderをどこに仕込むのか?ということが疑問になりそうですが、Ajaxリクエスト先のAPIサーバに設置します。(リクエスト元に設定する方式だとXSRFなどが好き勝手出来てしまうのでそれは無いですね)
- Access-Control-Allow-Originにリクエストを受け付けるURLを指定、もしくは*(ワイルドカード)を指定すると全てのURLを受け付ける設定になります。
関連Header一覧
- 色々なブラウザ仕様があるのでAccess-Control-Allow-Originだけでなく、念のため以下のResponseHeaderも設定しておくと動作が確認できるようです。
Demo
今回のAPI/ClientともにサンプルコードをGitHubに挙げました。https://github.com/yutakikuchi/JS/tree/master/crossdomain
API Server
- アクセスするAPIサーバをGoogle App Engineに置きます。上で説明したHeaderを仕込み、とりえあずは簡単な文字列(CrossDoaminRequest)だけをprintします。
#!/usr/bin/env python # -*- coding: utf-8 -*- from google.appengine.ext import webapp from google.appengine.ext.webapp import template, util class CrossDomain(webapp.RequestHandler): def get(self): self.response.headers[ 'Content-Type' ] = 'text/plain' self.response.headers[ 'Access-Control-Allow-Origin' ] = '*' self.response.headers[ 'Access-Control-Allow-Methods' ] = 'GET' self.response.headers[ 'Access-Control-Allow-Headers' ] = '*' self.response.headers[ 'Access-Control-Allow-Age' ] = '86400' self.response.out.write( 'CrossDoaminRequest' ) def main(): application = webapp.WSGIApplication([('/CrossDomain', CrossDomain)]) util.run_wsgi_app(application) if __name__ == '__main__': main()API出力Header
- 設定したAPI(http://mobiles-proxy.appspot.com/CrossDomain)にRequestすると次のようなResponseHeaderが返却されます。Access-Cotroll-Allow-*というHeaderが返ってきていることが分かります。
http://mobiles-proxy.appspot.com/CrossDomain GET /CrossDomain HTTP/1.1 Host: mobiles-proxy.appspot.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:8.0.1) Gecko/20100101 Firefox/8.0.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ja,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7 Connection: keep-alive HTTP/1.1 200 OK Cache-Control: no-cache Content-Type: text/plain Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET Access-Control-Allow-Headers: * Access-Control-Allow-Age: 86400 Expires: Fri, 01 Jan 1990 00:00:00 GMT Content-Encoding: gzip Vary: Accept-Encoding Date: Sat, 25 Feb 2012 08:29:03 GMT Server: Google Frontend Content-Length: 38Request Client
- IEでも動くようにXDomainRequestとXMLHttpRequestをそれぞれ使い分けるようなコードを書きます。色々と試してみたところクライアント側でAPI ServerのResponseを取得した時にAccess-Control-Allow-Originの値にて許可されたサイトであることを判定しているようです。
<head> <meta http-equiv="content-type" content="text/html;charset=UTF-8"> <title>CrossDomainAjax</title> <script> var Request = function() { var url = 'http://mobiles-proxy.appspot.com/CrossDomain'; var XHR; // IE対策 if( window.XDomainRequest ) { XHR = new XDomainRequest(); XHR.onload = function(){ echo( XHR.responseText ) }; XHR.open( 'GET', url ); XHR.send(); // IE以外 } else { XHR = new XMLHttpRequest(); XHR.onreadystatechange = function(){ if( XHR.readyState === 4 && XHR.status == 200 ) echo( XHR.responseText ); }; XHR.open( 'GET', url, true ); XHR.send(); } } var echo = function( text ) { alert( text ); } </script> </head> <body> <input type='button' name='ajax' value='CrossDomainRequest' onClick='javascript:Request();' /> </body> </html>実行検証
- いくつかのBrowserで動作確認をします。環境がMacなのでIEは試していません。
- 結果SafariはAccess-Control-Allow-Originが無くても実行が出来てしまいました。またOperaは動作しませんでした。
Browser Access-Control-Allow-Origin Access-Control-Allow-Methods Access-Control-Allow-Headers Access-Control-Allow-Age Safari ver 5.1.2 不要 不要 不要 不要 Chrome ver 17.0.963.56 要 不要 不要 不要 FireFox ver 10.0.2 要 不要 不要 不要 Opera ver 11.61 - - - -
まとめ
- JSONPを用いなくてもXHR Level2を利用するとCrossDomainRequestが可能です。
- XHR Level2を利用するにはAccess-Control-Allow-OriginというResponseHeaderをアクセス先のサーバが返す必要があります。
- IEで動作させるにはXDomainRequestオブジェクトを生成します。
- 各種ブラウザによって挙動が異なり、SafariはAccess-Control-Allow-Originさえ無くても動き、Operaは未対応です。
- 今回の検証サンプルコードを以下に設置しました。https://github.com/yutakikuchi/JS/tree/master/crossdomain