シンプルなユーザー登録

CakePHP(2.6.4)を使ったシンプルなユーザー登録のメモ。

**<font color="#dc143c">※本記事には修正が必要な個所がありま~~す~~した。（[参考](http://qiita.com/altere5/items/82c464696308b5a38824)）</font>**
**<font color="#dc143c">※なお、上記修正内容はすでに反映済みです</font>**


#概要

ユーザー登録画面にて「メールアドレス」と「パスワード」を入力し登録を行う。
なお、ユーザー名は「メールアドレス」を利用する。

#実装内容

##Userモデル(User.php)及びUserテーブル

Userモデルには独自検証ルールと保存前処理を実装。
ユーザー登録時のパスワードのハッシュ化はbeforeSaveにてBlowfishPasswordHasherを用いて行う。

~~重複ユーザーの登録が行われないよう重複チェック（isUnique）を実装。~~
重複ユーザーの登録が行われないよう標準ルールのisUniqueを利用。
また、パスワード欄とパスワード（確認）欄の一致を検証するため一致チェック（passwordConfirm）を実装。
パスワードに利用可能な文字を半角英数字(記号可)とする為のチェック(alphanumericsymbols)を実装。

検証ルールについては主に以下の内容を指定。

メールアドレスをキーとして重複登録のチェックを標準ルールの重複チェック（isUnique）を指定。
パスワード欄には半角英数字（記号可）のみ入力可能とする為、Userモデルに実装した文字種チェック(alphanumericsymbols)を指定。
同様にパスワード欄とパスワード（確認）欄を用いてパスワードの誤入力（勘違い）を防ぐ為、両フィールドの一致をチェックする為、一致チェック（passwordConfirm）を指定。

```php:User.php
<?php

App::uses('BlowfishPasswordHasher', 'Controller/Component/Auth');

class User extends AppModel {

    var $name = 'User';
    var $useTable = 'users';

	public $validate = array(
		'email' => array(
			array(
				'rule' => 'notEmpty', 
				'message' => 'メールアドレスを入力してください'
			), 
			array(
				'rule' => 'email', 
				'message' => '正しいメールアドレスを入力してください'
			), 
			array(
				'rule' => 'isUnique', 
				'message' => '入力されたメールアドレスは既に登録されています'
			), 
		),
		'password' => array(
			array(
				'rule' => 'notEmpty', 
				'message' => 'パスワードを入力してください'
			), 
			array(
				'rule' => 'alphanumericsymbols', 
				'message' => 'パスワードに使用できない文字が入力されています'
			), 
			array(
				'rule' => array('minLength', 8), 
				'message' => 'パスワードは8文字以上入力してください', 
			),
			array(
				'rule' => 'passwordConfirm', 
				'message' => 'パスワードが一致していません'
			), 
		),
		'password_confirm' => array(
			array(
				'rule' => 'notEmpty', 
				'message' => 'パスワード(確認)を入力してください'
			), 
		),
	);

	public function beforeSave($options = array()) {

		if (isset($this->data[$this->alias]['password'])) {
			$passwordHasher = new BlowfishPasswordHasher();
			$this->data[$this->alias]['password'] = $passwordHasher->hash(
				$this->data[$this->alias]['password']
			);
		}

    	return true;

	}

	public function passwordConfirm($check){

		//２つのパスワードフィールドが一致する事を確認する
		if($this->data['User']['password'] === $this->data['User']['password_confirm']){
			return true;
		}else{
			return false;
		}

	}

	public function alphanumericsymbols($check){
		$value = array_values($check);
		$value = $value[0];
    	return preg_match('/^[a-zA-Z0-9\s\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e]+$/', $value);
	}

}
```

```sql:user.sql
CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255),
    password VARCHAR(255),
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
) ENGINE = InnoDB;
```

##Signupコントローラー(SignupController.php)

Signupコントローラーではユーザー登録作業を実施。

indexアクションでは、入力データが送信されていない場合は「\$this->render()」にてindexビュー(Signup/index.tpl)を表示し、入力データが存在する場合は「\$this->User->invalidFields()」にて検査を実施。

検査を通った場合は入力内容を保存し、最後にサンクス画面を表示して終了。

なおセキュリティを高める為、Securityコンポーネントを利用。
不正なアクセスが行われた場合あらかじめ設定したblackholeメソッドの処理を実行。
（ここではエラーメッセージをセットし呼び出し元にリダイレクト）

```php:SignupController.php
<?php

class SignupController extends AppController {

	public $name = 'Signup';
	public $uses = array('User');
	public $components = array(
		'Security', 
	);

    public function beforeFilter() {
        parent::beforeFilter();
		$this->Security->blackHoleCallback = 'blackhole';
    }

	public function index(){

		if(!$this->request->data){
			$this->render();
			return;
		}

		//入力データをセット
		$this->User->set($this->request->data);

		//入力内容を検査
		if($this->User->validates()){

			//モデルの状態をリセット
			$this->User->create();

			//入力済みデータをモデルにセット
			$user = array('User' => $this->request->data['User']);

			//データを保存
			$this->User->save($user);

			//サンクス画面を表示
			$this->render('thanks');

		}

	}

	public function blackhole($type) {

		$this->Session->setFlash('不正なリクエストが行われました');
		$this->redirect(array('controller' => $this->controller, 'action' => $this->action));

	}

}
```

##Usersコントローラー(UsersController.php)

Usersコントローラーでは、ログイン及びログアウト処理と認証を必要なページとしてmypageアクションを実装。

今回はユーザー名としてメールアドレスを利用する為、「fields」にて「'username' => 'email'」を指定する。
またハッシュ化にBlowfishPasswordHasherも利用している為、「passwordHasher」にて「Blowfish」を指定。

なお、loginアクション及びlogoutアクションは未認証状態でもアクセス可能とする必要がある為、あらかじめ「beforeFilter」にて除外を行う。

loginアクションにて認証情報が送信されてきた場合は、ログイン処理を実施する。
認証が通った場合は、あらかじめ設定されたURLにリダイレクトする。

logaoutアクションが呼ばれた場合、ログアウト処理を実施しログアウト完了画面を表示する。

こちらでもセキュリティを高める為、Securityコンポーネントを利用。
不正なアクセスが行われた場合あらかじめ設定したblackholeメソッドの処理を実行。
（ここではエラーメッセージをセットし呼び出し元にリダイレクト）


```php:UsersController.php
<?php

class UsersController extends AppController {

	public $name = 'Users';
	public $uses = array('User');
	public $components = array(
		'Auth' => array(
			'authenticate' => array(
				'Form' => array(
					'passwordHasher' => 'Blowfish', 
					'fields' => array(
						'username' => 'email', 
						'password' => 'password', 
					), 
				)
			), 
			'loginAction' => '/users/login', 
			'loginRedirect' => '/users/mypage', 
		), 
		'Session', 
		'Security', 
	);

    public function beforeFilter() {
        parent::beforeFilter();
        $this->Auth->allow('login', 'logout');
		$this->Security->blackHoleCallback = 'blackhole';
    }

	public function login() {

		//フォームから情報が送信された場合のみの認証を実施
		if ($this->request->is('post')) {

			//認証処理を実施
			if($this->Auth->login()){
				//認証に成功した場合は、設定されたURLへリダイレクト
				return $this->redirect($this->Auth->redirectUrl());
			}else{
				$this->Session->setFlash('ログインに失敗しました');
			}

		}

	}

	public function logout() {

		//ログアウトを実施
		$this->Auth->logout();

		//ログアウト完了画面を表示
		$this->render('logout');

	}

    public function mypage() {

	}

	public function blackhole($type) {

		$this->Session->setFlash('不正なリクエストが行われました');
		$this->redirect(array('controller' => $this->controller, 'action' => $this->action));

	}

}
```

#総括及び課題(疑問)

最もシンプルなユーザー登録の処理の為、アカウントの有効化やメールアドレスの検証等の処理は未実装。

~~パスワードのハッシュ化はSimplePasswordHasherにて行ったが、アルゴリズムの選択や強度の設定ができるはずなので確認が必要。~~
パスワードのハッシュ化はBlowfishPasswordHasherにて行ったが、強度の設定等についても確認が必要。

その他、例えばソーシャルログインも実装する場合も考慮するとテーブル構成は見直す必要があると思われる。
（メールアドレスが必ず入手可能ではない為）

#参考

[ホワイトスペースと半角記号にマッチする正規表現](http://qiita.com/suin/items/afa35d76f9c384fc0233)

#【参考】各種ビュー

```php:Signup/index.ctp
<?php echo $this->Form->create(); ?>

<?php echo $this->Form->label('User.email', 'メールアドレス:'); ?>
<?php echo $this->Form->text('User.email'); ?>
<?php echo $this->Form->error('User.email'); ?>

<?php echo $this->Form->label('User.password', 'パスワード:'); ?>
<?php echo $this->Form->password('User.password'); ?>
<?php echo $this->Form->error('User.password'); ?>

<?php echo $this->Form->label('User.password_confirm', 'パスワード(確認):'); ?>
<?php echo $this->Form->password('User.password_confirm'); ?>
<?php echo $this->Form->error('User.password_confirm'); ?>

<?php echo $this->Form->end('登録'); ?>
```

```php:Signup/thanks.ctp
登録しました
```

```php:Users/login.ctp
<?php echo $this->Form->create(); ?>
<?php echo $this->Form->label('User.email'); ?>
<?php echo $this->Form->text('User.email'); ?>
<?php echo $this->Form->label('User.password'); ?>
<?php echo $this->Form->password('User.password'); ?>
<?php echo $this->Form->end('ログイン'); ?>
```

```html:Users/logout.ctp
<p>ログアウトしました</p>
<p><a href="/users/login/">ログイン</a></p>
```

```html:Users/mypage.ctp
<p>マイページ</p>
<p><a href="/users/logout/">ログアウト</a></p>
```