本文へジャンプする



会社案内

ブログ

CakePHPで、確認画面、確認メールありのユーザー登録をつくる

2010年2月8日

Google Buzz

CakePHPはデータベース周りの処理がとにかく楽ちんなのですが、一度確認画面を挟んだり確認メールを挟んだりすると、コントロールが難しくなります。

そこで、ここではどちらも実現したサンプルを作ってみました。以下からご覧下さい。 http://h2o-space.com/dev/user_add/

まず、メールアドレスとパスワードを設定すると確認画面に移動します。書きなおすことも出来ます。 続いて、確認メールを送信して送られるURLをクリックすると正式登録ができるという仕組みです。

順番に作っていきましょう。

データベースの構築とCakePHPの準備

まずは、データベースを準備します。次のテーブルを作成しましょう。

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」をコピーします。

Userコントローラ・モデルの追加

続いて、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) = split(' ', 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の設定をしないと動作しません)

エイチツーオー・スペースの最新情報をメールまたはRSSでお受け取りいただけます。ぜひご利用ください。

この記事へのコメント

3件のコメントがあります
フォーラムから流れてきました
2010年3月3日

アクティベーションコードの生成は、
String::uuid を用いる場合と比べて、
どのようなメリット・デメリットが考えられるでしょうか?

utatyaku
2010年3月3日

keycodeをsha1ハッシュで生成するというのはどうでしょうか? 生成時に暗号化キーとしてSecurity.saltの値やusernameの値を指定してやればセキュリティ的にも問題ないと思います。
sha1での暗号化の参考サイト→http://www.cpa-lab.com/tech/064

h2ospace
2010年3月4日

# フォーラムから流れてきましたさん

なんと、uuid・・気がつきませんでした。
そんな便利な機能があるのですね。それでしたら、ぜひそれを使わせていただきます!

プログラムを改良したら、記事を書き直しますね。

# utayakuさん

確かに、usernameなどの重複しない値を使えば、sha1で重複しなくなりますね。

ハッシュのデメリットは、結構文字列の長さが長いんですよね。。
もう少し短い文字数だと助かりますね。

情報ありがとうございました!

お名前
メールアドレス
URL
コメント
 

トラックバック

この記事へのトラックバック
http://h2o-space.com/blog/1950/trackback
pagetop


» twilog

あわせて読みたいブログパーツ

サイト内検索

powered by Yahoo! JAPAN