パーソナルデータのmashupとクロスドメインJSONのセキュリティ(2)

前回のエントリでクロスドメインでパーソナルデータをJSON呼び出しする際のセキュリティ問題について触れた。

簡単に言うと、ユーザのパーソナルデータをmashupする際には、ユーザだけでなくちゃんとそのデータを利用するアプリケーションの認証を行わなければならない、ということでもある。

そのためには、共有鍵を利用してHMACを作成するなどいろいろ方法はあるだろうが、ここでは非常に簡単に呼び出し元のアプリケーション認証を行う方法について提案する。

先に答えを書くと、クライアント(ブラウザ)が送信してくるリファラヘッダ情報を利用する、というものである。

ただこれだけではわかりにくいと思うので、具体的なデータのやり取りのイメージを交えて説明してみる。


最初にJSONでのクロスドメイン呼び出しについて、おさらいしておく。

いま、前回の例で挙げたようなオンラインCRMサービス(仮にwww.examplecrmservice.comで提供されているとしよう)が顧客コンタクトの一覧をJSONサービスで提供している。そしてこのサービスエンドポイントが

http://www.examplecrmservice.com/json/contactlist

であるとする。

ユーザがCRMサービスに認証済みの状態でブラウザからこのURLに直接アクセスすると、以下のようなJSONデータがレスポンスとして返ってくる

[
  { "id" : 10000234,  "name" : "山田 太郎", "address" : "東京都千代田区丸の内2-4-1", "rank", "AA" } ,
  { "id" : 10000456,  "name" : "鈴木 一郎", "address" : "東京都港区六本木6-10-1", "rank", "A" } ,
  { "id" : 10000789,  "name" : "山本 花子", "address" : "東京都渋谷区桜丘町26-1", "rank", "B+" } ,
  { "id" : 10101012,  "name" : "佐藤 恵子", "address" : "東京都港区台場2-4-8", "rank", "S" } ,
  { "id" : 10101345,  "name" : "田中 伸一", "address" : "東京都港区赤坂5-3-6", "rank", "B-" } 
]

このデータは、ユーザがあらかじめ自分の顧客リストとして登録していたものであると想定しよう。つまり万人が見ることができるものではない、パーソナルデータである。

CRMサービスは、ユーザのCookie情報を見てユーザを判別し、そのユーザにひもづけられている顧客コンタクトリストデータを選択して返している。そのためユーザが異なれば同じエンドポイントでも全く異なるJSONデータが返ってくるだろう。

このJSONデータをブラウザから直接クロスドメインで呼び出しできるようにするためには、JSONPの形式をサポートしていればいい。つまり

http://www.examplecrmservice.com/json/contactlist?callback=handleContactList

というリクエストに対し

handleContactList([
  { "id" : 10000234,  "name" : "山田 太郎", "address" : "東京都千代田区丸の内2-4-1", "rank", "AA" } ,
  { "id" : 10000456,  "name" : "鈴木 一郎", "address" : "東京都港区六本木6-10-1", "rank", "A" } ,
  { "id" : 10000789,  "name" : "山本 花子", "address" : "東京都渋谷区桜丘町26-1", "rank", "B+" } ,
  { "id" : 10101012,  "name" : "佐藤 恵子", "address" : "東京都港区台場2-4-8", "rank", "S" } ,
  { "id" : 10101345,  "name" : "田中 伸一", "address" : "東京都港区赤坂5-3-6", "rank", "B-" } 
]);

というレスポンスを返すようにする。これはそのままJavaScriptの関数実行命令になっていることに注意されたい。


一方、mashupを行うアプリケーションサービス側は、シンプルにHTMLとJavaScriptを書くだけでよい。

例えば以下のようなJavaScriptコードを書いて

http://www.crmserviceandgooglemap.org/mashup.html

というHTMLファイルとしてサーバ上にアップロードしておく。

<script type="text/javascript">
function handleContactList(contacts) {
  for (var i=0; i<contacts.length; i++) {
    ... plotting contact info to google maps ...
  }
}
</script>
<script type="text/javascript" charset="utf-8" src="http://www.examplecrmservice.com/json/contactlist?callback=handleContactList"></script>

外部スクリプトの形でインクルードされているJSONサービスは、サーバからのスクリプト読み込みが完了すると同時にあらかじめHTML内で定義されていたhandleContactList関数を呼び出す。そしてその際に引数として先のJSONデータが与えられる。

これにより、異なるドメインのアプリケーションからコンタクトリストデータを取り込むことが出来ることになる。


ここで、外部スクリプトインクルードの際に発行されるHTTPリクエストについて調べてみる。


上の例では、http://www.crmserviceandgooglemap.org/mashup.htmlが読み込まれると同時に、scriptタグが解析され、src属性に指し示される外部スクリプトの呼び出しが行われる*1。その際、www.examplecrmservice.com に届くHTTPリクエストは以下のようになる

GET /json/contactlist?callback=handleContactList HTTP/1.1
Host: www.examplecrmservice.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; ja-JP-mac; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4
Accept: */*
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
Keep-Alive: 300
Connection: keep-alive
Referer: http://www.crmserviceandgooglemap.org/mashup.html
Cookie: _session_id=2247778e3245efbe4f6124f30ab6bf50

いま、上記のReferer ヘッダに、呼び出し元のアプリケーションのURLが含まれているのがわかるだろう。

つまり、この情報を利用して、CRMサービス側は呼び出し元のアプリケーションがどこにあるのかを知ることができる。そしてあらかじめ許可されているURLからの呼び出しのみにしか有効なJSONデータを返さないことで、不用意にデータを盗まれることを避けることができる。


ここで、HTTPのヘッダ情報など簡単に偽装されてしまうのではないか?という疑問があるかもしれない。

たしかに、ヘッダ情報は送信者が改変することができるものである。

しかしこれはユーザ側で操作しない限り変更は出来ない。つまり、勝手にユーザのブラウザが送出するヘッダ情報をサーバアプリケーション側の意思で変更させることは不可能だ。よってリファラのURLはアプリケーションの所在を表す確かな情報として機能するため、アプリケーションの認証に利用できる。

ただ問題は、ユーザエージェントによってはリファラの送出を意図的にカットしている場合がある、ということだろう。もしそのユーザも含めてサポートしなければならないのであれば、この方法は使えない。ただ、大多数にはリーチできるし、それを補う利点くらいの利点は存在していると思う。


以上でクロスドメインでのJSONサービスをセキュアに行う方法を実際のデータのやり取りのイメージを含めて説明してみた。

最後に、このパーソナルデータのやり取りを、ユーザの許認可ベースで行う実装の詳細について、この後の回で触れようと思う。

*1:このタイミングは動的スクリプト呼び出しによって調整できる