CakePHPでSecurityコンポーネントを使っていると、時折発生するBlackHole。
発生時はエラーログを確認するしか詳細な原因がわからないのですが、多くの場合は以下のどれかに該当するのではないでしょうか。
- CSRFチェックのトークンがUseOnceなのにフォーム送信後にブラウザの「戻る」とか押してからフォームを再送した。
- フォームを生成してから30分過ぎた。(トークンのデフォルト有効期限が30分)
- JavaScriptでHidden項目を書き換えた。
- JavaScriptで入力項目を追加したり取り去ったりした。
ブラウザの戻るを想定する場合はトークンを複数回使えるように設定しなければいけません。(ただし、セキュリティー的には若干低下します)
複数回使いまわせるようにするにはControllerのbeforeFilterなどで以下のようにcsrfUseOnceをfalseにします。
$this->Security->csrfUseOnce = false;
また、トークンのデフォルト有効期限が短すぎると判断される場合はbeforFilterで
$this->Security->csrfExpires = '+1 hour';
とするか、または$componentsの設定で以下のように設定すると良いでしょう。
public $components = array( 'Security' => array( 'csrfExpires' => '+1 hour' ) );
そもそもCSRF対策不要だよ!ってアプリケーションでは、
public $components = array( 'Security' => array( 'csrfCheck' => false ) );
として、CSRF対策機能をばっさり無くすことも可能ですが、セキュリティー的に問題が残るため、多くの場合はおすすめできません。
あと、JavaScriptでフォームの中身を変更すると、ことごとくBlackHoleに吸い込まれる事がありますが、Securityコンポーネントを有効にするとデフォルトでついてくる機能「FormTamperingPrevention」のおかげです。
Cakeがフォームを生成した時にフォームの内容をまるごとハッシュ化しており、フォーム送信時にフォームの内容に差異が生じていると、フォームを不正に改変されたとみなしてBlackHoleに吸い込んでくれる機能です。
ただし、JavaScriptでHiddenに値をセットしたり、Ajaxを使う場合に項目を動的に追加したりなんかしてもBlackHoleに吸い込んでくれるので、そのような場合は以下の何れかの対策が必要です。
FormTamperingPreventionをまるっと無効にする場合はbeforeFilterで以下のように指定します。
$this->Security->validatePost = false;
また、特定のフィールドだけFormTamperingPreventionの対象外としたい場合(JavaScriptで値をセットするhidden項目など)は、ViewのFormHelperを用いて以下のように指定することで回避できます。
$this->Form->unlockFields('Model.field_name');
BlackHoleに吸い込まれた時の処理
BlackHoleに吸い込まれた場合、デフォルトではDebugモードにもよりますが、プロダクションモードでは真っ白なページが表示され、404エラーが返されてしまいます。
では実際に何らかの問題が生じてBlackHoleに吸い込まれるべくして吸い込まれた場合、どのようにハンドリングすればスマートでしょうか。
ここはひとまずCakePHPのCookBookにのっとってblackholeメソッドを用意し、そちらに処理を任せるようにしましょう。
public function beforeFilter() { $this->Security->blackHoleCallback = 'blackhole'; } public function blackhole($type) { // handle errors. }
上記はCookBookからの引用になります。またblackholeメソッドのパラメータ$typeは以下の値をとるようですが、内容はCookBookのほぼ直訳になります。
- ‘auth’ フォームバリデーションエラーまたはコントローラ/アクションの不一致によるエラー。
- ‘csrf’ CSRFエラー
- ‘get’ HTTPメソッド制限違反
- ‘post’ HTTPメソッド制限違反
- ‘put’ HTTPメソッド制限違反
- ‘delete’ HTTPメソッド制限違反
- ‘secure’ SSLメソッド制限違反
なので、blackholeメソッド側で$typeを参照し、エラー内容に沿ったメッセージを表示してあげると良さそうです。