CakePHP の AuthComponent の改造
CakePHP には AuthComponent というユーザー認証用のライブラリが標準で付いてくる。(1.2 からの機能らしいが) Rails でいうとちょうど ActsAsAuthenticated にあたるようなライブラリである。ちょっと使ってみたがなかなか便利。詳しい使い方については、以下のエントリがよい。
- 【CakePHP】AuthComponentについてのまとめ 【ざっくり基本編】
- 【CakePHP】AuthComponentについてのまとめ その2【ちょっとしたコツ編】
- 【CakePHP】AuthComponentについてのまとめ その3【ログイン後のリダイレクト編】
さて、AuthComponent なかなか素敵ではあるのだが、ちょっと困ったことがある。パスワードを自動的に暗号化してくれるのは便利なのだが、融通が利かないのだ。基本的には、 ソルト(salt)と平文パスワードを結合して、そのハッシュ値を暗号パスワードにしている。ただ、このソルト、app/config/core.php で定義された定義された定数にすぎない。ありがちな実装は、データベースのユーザーテーブル上に、salt というフィールドを用意して、各ユーザーごとに別のソルトを使うということがよく行われる。
これに対応するために、AuthComponent を改造してみた。
CakePHP は、コントローラのコンポーネントというものがある。これは、ざっくり言えば、コントロールの共通部品のようなものだ。共通関数のオブジェクト版とでも言おうか。AuthComponent は、もともと cake/libs/controller/components/auth.php に定義されている。これを app/controllers/components/ にコピーすると、こちらのファイルのほうが先に読み込まれるようになるようだ。そこで、コピー先の app/controllers/components/auth.php を修正していく。
まずは、startup() の
$this->data = $controller->data = $this->hashPasswords($controller->data);
を
$this->data = $controller->data;
とする。hashPasswords はパスワードを暗号化するのだが、この時点では、データベースからユーザーごとの salt が入手できないので、暗号化を先延ばしするための修正である。
次に、identify(), hashPasswords(), password() をすべてコメントアウト。
最後に次のコードを追加する。
<?php // はてな記法のための便宜 // 引数として与えられる $data の内容は // $data['User.username'], $data['User.password'] のようなものとだけ仮定。 // これと salt を組み合わせて暗号化パスワードを作り、データベースを検索する。 function identify($data) { $model =& $this->getModel(); $model_name = $this->userModel; $username_field = $this->fields['username']; $password_field = $this->fields['password']; $plain_password = $data[$model_name . '.' . $password_field]; $user = $model->find(array($username_field => $data[$model_name . '.' . $username_field])); $salt = $user[$model_name]['salt']; $hashed_password = $this->hashPassword($plain_password, $salt); if($user && $user[$model_name][$password_field] === $hashed_password) { return $user[$model_name]; } else { return null; } } function hashPassword($plain_password, $salt) { $source = "--{$salt}--{$plain_password}--"; return md5($source); }
hashPassword() では、平文パスワードとソルトに基づいて、暗号パスワードを作る。md5 を使ったのは一例にすぎず、好みのハッシュ関数を使えばいい。 startup() -> login() -> identify() という感じの呼び出し関係である。startup() のなかで、$data が用意されるのだが、上のように $data['User.email'], $data['User.password'] というキーでフォームの値が入ってくる。
上のコードはあくまでも実装例にすぎない。実際には、AuthComponent は ACL を使った複雑なアクセス制限など、もっと多様な機能を持っている。しかし、「ログインしているか否か」だけが問題になるアプリも多いので、やや無意味に複雑な感じもしなくもない。そういうときは、ソースをいじって使わない機能は削ってしまってもいいかもしれない。AuthComponent の改造はそれほど難しくないはずだ。
Rails は Ruby の柔軟さ(≒邪悪さ)を極限まで駆使し、技巧の限りを尽くして作り上げられている。まさに黒魔術だ。Rails は呼び出し階層が深い上、識別子がローカル変数なのかメソッド呼び出しなのか一瞬はっきりしない。そのメソッドも、ありとあらゆるところから動的にロードされているので、元のメソッド定義を見つけるのに一苦労である。
その点 CakePHP のソースコードは、思ったより読みやすい。PHP は、Ruby のような凝ったことができないので、表記は冗長で美しくはないが、その分、とっつきやすい。Rails のソースコード全解読は、その複雑さの前に挫折したが、CakePHP なら短期間でかなりのソースが読みこなせるかもしれない。