CakePHP の AuthComponent の改造

CakePHP には AuthComponent というユーザー認証用のライブラリが標準で付いてくる。(1.2 からの機能らしいが) Rails でいうとちょうど ActsAsAuthenticated にあたるようなライブラリである。ちょっと使ってみたがなかなか便利。詳しい使い方については、以下のエントリがよい。

さて、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 の改造はそれほど難しくないはずだ。

RailsRuby の柔軟さ(≒邪悪さ)を極限まで駆使し、技巧の限りを尽くして作り上げられている。まさに黒魔術だ。Rails は呼び出し階層が深い上、識別子がローカル変数なのかメソッド呼び出しなのか一瞬はっきりしない。そのメソッドも、ありとあらゆるところから動的にロードされているので、元のメソッド定義を見つけるのに一苦労である。

その点 CakePHPソースコードは、思ったより読みやすい。PHP は、Ruby のような凝ったことができないので、表記は冗長で美しくはないが、その分、とっつきやすい。Railsソースコード全解読は、その複雑さの前に挫折したが、CakePHP なら短期間でかなりのソースが読みこなせるかもしれない。