Phalconのチュートリアル(チュートリアル 1)にCSRF対策を追加してみる
この記事はPhalcon Advent Calendar 2014の16日目です。昨日はyohgakiさんの「Phalcon 1.3 と 2.0のベンチマーク」でした。
「Phalconのチュートリアル(チュートリアル 1)」には、CSRF対策がありませんでした。そこで追加してみようと思います。
PhalconでのCSRF対策についての公式ドキュメントは以下になります。
フォームにトークンを追加
まず、フォームにトークンを追加します。
--- a/app/views/signup/index.phtml
+++ b/app/views/signup/index.phtml
@@ -4,6 +4,10 @@
<?php echo Tag::form("signup/register"); ?>
+<!-- CSRF対策のトークン -->
+<input type="hidden" name="<?php echo $this->security->getTokenKey() ?>"
+ value="<?php echo $this->security->getToken() ?>"/>
+
<p>
<label for="name">Name</label>
<?php echo Tag::textField("name") ?>
POSTメソッドのリクエストでトークンをチェック
コントローラで$this->security->checkToken()
メソッドを使い、トークンをチェックすればいいのですが、今回は、すべてのPOSTメソッドで自動的にトークンをチェックするようにしてみます。
--- a/public/index.php
+++ b/public/index.php
@@ -11,6 +11,47 @@ try {
//Create a DI
$di = new Phalcon\DI\FactoryDefault();
+ // CSRF対策にセッションを使うので自動でスタートするように
+ $di->setShared('session', function() {
+ $session = new \Phalcon\Session\Adapter\Files();
+ $session->start();
+ return $session;
+ });
+
+ // CSRF対策をディスパッチャーで実装
+ $di->set('dispatcher', function() use ($di) {
+ $eventsManager = new Phalcon\Events\Manager();
+ // beforeDispatchLoopにイベントを設定
+ $eventsManager->attach(
+ "dispatch:beforeDispatchLoop",
+ function($event, $dispatcher) use ($di) {
+ // POSTメソッドのみを対象に
+ if ($di->get('request')->getMethod() !== 'POST') {
+ return;
+ }
+ // トークンをチェックして例外を投げる
+ if (! $di->get('security')->checkToken()) {
+ throw new \Exception('Token error!');
+ }
+ }
+ );
+ // 例外を受けるためにbeforeExceptionにイベントを設定
+ $eventsManager->attach(
+ "dispatch:beforeException",
+ function($event, $dispatcher, $exception) {
+ // トークンエラーの例外の場合、そのままその例外を投げる
+ if ($exception->getMessage() === 'Token error!') {
+ throw $exception;
+ }
+ }
+ );
+
+ $dispatcher = new Phalcon\Mvc\Dispatcher();
+ $dispatcher->setEventsManager($eventsManager);
+
+ return $dispatcher;
+ });
+
//Setup the database service
$di->set('db', function(){
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
@@ -38,6 +79,7 @@ try {
//Handle the request
$application = new \Phalcon\Mvc\Application($di);
echo $application->handle()->getContent();
-} catch(\Phalcon\Exception $e) {
- echo "PhalconException: ", $e->getMessage();
+} catch(\Exception $e) {
+ // 最終的にすべての例外をここでキャッチ
+ echo "Exception: ", $e->getMessage();
}
これで完了です。
解説
まず、CSRF対策ではセッションを使うため、セッションを自動的に開始するようにします。
// CSRF対策にセッションを使うので自動でスタートするように
$di->setShared('session', function() {
$session = new \Phalcon\Session\Adapter\Files();
$session->start();
return $session;
});
次に、トークンのチェックをディスパッチャーに実装します。
beforeDispatchLoop
イベントで、POSTメソッドの場合だけトークンをチェックし例外を投げます。
例外が投げられるとbeforeException
イベントが発生するので、そこでトークンエラーの例外の場合、そのまま再度受けた例外を投げています。とりあえずException
クラスを使っていますが、実際には専用の例外クラスを作成するのがよいでしょう。
// CSRF対策をディスパッチャーで実装
$di->set('dispatcher', function() use ($di) {
$eventsManager = new Phalcon\Events\Manager();
// beforeDispatchLoopにイベントを設定
$eventsManager->attach(
"dispatch:beforeDispatchLoop",
function($event, $dispatcher) use ($di) {
// POSTメソッドのみを対象に
if ($di->get('request')->getMethod() !== 'POST') {
return;
}
// トークンをチェックして例外を投げる
if (! $di->get('security')->checkToken()) {
throw new \Exception('Token error!');
}
}
);
// 例外を受けるためにbeforeExceptionにイベントを設定
$eventsManager->attach(
"dispatch:beforeException",
function($event, $dispatcher, $exception) {
// トークンエラーの例外の場合、そのままその例外を投げる
if ($exception->getMessage() === 'Token error!') {
throw $exception;
}
}
);
$dispatcher = new Phalcon\Mvc\Dispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
最終的に、トークンエラーの例外はindex.php
の最後のcatch句でキャッチされます。
…略…
} catch(\Exception $e) {
// 最終的にすべての例外をここでキャッチ
echo "Exception: ", $e->getMessage();
}
と、こんな感じでいいのかなと思いますが、まだあんまりよくわかってませんので、間違いがありましたらご指摘ください。
この記事はPhalcon Advent Calendar 2014の16日目です。明日は、yohgakiさんの「Phalcon PHPとSails Node.jsのベンチマーク」です。あと、誰かドキュメントの翻訳について書いてくださいまし。
関連
Date: 2014/12/16