2011年2月28日月曜日

Access-Control-Allow-Origin

WebブラウザからRESTをアクセスするプログラムを開発するときに、困るのがクロスオリジンの問題。
Webページ上で動作するJavaScriptプログラムは、Webページをダウンロードしたサイトにしかアクセスができない、というアレです。

プログラム開発中では、作ったプログラムをわざわざサーバーに上げるといったような作業が必要となるため、とても不便です。
また、運用時でもREST APIを一般に公開する場合には、大きな制約になります。

この問題は従来JSONPで回避してきましたが、最近はAccess-Control-Allow-Originという手法があるのを知り、g3に実装してみました。


Access-Control-Allow-Originはブラウザ側でのクロスオリジンの選択をするために必要な情報を、HTTPのヘッダにクロスオリジン情報を付加するものです。
ブラウザは、従来はクロスオリジンであればすべて拒否していたのを、ヘッダに許可情報がある場合はクロスオリジンを許可するという動きになります。

具体的には、GET, POST, PUT, DELETEではリプライのヘッダにAccess-Control-Allow-Originヘッダを設定します。

Servletの該当箇所は以下のようになります。(g3の実装なのでScalaです。)

resp.setHeader("Access-Control-Allow-Origin", "*")

GET, POST, PUT, DELETEにAccess-Control-Allow-Originをつけるだけでよいと勘違いしていて、ちょっとはまったのですが、実はOPTIONSでリソースアクセスに対するクロスオリジン定義を返さないといけないのですね。
以下の情報をヘッダに設定します。

Access-Control-Allow-Origin
クロスオリジンを許すURL。すべて許す場合は「*」。
Access-Control-Allow-Methods
Access-Control-Request-Methodに指定されたメソッドを返すのが丁寧っぽいですが、公開してるメソッドをすべてを毎回返すようにしても大丈夫のようです。
Access-Control-Allow-Header
Access-Control-Request-Headersで指定されている文字列を返します。ブラウザがチェック用に使っているみたいです。
Access-Control-Max-Age
このOPTIONSの設定の有効時間。この時間が過ぎると再度OPTIONSでクロスオリジン定義を取りにきます。

Servlet該当箇所は以下のようになります。

resp.setHeader("Access-Control-Allow-Origin", origin)
      resp.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, OPTIONS")
      resp.setHeader("Access-Control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"))
      resp.setHeader("Access-Control-Max-Age", accessControlMaxAge.toString)