なぜJSONPだとクロスドメイン制約を超えられるのか?

 なぜ通常のXMLHttpRequestにはクロスドメイン制約があるのに、JSONPではクロスドメインでリクエストを送信できるのか?不思議に感じたので、ちょっと調べてみた。
 クロスドメイン制約は「ブラウザ上で実行されるJavaScriptは同じドメインにしかリクエストの送信やクッキーの編集を行えない」という制限である。
 なぜこのような制限が必要になるのか。クロスドメイン制約がなかったらどうなるかを思考実験してみよう。ブラウザ上では、いくつものサイトからダウンロードしてきたJavaScriptが同時に実行されることは珍しくない。また、悪意のあるページにアクセスしてしまい、悪意あるJavaScriptを実行してしまうことも、十分に起こり得る話である。間違えて変なページにアクセスしたら致命的な問題が起きました、ではまずいので、ブラウザではJavaScriptができる事にかなりの制限を与えている。XMLHttpRequestのクロスドメイン制約もこの制限の一つである。
 XMLHttpRequestは、JavaScriptが自由にHTTP通信を行える非常に強力な機能である。クロスドメイン制約がなく、悪意あるJavaScriptが自由にXMLHttpRequestをいろんなサイトに送る事ができたらどうなるだろうか?
 例えば、銀行のサイトにログインした状態で、別のタブで悪意のあるサイトにアクセスしたものとする。悪意あるJavaScriptが自由にXMLHttpRequestを送れるとすると、ブラウザから認証クッキーも送られるという仮定の元ではあるが、悪意あるJavaScriptは口座残高の情報などを簡単に取れてしまうし、送金作業なんかもできるかもしれない。ブラウザというのは複数のページの情報を同時にロードしているものなので、サーバー側にある機密情報を守るためにクロスドメイン制約が重要なのである。サーバー側にある、と表現したのは、ブラウザ側(クライアント側)にも機密情報は存在するからだが、そもそも別のタブの内容に関しては、ブラウザにバグがない限り絶対に参照できないようになっている。当たり前の仕様なので特に名前とかはついていない。
 クロスドメイン制約は、悪意あるサイトにアクセスした際に情報が漏洩することを防ぐためにブラウザが提供する仕組みなのだ。では、JSONPはなぜクロスドメイン制約を破れるのだろうか。なぜセキュリティ上の問題にならないのか?
 まず、JSONPが使われる場面について考えてみよう。そもそも、世の中には機密情報とそうでない情報がある。クロスドメイン制約が重要なのは、悪意あるJavaScriptがあなたのブラウザ上で実行され、銀行などのサーバーにアクセスして機密情報を盗み出す事を防ぐためである。クロスドメイン制約のおかげで、悪意のあるJavaScriptが実行されても、機密情報にはアクセスできない。
 ここでポイントになるのが、JSONPはクロスドメイン制約を回避して通信するためにわざわざサーバー側で対応が必要になるという点である。後述するが、JSONPでまともに機密情報を扱うことは不可能に近い。つまり、JSONPAPIをわざわざ用意するということは、そこで提供される情報は機密情報ではないはずである。
 「機密情報でなければJSONPで提供してもいい」のだ。逆に、なにも考えずに機密情報をJSONPで提供するようなサイトが出てきてしまうと、それはセキュリティ上の問題になるだろう。ただし、それはそのサイトの脆弱性であってブラウザの脆弱性ではない。JSONPAPIを提供するかどうかはそのサイトが決めるからだ。
 ここまでを一旦箇条書きでまとめよう。

  • ブラウザ上では複数のサイトのJavaScriptが実行されるため、無制限にXMLHttpRequestでどこにでもリクエストを飛ばせるとまずい
  • JSONPはわざわざJSONPのためにサーバー側で準備しないと使えない(データをわざわざJavaScriptのコールバック形式で渡さないといけない)ため、クロスドメイン制約を回避してもセキュリティ上問題にはならないが、もちろん機密情報をJSONPに含んではならない

 「JSONP APIをサーバー側でわざわざ用意する」というところが、「この情報は機密情報ではない」と宣言することに相当するわけね。あー、やっとわかってすっきりした。
 さて、無制限にXMLHttpRequestを使えるとまずいという事はわかったが、クロスドメイン制約は厳しすぎないだろうか。さっきも書いたが、情報には機密情報とそうでない情報があり、本来であれば、機密ではない情報を提供するAPI、例えば、各都道府県の天気予報をXMLJSON で提供するAPIなどは、クロスドメイン制約にしばられずにブラウザ上から利用出来てもいいわけだ。セキュリティのために利便性をかなり落としていると言える。
 あるサーバーが機密情報を持っているのか、他のサイト上のJavaScriptからAPIを叩いてもいいのかどうかは、当たり前ではあるが、そのサーバー自体が宣言すべきである情報である。「このAPIはどこから使ってもいいです」という宣言がサーバー上のどこかにあれば、そのAPIに対してはどこからアクセスしても大丈夫なはずだ。
 この宣言に相当する仕組みが、フラッシュでのcrossdomain.xmlであり、XMLHttpRequest Level 2が読むAccess-Control-Allow-Originである。
 XMLHttpRequest Level 2では、クロスドメインなリクエストもとりあえず送信してみて、返ってきたHTTPヘッダにAccess-Control-Allow-Originが含まれているかどうかを見る。含まれており、なおかつ利用可能条件が一致する場合にのみ、JavaSciptは結果を受け取ることができる。
 既にJSONPがあるのにわざわざXMLHttpRequest Level 2を使うのは、JSONPには

  • リクエスト元を制限する確実な方法がない
  • 読み込んだJavaScriptがブラウザ上で実行されるので、信用できないサイトのJSONPは読み込んではいけない

 といった問題点があるからである。XMLHttpRequest Level 2を実装したブラウザのみを閲覧環境とできるようなサイトであるならば、JSONPよりもXMLHttpRequest Level 2を使ったほうが良い。Firefox 3.5, Google Chrome 2, IE 8(こいつだけはちょっと仕様が違うけど)以降で使える。