
2010年2月8日
CakePHPはデータベース周りの処理がとにかく楽ちんなのですが、一度確認画面を挟んだり確認メールを挟んだりすると、コントロールが難しくなります。
そこで、ここではどちらも実現したサンプルを作ってみました。以下からご覧下さい。 http://h2o-space.com/dev/user_add/
まず、メールアドレスとパスワードを設定すると確認画面に移動します。書きなおすことも出来ます。 続いて、確認メールを送信して送られるURLをクリックすると正式登録ができるという仕組みです。
順番に作っていきましょう。
まずは、データベースを準備します。次のテーブルを作成しましょう。
CREATE TABLE `ua_users` ( `id` int(11) NOT NULL auto_increment, `status` tinyint(4) default NULL, `username` varchar(255) default NULL, `password` varchar(255) default NULL, `keycode` varchar(20) default NULL, `created` datetime default NULL, `modified` datetime default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CakePHPの Authコンポーネントを使うため、「username」と「password」というフィールド名を作っておきます。他に重要なのは「status」と「keycode」です。これらはこの後解説します。
次に、CakePHPをダウンロードして、/app/config/core.phpの「Security.salt」の設定と「database.php」を設定して行きます。このあたりはさまざまなサイトで解説があるので、そのあたりを参考に。
続いてコンポーネントを追加します。ここでは、電子メールの送信にQdmailを使わせていただきます。
ダウンロードして「/app/controllers/components」に「qdmail.php」をコピーします。
続いて、Bakeなどを使ってUserのコントローラーやモデルを追加して行きます。
/app/models/user.php
class User extends AppModel {
var $name = 'User';
var $validate = array(
'username' => array(
'rule' => 'email',
'required' => true,
'message' => '* メールアドレスを記入してください'
),
'password_s' => array(
'rule' => array('minLength', 6),
'required' => true,
'message' => '* パスワードは 6文字以上で記入してください'
)
);
}
/app/controlls/user_controller.php
class UsersController extends AppController {
var $name = 'Users';
var $components = array('Session', 'Auth', 'Qdmail');
function beforeFilter() {
$this->Auth->allow('*');
}
}
モデルでは、「username」と「password_s」にそれぞれ validateを設定します。コントローラーでは、「Session」「Auth」と先程組み込んだ「Qdmail」コンポーネントを読み込んで、beforeFilterで「Auth」の「allow」で全部のアクションに許可をします。 こうしないと、ログイン画面に飛ばそうとしてしまいます。
それでは、登録画面を作りましょう。ビューを準備します。
/views/add.ctp
<h2>ユーザー登録</h2>
<?php echo $form->create('User', array('action'=>'add')); ?>
<dl>
<dt>メールアドレス</dt>
<dd><?php echo $form->text('User.username') . $form->error('User.username'); ?></dd>
<dt>パスワード</dt>
<dd><?php echo $form->password('User.password_s') . $form->error('User.password_s'); ?></dd>
</dl>
<?php echo $form->submit('登録内容を確認する'); ?>
<?php echo $form->end(); ?>
ここでのポイントは、パスワードのフィールドが「password_s」というフィールドになっていて、データベースと一致していません。 これは、Authコンポーネントを使う弊害で、Authコンポーネントは「password」というフィールド名を見ると、すぐに暗号化をしてしまいます。しかも、不可逆暗号なのでもとに戻すことも出来ません。
これでは、ユーザー登録時にパスワードの文字数チェックが出来ないのでフィールド名を変えているのです。
コントローラーの「add」アクションは次のようになります。
/app/controllers/users_controller.phpに追加
function add($action = null) {
// submit
if (!empty($this->data)) {
$this->User->set($this->data);
if ($this->User->validates()) {
$this->data['User']['password_s'] = $this->Auth->password($this->data['User']['password_s']);
$this->Session->write('user', $this->data);
$this->set('data', $this->data);
$this->render('check');
}
}
// rewrite
if ($action == 'rewrite' && $this->Session->check('user')) {
$this->data = $this->Session->read('user');
// remove 'password_s'
$this->data['User']['password_s'] = '';
}
}
確認画面に移動するために、登録された内容をセッションに記録します。このとき、password_sはAuthコンポーネントを使って暗号化してから記録しておきます。
続いて確認画面を作ります。
/views/check.ctp
<h2>登録内容の確認</h2>
<?php echo $form->create('User', array('action'=>'check')); ?>
<dl>
<dt>メールアドレス</dt>
<dd><?php echo h($data['User']['username']); ?></dd>
<dt>パスワード</dt>
<dd>【表示しません】</dd>
</dl>
<?php echo $html->link('≪書き直す', 'add/rewrite'); ?>
<?php echo $form->submit('確認メールを送信する'); ?>
<?php echo $form->end(); ?>
この時点で、すでにパスワードは表示できなくなっているため、ここでは表示していません。
「書き直す」というリンクは「add」アクションに「rewrite」というパラメータを付加してリンクします。 先程のプログラムで、addアクションは「$action」パラメータが「rewrite」でかつ、セッションにデータが記録されていたら、それを復元するという処理を入れています。
ただし、パスワードは再現出来ないため空白にして改めて記入してもらいます。
コントローラーにも「check」アクションを作ります。
/app/controllers/users_controller.phpに追加
function check() {
if (!$this->Session->check('user')) {
$this->redirect('add');
}
$this->data = $this->Session->read('user');
// delete already data
$this->User->deleteAll(array('username'=>$this->data['User']['username']));
// create keycode
$this->data['User']['keycode'] = $this->Utility->getRandomString('10', 'num_char');
$this->data['User']['status'] = '0';
$this->data['User']['password'] = $this->data['User']['password_s'];
// pre add
if (!$this->User->save($this->data)) {
$this->redirect('add');
}
// send a comfirmation mail
$from = 'support@h2o-space.com';
$subject = 'ユーザー登録の確認';
$body = "次のURLをクリックして、ユーザー登録を完了してください。\n{url}";
$this->Qdmail->to($this->data['User']['username']);
$this->Qdmail->from($from);
$this->Qdmail->subject($subject);
$body = r('{url}', Router::url('/', true) . 'users/active/' . $this->data['User']['keycode'] , $body);
$this->Qdmail->text($body);
$this->Qdmail->send();
$this->set('email', $this->data['User']['username']);
$this->render('check_mail');
}
ここが最難関。まずはセッションをチェックして、きちんと記録されていたらデータベースに仮登録をします。 このとき、連続して何度も登録されている場合のために、すでに登録されているデータを一旦削除します。
そして、確認メールに記載するキーコードを生成させます。キーコードの生成は汎用性が高いので、別途コンポーネントを作成しました。Utilityコンポーネントです。次のファイルを作りましょう。
/app/controllers/components/utility.php
class UtilityComponent extends Object {
/**
* get random string
* @param int $length
* @param string $complex (num, char, num_char, all)
*/
function getRandomString($length = 10, $complex = 'num') {
$num = '012345679';
$char = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$sign = '!#$%&()-=+*}{[]';
switch($complex) {
case 'num':
$str = $num;
break;
case 'char':
$str = $char;
break;
case 'num_char':
$str = $num . $char;
break;
case 'all':
$str = $num . $char . $sign;
break;
}
list($msec, $sec) = explode(' ', microtime());
mt_srand($msec*100000);
$ret = '';
for($i=0; $i<$length; $i++) {
$x = mt_rand(0, strlen($str)-1);
$ret .= substr($str, $x, 1);
}
return $ret;
}
}
桁数や、種類を指定してランダムな文字列を取得することが出来ます。 これを、「users_controller.php」から、次のようにして参照します。
/app/controllers/users_controller.phpを変更
class UsersController extends AppController {
var $name = 'Users';
var $components = array('Session', 'Auth', 'Qdmail', 'Utility'); // ←ここ
「$components」の最後に「Utility」を追加しました。これで、使うことが出来ます。
こうして、keycodeやstatusをセットしたら、一旦仮登録をしておきます。その上で、Qdmailを使って確認メールを送ります。
最後に確認メールの確認です。「active」アクションを作ります。
/app/controllers/users_controller.phpに追加
function active($keycode = null) {
if ($keycode == '') {
$this->redirect('index');
}
$data = $this->User->findAllByKeycode($keycode);
if ($data) {
$this->User->updateAll(
array('keycode'=>null, 'status'=>'1'),
array('id'=>$data[0]['User']['id'])
);
$this->render('finish');
} else {
$this->render('failed');
}
}
activeコントローラーには「$keycode」パラメータが渡ってくるので、これを使ってデータベースからデータを参照します。
発見できたら、statusを 1に、keycodeを削除してアップデートをかけることで「正式登録」にします。後は、完了画面を表示させて完了。 出来上がりました!
プログラムを作って記事を書きながら思ったのですが、今の仕組みだとkeycodeがたまたま重なってしまったときに、別人が正式登録されてしまいますね。。 確認メールのパラメータにidを増やしたり、keycodeを重ならないようなしくみにするなどの工夫が必要でした。。また改めてバージョンアップしてエントリーします。
何かの参考にしてみてくださいませ。サンプルコードのダウンロードはこちらからどうぞ。 (database.phpとcore.phpのSecurity.saltの設定をしないと動作しません)
Tweetエイチツーオー・スペースの最新情報をメールまたはRSSでお受け取りいただけます。ぜひご利用ください。
この記事へのコメント
2010年3月3日
アクティベーションコードの生成は、
String::uuid を用いる場合と比べて、
どのようなメリット・デメリットが考えられるでしょうか?
2010年3月3日
keycodeをsha1ハッシュで生成するというのはどうでしょうか? 生成時に暗号化キーとしてSecurity.saltの値やusernameの値を指定してやればセキュリティ的にも問題ないと思います。
sha1での暗号化の参考サイト→http://www.cpa-lab.com/tech/064
2010年3月4日
# フォーラムから流れてきましたさん
なんと、uuid・・気がつきませんでした。
そんな便利な機能があるのですね。それでしたら、ぜひそれを使わせていただきます!
プログラムを改良したら、記事を書き直しますね。
# utayakuさん
確かに、usernameなどの重複しない値を使えば、sha1で重複しなくなりますね。
ハッシュのデメリットは、結構文字列の長さが長いんですよね。。
もう少し短い文字数だと助かりますね。
情報ありがとうございました!
2010年4月5日
とても参考になりました。ありがとうございます。
ひとつ気になったのですがUtility.phpで使われてるsplit関数はPHP5.3から非推奨みたいなのですが、explodeで代用しても問題ないですよね?
2010年4月6日
コロ助さん
コメントありがとうございます! ご指摘の通り、splitではなく explodeの方が適切かもしれませんね。記事の方も修正しておきますね。ありがとうございました!
トラックバック