クロスドメインXHRのためのApacheプロキシ

XMLHttpRequestがsame origin policyに縛られているのをなんとかするためのプロキシをmod_proxyだけでやるという話.

XHRとクロスドメイン

XMLHttpRequest使うと, JavaScriptを積極的に使ったダイナミックHTMLのページから, 新たなデータを動的にリクエストすることができ, 取得したデータを元のページ内で画面遷移なしに利用できる. いわゆるAjaxの要となる技術.

ただし, XMLHttpRequestは基本的に同一ドメインに対してのリクエスト以外は無効という制限がある. もしこの制限がないとセキュリティ上の問題が発生する. たとえばGmailのURLに対してXMLHttpRequestでリクエストを行なうコードをどこかのサイトに仕掛けておくとする. このXMLHttpRequestでは, 認証情報を含むCookieもブラウザによって送信される. すると, Gmailにログインしている人がそのサイトを訪れる度に, その人のメールボックスや連絡先を盗み取れることになる.

そうは言うものの, 同じWebサービスでもドメインを跨いで運用している場合もあり, 同じドメインでないといけないという制約はきつすぎる場合もある. 最近はこれを緩和して, ドメインを跨ぐリクエストを明示的に許可できるようにする方法(XMLHttpRequest Level 2, Cross-Origin Resource Sharing)もあり, リクエスト先のサーバからのHTTPレスポンスのAccess-Control-Allow-Origin等にリクエスト元が含まれていればドメインを跨いでいてもレスポンスが得られる仕組みになっている.

とくに公開APIを提供するWebサービスで, かつユーザ認証を伴なわないものについては, すべてのアクセス元からのXMLHttpRequestを許すべき. 逆にそのようなAPIを利用したいだけの場合には, Cookieの送信をしない代わりにクロスドメインXMLHttpRequestを許すような仕組みがあってもよさそうなところ.

同一ドメイン制約をなんとかする方法

クロスドメインでXHRするための方法としては以下のものが知られている.

  • JSONP
    • もちろんサーバ側がcallbackを受け付ける場合のみ
    • データもJavaScriptとして解釈できるものに限られる
  • Flashを使う
    • サーバ側にポリシーファイルを置く必要あり
  • Access-Control-Allow-Origin等を設定する
  • クロスドメインリクエストプロキシ

最初の3つはXHRのリクエスト先サーバによるお膳立てが必要. そういう仕掛けが望めない場合には, ドメインを跨ぐためのプロキシを立てるという方法がある.

XDRプロキシ

方法としては単純で, 自分の管理下にあるプロキシサーバに代わりにリクエストしてもらい, そのプロキシサーバからのレスポンスではAccess-Control-Allow-Originであらゆるリクエスト元を許可するようにする. プロキシサーバのドメインはふつうリクエスト先のサーバとは別なのでCookieは渡らず, 「Cookieの送信をしない代わりにクロスドメインXMLHttpRequestを許すような仕組みがあってもよさそう」というのをまさに体現したものということになる.

このようなプロキシサーバの実装例としてはたとえば404 Blog Not Found:Ajax - Goodbye, JSONP. Hello, Access-Control-Allow-Originがあるけれど, Perlスクリプトで実装というのはなんともイケてない感じがする. どうせApache等で動かすのだろうから, HTTPサーバのプロキシ機能でなんとかしたい.

Apache 2.2のmod_proxyにはProxyPassMatchというのがあるので, 実は簡単にできる. Perlなんて書かなくてもよかったんや! (Accss-Control-Allow-Originの設定のためにmod_headersも使う.)

<VirtualHost *:80>
    ServerName xdr.example.org

    ProxyPassMatch ^/http://(.*)$ http://$1
    <Proxy *>
        Allow from all
    </Proxy>

    Header add Access-Control-Allow-Origin "*"
</VirtualHost>

あるいは2.2.6より古い場合はmod_rewriteを併用して

<VirtualHost *:80>
    ServerName xdr.example.org

    RewriteEngine On
    RewriteRule ^/http://(.*)$ http://$1 [P]
    <Proxy *>
        Allow from all
    </Proxy>

    Header add Access-Control-Allow-Origin "*"
</VirtualHost>

などとする. こういうものを用意しておいて, http://example.net/hogehogeXMLHttpRequestする代わりにhttp://xdr.example.org/http://example.net/hogehogeXMLHttpRequestするようにすれば, ドメインを跨いでいても成功する.

上記の設定はオープンプロキシになっているので, 悪用されたらまずいと思うならAllow fromのところを適宜制限する.

参考資料

あとで調べたらApacheの設定方法も全く同じものが公開されていた:

追記: 2013-11-02

アクセス先がリダイレクトされてしまうと, Locationヘッダはそのまま返ることになって, ブラウザが改めて別ドメインにアクセスしようとして失敗してしまう. これを避けるには, レスポンスのLocationヘッダを書き換えればよい:

    Header edit Location ^(.*)$ http://xdr.example.org/$1

Headerディレクティブのeditは2.2.4からサポートされている.