-
アカウントロック解除機能の実装
前回の続きです。
##はじめに
要件をまとめます。
- アカウントがロックされたとき、ロックされたアカウントのユーザー宛てに、メールを送信する。
- アカウントロック解除ページのURLはメールで通知する。
- アカウントロック解除ページの「解除」ボタンを押下すると、アカウントロックを解除する。
アカウントロックは、当然ログインに成功していないユーザーが利用する機能ですので、セッションを用いてユーザーを特定することはできません。
そこで、「解除コード」のようなキーを用いてユーザーを特定することになります。
「解除コード」はアカウントがロックされたタイミングで発行し、DBに保存しておく必要があります。
UserModel.class.php
に定義されている、loginFailureIncrement()
にその処理を足しておきましょう。
tbl_user
テーブルのtoken
にその値を書き込みます。
/**
* ログイン失敗をインクリメントする
* 指定回数(self::LOCK_COUNT)に満たないときのみ+1
* @return bool
*/
public function loginFailureIncrement()
{
$count = $this->getLoginFailureCount();
if (self::LOCK_COUNT > $count) {
$now = (new \DateTime())->format('Y-m-d H:i:s');
$this->setLoginFailureCount(1 + $count)
->setLoginFailureDatetime($now);
// 追加
if (self::LOCK_COUNT === 1 + $count) {
$token = sha1(uniqid());
$this->setToken($token);
}
return $this->save();
}
//ログイン失敗が設定以上のとき
return true;
}
##HTMLの作成
最初に、アカウントロック解除ページ(unlock.php)を作成します。
{block name='meta'}
<style type="text/css">
.login-box, .register-box {
margin: 20px auto;
}
</style>
{/block}
{block name='content'}
<div class="login-box">
<div class="login-logo">
<a href="/">
<b>Admin</b>
LTE
</a>
</div>
<!-- /.login-logo -->
<div class="login-box-body">
<p class="login-box-msg">ロックを解除するには、下のボタンを押してください。</p>
<form action="" method="post">
<div class="row">
<div class="col-xs-6 col-xs-offset-3">
<button type="submit" class="btn btn-primary btn-block btn-flat">
ロックを解除する
</button>
</div>
</div>
</form>
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
{/block}
{block name='script'}
{/block}
##PHP
この解除ページは、アカウントロックがされていないユーザーに表示する必要はありませんので、トークンが有効であるかどうかを判断して、必要がない場合には、非表示にしたいところです。
そこで、このページのURLは、以下のように表現しておきましょう。
http://www.example.com/unlock.php?token=(解除キー)
トークンキーが一致しているユーザーからモデルを取得すれば、ユーザーを特定できます。逆にできなければ、このページを非表示にします。
基本的には、Controllerに記述します。
追加
/**
* トークンからユーザーモデルを取得し、ロック中かどうかを判定する
* @return boolean
*/
public static function isAccountLock()
{
$token = filter_input(INPUT_GET, 'token');
$objUserModel = new UserModel();
$objUserModel->getModelByToken($token);
return $objUserModel->isAccountLock();
}
追加
/**
* トークンからユーザーを検索する
* @param string $token
* @return \MyApp\model\UserModel
*/
public function getModelByToken($token)
{
$dao = UserDao::getDaoFromToken($token);
return (isset($dao[0])) ? $this->setProperty(reset($dao)) : null;
}
追加
/**
* トークンから配列を取得する
* @param type $token
* @return array
*/
public static function getDaoFromToken($token)
{
$sql = "SELECT ";
$sql .= "`userId`";
$sql .= ", `password`";
$sql .= ", `displayName`";
$sql .= ", `email`";
$sql .= ", `token`";
$sql .= ", `loginFailureCount`";
$sql .= ", `loginFailureDatetime`";
$sql .= ", `deleteFlag` ";
$sql .= "FROM `tbl_user` ";
$sql .= "WHERE `token` = :token ";
$sql .= "AND `deleteFlag` = 0 ";
$arr = array();
$arr[':token'] = $token;
return Db::select($sql, $arr);
}
<?php
/**
* unlock.php
*/
namespace MyApp;
use MyApp\controller\LoginController;
use MyApp\common\Template;
define('LAYOUT', 'index');
try {
require_once '../common.php';
// アカウントロック判定
Template::assign('is_lock', LoginController::isAccountLock());
} catch (\Exception $e) {
Template::exception($e);
} finally {
Template::display();
}
is_lock
を使ってページの表示を切り替えます。
テンプレートを以下のように変更します。
{block name='meta'}
<style type="text/css">
.login-box, .register-box {
margin: 20px auto;
}
</style>
{/block}
{block name='content'}
<div class="login-box">
<div class="login-logo">
<a href="/">
<b>Admin</b>
LTE
</a>
</div>
<!-- /.login-logo -->
<div class="login-box-body">
{if $is_lock}
<p class="login-box-msg">ロックを解除するには、下のボタンを押してください。</p>
<form action="" method="post">
<div class="row">
<div class="col-xs-6 col-xs-offset-3">
<button type="submit" class="btn btn-primary btn-block btn-flat">
ロックを解除する
</button>
</div>
</div>
</form>
{else}
<p class="login-box-msg">アカウントはロックされていません。</p>
<p class="login-box-msg">
<a href="/">ログインページ</a> よりログインしてください。
</p>
{/if}
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
{/block}
{block name='script'}
{/block}
##ロック解除・メソッドの実装
これも入り口となるメソッドはコントローラーに記述します。
unlock()
という名前にしておきましょう。この関数が true
を返したときに、テンプレートには「アカウントロックを解除しました」とメッセージを表示します。
/**
* ロックを解除する
* @return boolean | null
*/
public static function unlock()
{
if (null == filter_input_array(INPUT_POST)) {
return;
}
$token = filter_input(INPUT_GET, 'token');
Db::transaction();
$objUserModel = new UserModel();
$objUserModel->getModelByToken($token);
$objUserModel->setLoginFailureCount(0)
->setLoginFailureDatetime(NULL)
->setToken('')
->save();
DB::commit();
return true;
}
<?php
/**
* unlock.php
*/
namespace MyApp;
use MyApp\controller\LoginController;
use MyApp\common\Template;
define('LAYOUT', 'index');
try {
require_once '../common.php';
Template::assign('is_lock', LoginController::isAccountLock());
Template::assign('success', LoginController::unlock()); // 追加
} catch (\Exception $e) {
Template::exception($e);
} finally {
Template::display();
}
{block name='meta'}
<style type="text/css">
.login-box, .register-box {
margin: 20px auto;
}
</style>
{/block}
{block name='content'}
<div class="login-box">
<div class="login-logo">
<a href="/">
<b>Admin</b>
LTE
</a>
</div>
<!-- /.login-logo -->
<div class="login-box-body">
{if $success|default:null}
<p class="login-box-msg">アカウントロックを解除しました。</p>
<p class="login-box-msg">
<a href="/">ログインページ</a> よりログインしてください。
</p>
{else}
{if $is_lock}
<p class="login-box-msg">ロックを解除するには、下のボタンを押してください。</p>
<form action="" method="post">
<div class="row">
<div class="col-xs-6 col-xs-offset-3">
<button type="submit" class="btn btn-primary btn-block btn-flat">
ロックを解除する
</button>
</div>
</div>
</form>
{else}
<p class="login-box-msg">アカウントはロックされていません。</p>
<p class="login-box-msg">
<a href="/">ログインページ</a> よりログインしてください。
</p>
{/if}
{/if}
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
{/block}
{block name='script'}
{/block}
ひと通りの機能で中心的な部分を実装したことになりますが、これだけでは不十分です。このままではまだ、CSRF
の問題があります。
これいついては、まだ続きで実装してゆきましょう。