Auth コンポーネントのパスワードハッシュ化回避
Auth コンポーネントの使用時に、
[/users/add] や [/users/edit/
で、パスワードを入力させて、バリデーションをしたいとき、
アクションの段階では、既にパスワードがハッシュ化されているため、
バリデーションがやりづらい問題を回避します。
結論は、
Auth コンポーネントの
「標準の AuthComponent::password 以外のハッシュ関数を使用する機能」を使って、
「ハッシュ化しないハッシュ関数」を作るという方法です。
参考
- 【CakePHP】AuthComponentについてのまとめ その2【ちょっとしたコツ編】
- http://blog.ne2ma2.com/archives/161
- 一番初めの着想を得たエントリー。
- ここから、自作のコンポーネントを作る。(「password」を「_password」に退避して、コンポーネント側で、バリデートとかしていた。)
- しかし、view が不自然(「_password」のフォーム)で、スッキリとせず、悶々とする。
- CookBook 5.2.6.13 authenticate
- CookBook 5.2.4 Change Hash Function
- http://book.cakephp.org/view/566/Change-Hash-Function
- 執筆時点(2010-06-10)の段階では、日本語版にはサンプルが乗っていませんでした。
テーブルの作成
- mysql のサンプルです。
-- -- テーブルの構造 `users` -- CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `created` datetime DEFAULT NULL, `modified` datetime DEFAULT NULL, `username` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `password` varchar(200) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=36 ;
なにをやっているの?
Auth コンポーネントは、
デフォルトの設定は、以下の様になっています。
<?php // 認証に使うモデル $this->Auth->userModel = 'User'; // ユーザー名 と パスワードのフィールド名 $this->Auth->fields = array('username' => 'username', 'password' => 'password');
コントローラーの「add」や「edit」のアクションを実行する前の段階(beforeFilterの後)で、
$this->data['User']['password']
($this->data[$this->Auth->getModel()->alias][$this->Auth->fields['password']])
をハッシュ化します。
ただ、デフォルトでは、ハッシュ化を止める方法はありません。
また、バリデートをする際には、
モデル内では、$this->data[$model->alias] のパスが同じく使用されるため、
ハッシュ化したものをチェックすることになってしまいます。
どう回避するのか
モデルや password フィールドを退避する、など様々回避方法は考えられますが、
そこは、cakephp 、丁度よい機能があります。
AuthComponent::hashPasswords のソースです。
<?php function hashPasswords($data) { // 標準の AuthComponent::password (Security::hash のラッパ)以外のハッシュ関数を使用する機能 // $this->authenticate がオブジェクトで、($this->authenticate のデフォルトは 「null」) // そのオブジェクトが、'hashPasswords' というメソッドを持っていれば、そちらを実行して結果を返す。 if (is_object($this->authenticate) && method_exists($this->authenticate, 'hashPasswords')) { return $this->authenticate->hashPasswords($data); } if (is_array($data)) { $model =& $this->getModel(); if(isset($data[$model->alias])) { if (isset($data[$model->alias][$this->fields['username']]) && isset($data[$model->alias][$this->fields['password']])) { $data[$model->alias][$this->fields['password']] = $this->password($data[$model->alias][$this->fields['password']]); } } } return $data; }
つまり、
標準の AuthComponent::password 以外のハッシュ関数を使用する機能を使って、
「何もしないハッシュ関数」を作るという方法です。
ソース
add アクションのみ解説しています。
view は、記載していませんが、
bake で焼いたもので、変更を加えず利用できます。
<?php // [app/models/user.php] class User extends AppModel { // var $validate = array( 略 ) // 「何もしない」ハッシュ関数 // 何も手を加えず、返すだけ。これだけ。 function hashPasswords($data) { return $data; } } // [app/app_controller.php] // Auth コンポーネントを読み込みます。 // ログイン状態での作業が多いという想定で、ここに記述していますが、この辺はお好みで。 class AppController extends Controller { var $components = array( 'Security', 'Auth', 'Session', //{{{!debug 'DebugKit.Toolbar', //}}}!debug ); function beforeFilter() { // 共通のログイン不要なアクション $this->Auth->allow('index', 'view'); } } // [app/controllers/users_controller.php] class UsersController extends AppController { // ここでは、「ユーザーの一覧や個々のページは、ログインを必要とする」ということにしているので、 // parent::beforeFilter() は記述していません。 function beforeFilter() { // ログインしていない一般ユーザー登録を可能にするので、 // 「add」を許可します。 $this->Auth->allow('add'); // 'add' と 'edit' のアクションについては、ハッシュ化させないため、場合分けをしています。 // no hash passowrd action if (in_array($this->params['action'], array('add', 'edit'), true)) { // 移譲するハッシュ関数を定義したモデルをセットします。 $this->Auth->authenticate = ClassRegistry::init('User'); } } function add() { // $this->Auth->authenticate を設定しない(デフォルトの「null」)場合は // この時点で、既に「password」はハッシュ化されています。 // 「何もしない」ハッシュ関数で、入力されたままの「password」を入手できます。 if (!empty($this->data)) { // モデルの $this->data などを初期化します。 // 手動でバリデートを行う前に、バリデートをするデータをモデルに伝えます。 $this->User->create($this->data); // $this->data (トークン値などは略) // → array( // 'User' => array( // 'username' => '<入力したユーザー名>', // 'password' => '<入力した平パスワード>' // ) // ) // 手動でバリデートを行います。 // バリデートに成功したならば、ハッシュ化をして保存を試みます。 // 手動で行わない場合は、 // User::beforeSave で、コントローラーの // $this->data[$this->Auth->getModel()->alias][$this->Auth->fields['password']] // に当たる部分を Security::hash($password, null, true) で上書きことで対応できますが、 // モデル内で、「$this->Auth->fields['password']」に当たる部分を動的に取得する方法が困難です。 // 保存する値ですので、「決め打ち」はできます。 if ($this->User->validates()) { // Set クラスで使用するパスを作ります。 // Auth コンポーネントのデフォルトの設定のため、 // モデルは、「User」。パスワードフィールドは、「password」です。 // $path // → 'User.password' $path = $this->Auth->getModel()->alias . '.' . $this->Auth->fields['password']; // 分けて解説します。 // $this->data (トークン値などは略) // → array( // 'User' => array( // 'username' => '<入力したユーザー名>', // 'password' => '<入力した平パスワード>' // ) // ) // // $this->data から パスに相当する値を取得します。 // $this->data['User']['password'] と同じです。 // Set::extract($this->data, $path) // → Set::extract($this->data, 'User.password') // → <入力した平パスワード> // // パスワードをハッシュ化します。 // $this->Auth->password(Set::extract($this->data, $path))) // → $this->Auth->password('<入力した平パスワード>') // → Security::hash('<入力した平パスワード>', null, true) // → <ハッシュ化されたパスワード> // // ハッシュ化したパスワードを上書きしています。 // $this->data['User']['password'] = $this->Auth->password($this->data['User']['password']) // と同じです。 // $this->data の パス('User.password') に データ('<ハッシュ化されたパスワード>') を挿入します。 // Set::insert($this->data, $path, $this->Auth->password(Set::extract($this->data, $path))) // → Set::insert($this->data, 'User.password', '<ハッシュ化されたパスワード>') // → array( // 'User' => array( // 'username' => '<入力したユーザー名>', // 'password' => '<ハッシュ化されたパスワード>' // ) // ) $this->data = Set::insert($this->data, $path, $this->Auth->password(Set::extract($this->data, $path))); // 保存をします。 // モデル側に既にある $this->data の $this->data['User']['password'] はハッシュ化されていないため、 // 第一引数に、ハッシュ化したパスワードを含んだデータを渡すことで、 // ハッシュ化したパスワードを保存できます。 // 既にバリデート済みのため、第二引数に、「false」を与えることで、バリデートをせず、保存を試みます。 if ($this->User->save($this->data, false)) { $this->Session->setFlash(__('The User has been saved', true)); $this->redirect(array('action' => 'index')); } } $this->Session->setFlash(__('The User could not be saved. Please, try again.', true)); } }
変更履歴-2010-06-11
- 「$this->User->create($this->data);」が抜けていたのを修正
- 上記に加え、解説コメントを追加