CakePHP

CakePHP2.1 SecurityコンポーネントとAjax通信

Securityコンポーネントを利用するコントローラに、jQuery のAjax関数「$.post()」でアクションを実行しようとしても、しっかりとブラックホール行きとなり期待する処理ができない。どうするか、以下は自分で試してみての解決方法です。

先ず、セキュリティを回避してアクションを実行するには、以下のように「beforeFilter()」でSecurityのプロパティに値を設定すればいいようだ。

public function beforeFilter() {
    parent::beforeFilter();

    if ($this->params['action'] == アクション名) {
         $this->Security->csrfCheck = false;
         $this->Security->validatePost = false;
    }
}

しかしこのままの利用はセキュリティ上好ましくないので、Securityコンポーネントが出力するワンタイムトークンの利用を考えてみた。その方法は、ビュー内に出力されたトークンをjQueryで拾い上げ、サーバー側にPOSTデータが渡されたとき、セッションに保存されているトークンと比較する方法である。

以下はJavaScriptでトークンを拾い、Ajaxで送信する例。

var params = {
    'data[Comment]' : $("input[name='data[Comment]']").val(),
    'data[Token][key]' : $("input[name='data[_Token][key]']").val()
}

$.post('/comments/add', params, function(data) {
    alert(data);
});

Ajaxで送られたデータを受け取って処理する側「/comments/add」では、beforeFilter()でセキュリティを無効にしてadd()でトークンを確認すればよい。トークンはセッションデータに保存されているので、次のようにして読み出す。

$this->token = Session.read('_Token.key');

注意しなければならないのは、add()で読み出してもトークンは既に新しく書き直されているため、決して送ったトークンと一致する事はないことだ。そこで書き直される前、「beforeFilter()」で必ず読み出しておく必要がある。add()アクションでは次のように比較して確認する。

if ($this->request->data['Token']['key'] == $this->token)
 :

さてここで困った事に。それは、1回ならこのAjax通信は処理できる。が、続けての通信はセッションデータ内のトークンが書き直されているため、エラーになってしまうのである。

これを回避するための考え付いた方法が、Ajax通信する前の最初のアクションでセッションにトークンを保存しておき、比較はCakePHPが保存したトークンを使わず、自分で保存したトークンを使うのである。

これで取り敢えず解決した。

が、またまた問題である。

ブラウザで同じ画面を複数開くと、自分で保存したトークンの値が書き直されてしまうため、新しく開いた画面では処理可能だが、前の画面ではエラーになるのである。

皆さんならどのように解決するでしょうか?

自分の考え付いた方法はトークンの値として、Security.saltとセッションIDのハッシュ値を利用する方法です。これならログイン中は、複数画面で同じAjax処理が可能になるのではないかと。