CakePHP2から5に乗り換えた話 〜ハッシュ化されたパスワードを書き換える〜

LegacyPasswordHasherからDefaultPasswordHasherへ

この記事を三行にまとめると

DefaultPasswordHasherでログインチェック
LegacyPasswordHasherでログインチェック
DefaultPasswordHasherでハッシュ化
以前LegacyPasswordHasherを使ってCakePHP2のユーザーデータをそのまま使ってログインする方法を書きました。
CakePHP2から5に乗り換えた話 〜2のパスワードを5でそのまま使う〜

これでも問題ないとは思うんですが、たぶん新しいハッシュ方式の方がセキュリティも強固だろうし、それならパスワードも新しいものに書き換えてLegacyPasswordHasherを使わずにログインできるようにしたいところですよね。でもハッシュ化ってのは復元できない変換なのでハッシュ化されたパスワードを見ても元のパスワードは分かりません。

そこで以下のような手順でLegacyPasswordHasherのパスワードをDefaultPasswordHasherに書き換える方法を考えてみました。

1.データベースのテーブルにoldpasswordみたいなフィールドを追加してLegacyPasswordHasherをそっちに移し替える
2.DefaultPasswordHasherでのログインに失敗したらLegacyPasswordHasherのパスワードで再度認証処理を試みる
3.認証に成功したらDefaultPasswordHasherでハッシュ化したものをpasswordフィールドに登録してログイン
4.LegacyPasswordHasherでも認証失敗したらログインエラー

一応これで元のパスワードが分からなくても再登録みたいなことをせずにログインし直すことができますし、以後はDefaultPasswordHasherの方でログインできるようになります。

1はデータベースに関する話なのでここでは省略して、2以降の処理を見ていきたいと思います。

なお今回はUsersController.phpのloginアクションでログイン処理を行うものとし、ユーザーデータはusersというテーブルに入っているものとします。



ログイン失敗時の再認証処理

認証の処理をざっくり書くとこんな感じです。

$result = $this->Authentication->getResult();

// ログインに成功していれば/users/indexにリダイレクト
if($result && $result->isValid()) {
  return $this->redirect('/users/index');
}

// ログイン失敗
if($this->request->is('post') && !$result->isValid()) {
  $this->Flash->error('IDかパスワードが違います');
}

今回やろうとしていることは、このログイン失敗時のときにLegacyPasswordHasherでの認証を自作するというものです。

なのでログイン失敗時の処理をこのように書き換えます。

if($this->request->is('post') && !$result->isValid()) {
  if($user = $this->Users->LegacyPasswordCheck($this->request->getData())) {
    $this->Authentication->setIdentity($user);
    return $this->redirect('/admin/users/index');
  } else {
    $this->Flash->error('IDかパスワードが違います');
  }
}

LegacyPasswordCheckという関数を呼び出してPOSTデータを渡し、LegacyPasswordHasherでのチェックを試みるという内容です。それでOKならAuthenticationのsetIdentityでログイン済みの状態にしてリダイレクトし、NGならエラーメッセージを出します。



LegacyPasswordCheck関数

LegacyPasswordCheckはUsersTable.phpに書きます。コードはこんな感じ。

namespace App\Model\Table;

use App\Model\Table\AppTable;
use Authentication\PasswordHasher\LegacyPasswordHasher;
use Authentication\PasswordHasher\DefaultPasswordHasher;

class UsersTable extends AppTable {
  public function LegacyPasswordCheck($data) {
    // 古いパスワードでのログインチェック
    $query = $this->find()->where([
      'username' => $data['username'],
      'oldpassword' => (new LegacyPasswordHasher)->hash($data['password']),
    ]);
    $result = $query->first();

    // データが取得できなければログイン失敗
    if(empty($result)) {
      return false;
    }

    // 新しいパスワードをセット
    $data['password'] = (new DefaultPasswordHasher)->hash($data['password']);
    $entity = $this->patchEntity($result, $data);
    return $this->save($entity);
  }
}

ここではusernameとoldpasswordというフィールドでログイン認証を行うという想定です。フォームから飛んできたパスワードをLegacyPasswordHasherでハッシュ化して一致するデータがあるかどうかを検証します。一致するデータがあればログイン成功と見なすので、パスワードをDefaultPasswordHasherでハッシュ化してpasswordフィールドに登録します。

これでpasswordに新しいハッシュ化されたパスワードが登録されるので、次からはDefaultPasswordHasherの方でログインできるようになります。






別にoldpasswordに古いパスワードを入れ直さなくてもpasswordフィールドで二回チェックしてOKなら上書きとかでも同じことはできますが、例えばCakePHP2からCakephp5への移行をやる際、一気にシステム全体を置き換えられるなら良いんですが、サービスの規模がでかいと少しずつ改修する感じでしばらく2と5の両方にアクセスできる状態になる場合もあると思うので、今回は念のため書き換えた後もoldpasswordでもログインできるような形にしました。

これがベストプラクティスかは分かりませんが、実際にこの方法で試したところ無事にDefaultPasswordHasherでの書き換えには成功しました。書き換える方法がなくて困っているときにはお試しあれ。

その他のCakePHP5に乗り換えた話はこちら
ようやくのぼりはじめたはてしなく遠い開発坂
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください