第5章 ログイン認証機能を作ろう

この章から読み進める人向け

この章から読み始める人は、準備のためにこちらのファイルをダウンロードしてください。

  • htdocs-chap4.zipの展開した内容はxampp\htdocs以下に直接展開してください。
  • user.sqlはデータベースのデータです。http://localhost/phpmyadminからyiiというデータベースを作ってその下にインポートしてください。(これはyiiである必要はありません。yii以外の名前の方がいい人は以下の設定ファイルでyiiの部分を自分のつけたデータベース名に変更してください。)

ですが前回からデータベースを使い始めたので、このままでは動作しないかと思われます。そこでデータベースの設定を行う必要があります。データベースの設定はxampp\htdocs\protected\config\main.phpから行う必要があります。51行目あたりにデータベースの情報設定がありますので、そこを編集してください。

'db'=>array(
			'connectionString' => 'mysql:host=localhost;dbname=yii',// dbname=yiiに変更する
			'emulatePrepare' => true,
			'username' => 'root',
			'password' => 'unko9314', // ここを0章で設定したmysqlのパスワードに変更する
			'charset' => 'utf8',
		),

本書ではMySQLのパスワードをunko9314にしていますので、それをご自身のパスワードに変更する必要があります。


まずは下準備をしよう(データベースにパスワード項目を追加)

さてこの章では会員専用ページのためのシステムを構築していきます。具体的にはデータベースに登録されている会員だけが入れる専用の領域をつくります。まずe-mailとパスワードを入力して認証を行い、認証後が成功したらログイン状態になるという仕組みです。

このようなおおまかな仕組みはすでにyiiframeworkに中に組み込まれていますので、使い方を見ていきましょう。

前回までは会員用のデータベースをMySQL上に構築しましたが、そこには会員ごとのパスワードは記録されていませんでしたので、まずはそれを追加するところからはじめましょう。

まずはhttp://localhost/phpmyadmin/を開いてください。その後、画面左のデータベース一覧からyiiを選んでください。(もしyii以外の名前をデータベース名にしている方は該当のものを選んでください。)

yiiを選択
yiiを選択

そしてSQLを選んでください。

chap5-2

そして入力欄に以下の命令をコピーして貼り付けてください。これはuserというテーブルにpasswordという新しい項目を追加してくださいという意味です。またその項目のデータの形式はvarchar(64)というものです。

ALTER TABLE user ADD password varchar(64) NOT NULL;

chap5-3

これでデータベース上の作業は終了ですので画面は閉じても構いません。

次はyiiのModel側を編集します。


Modelにpasswordを追加しよう

次にC:\xampp\htdocs\protected\models\User.phpを開いてください。そこでまず32行目あたりを以下のように編集します。Modelでは、各データ(カラム)の満たすべきルールを記述することができます。ここではpasswordは4文字以上64文字以内というルールを追加しました。

		return array(
			array('email, name', 'required'),
			array('email', 'length', 'max'=>128),
			array('name', 'length', 'max'=>64),
			array('password','length','max'=>64,'min'=>4), // ここを追加
			// The following rule is used by search().
			// @todo Please remove those attributes that should not be searched.
			array('id, email, name', 'safe', 'on'=>'search'),
		);

※追加した部分は以下

array('password','length','max'=>64,'min'=>4),

さらに55行目あたりから以下のように編集します。ここは単にpasswordという項目に別のラベル(名前)を付けただけです。

        return array(
            'id'=>'ID',
            'email'=>'Email',
            'name'=>'Name',
            'password' =>'Password', // ここを追加
        );

以上でModelの編集は終了です。


ログインの仕組みをつくろう

さて、ここから肝心のログインの仕組みを作っていきましょう。ログインの仕組みはすでにyiiframeworkの中身組み込まれているのでそれを利用することにします。一旦ログインすれば、ログアウトするまで、そのユーザのセッションが保存されます。すなわち、画面の遷移ごとにパスワードを入力しなくても、そのままそのユーザがログインしているという情報を維持することができます。

さてhttp://localhost/site/loginにすでに会員のログインフォームのようなものがあるので、こいつをそのまま使っていきましょう。まずこのURLに対応しているのはSiteControllerのactionLoginですので該当部分を見ましょう。

C:\xampp\htdocs\protected\controllers\SiteController.phpを開いてください。とりあえずここがログインに関連している部分だということは頭に入れておきましょう。

	/**
	 * Displays the login page
	 */
	public function actionLogin()
	{
		$model=new LoginForm;

		// if it is ajax validation request
		if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
		{
			echo CActiveForm::validate($model);
			Yii::app()->end();
		}

		// collect user input data
		if(isset($_POST['LoginForm']))
		{
			$model->attributes=$_POST['LoginForm'];
			// validate user input and redirect to the previous page if valid
			if($model->validate() && $model->login())
				$this->redirect(Yii::app()->user->returnUrl);
		}
		// display the login form
		$this->render('login',array('model'=>$model));
	}

ログインの部分は以下の部分が関連しています。

$model->login()

そこでLoginFormのクラスを見てみましょう。これは実はModelとして定義されています。C:\xampp\htdocs\protected\modelsの中にLoginForm.phpがありますので、これを開いてください。そして以下の部分が$model-login()の処理の具体的な内容です。

	public function login()
	{
		if($this->_identity===null)
		{
			$this->_identity=new UserIdentity($this->username,$this->password);
			$this->_identity->authenticate();
		}
		if($this->_identity->errorCode===UserIdentity::ERROR_NONE)
		{
			$duration=$this->rememberMe ? 3600*24*30 : 0; // 30 days
			Yii::app()->user->login($this->_identity,$duration);
			return true;
		}
		else
			return false;
	}

さらに細かく見て、ログインをしているのは以下の処理の部分です。

Yii::app()->user->login($this->_identity,$duration);

$duarationはただの有効期限です。そして手前の$this->_identityの部分が「誰がログインするのか」という情報と関わりがあることが予想できます。これはUserIdentityというクラスのオブジェクトですので、このクラスを見てみましょう。

これはC:\xampp\htdocs\protected\componentsの中にUserIdentity.phpというファイルがあるのでこいつを開いてください。

public function authenticate()
    {
        $users=array(
			// username=password
            'demo'=>'demo',
            'admin'=>'admin',
        );
        if(!isset($users[$this->username]))
            $this->errorCode=self::ERROR_USERNAME_INVALID;
        elseif($users[$this->username]!==$this->password)
            $this->errorCode=self::ERROR_PASSWORD_INVALID;
        else
            $this->errorCode=self::ERROR_NONE;
        return !$this->errorCode;
    }

上記の部分がログインのコアな部分です。ここでパスワードの照合などを行っています。しかし今はyiiframeworkのサンプルの状態で、自分のこれから作るプログラム向けにはなってないので、ここを改造してやる必要があります。

さて、いまから作っていく会員ログインの仕組みでは、E-mailアドレスとパスワードでユーザのログイン認証をすることにします。

そのため具体的な処理レベルでいうと、「入力されたE-mailアドレスを用いて、Userデータベースから検索して、そしてそのユーザのパスワードと入力されたパスワードが同じならログイン成功」とします。

またログインフォームで、ユーザから入力されるユーザ名は$this->usernameに格納されるようになっています。(これはデフォルトでそうなっています)ただ今作ってるデータベースの中にはusernameという概念がないので、ここにはE-mailアドレスが入ることとします。

そこで、上記のソースコードを以下のように変更してください。

public function authenticate()
	{
		// E-mailで検索する
		$user = User::model()->findByAttributes(array('email'=>$this->username));
		// そんなE-mailのユーザは存在しない
		if(!isset($user))
			$this->errorCode=self::ERROR_USERNAME_INVALID;
		// パスワードが異なっている
		elseif(strcmp($user->password,$this->password))
			$this->errorCode=self::ERROR_PASSWORD_INVALID;
		// パスワードが正しい
		else
			$this->errorCode=self::ERROR_NONE;
		return !$this->errorCode;
	}

とりあえず上記のソースコードが何をしているかを解説します。

$this->usernameにはユーザがログインフォームから入力してきたメールアドレスが入力されています。次にUser::model()->findByAttributes()とは、userのデータベースの中から属性の値を指定し、合致するものを1つ取得するという命令です。ここでは中にarray(‘email’=>$this->username)を指定していますが、「emailの値がユーザのログインフォームで入力してきた値と同じようになるユーザ(会員)を探してください」ということになります。

$user = User::model()->findByAttributes(array('email'=>$this->username));

そしてその後に条件分岐がありますが、入力されたemailアドレスに合致するユーザが存在しなかった場合と、存在してもパスワードが間違っていた場合をエラーにして、そうでなければ正しいという流れになっています。これについてはもともとのソースコードもそうなっていて、条件の式だけを少しいじった形になります。


動作のテスト

さて、では実際にログインフォームが正しく動くか確認してみましょう。ただその前に今登録されている会員番号1のユーザのpasswordはデータベース上で何も値が入っていないので、phpmyadminから適当にパスワードをセットしておきます。

http://localhost/phpmyadminにログインしたあと、yii→userと選び、userのテーブルを表示してみました。

そこで図のようにパスワードに適当な値をセットします。(ここではパスワードをtestにしました)

chap5-4

その後、http://localhost/site/loginにアクセスしてログインフォームを表示させます。

chap5-5

そしてメールアドレスにyii@juncheng.orgと入力し、パスワードにtestを入力しましょう。そしてLoginを押します。

chap5-6

画面のメニューのところに、Logout(yii@juncheng.org)が表示されているのが分かるでしょう。どうやらログインの処理はうまくいっているようです。


セッション情報とユーザ情報の取得

とりあえずログインっぽい部分が成功したってのはわかると思います。しかし「じゃあ肝心のログインしているユーザの情報は具体的にどうやってプログラムから取得するの?」っていう疑問をお持ちかと思いますので、このあたりを細かく見て行きましょう。

ではUserControler.phpを開いてください。(これ以降たまにプログラムの細かいパスまでは表記するのを省略することがありますので、徐々に慣れて行ってください。)

前につくったactionTest()の部分からプログラムの動作実験してみましょう。

ここでのプログラムの目標としては

  • もしログインしていていれば、ようこそ○○さんというメッセージを表示
  • もしログインしていなければ、ログインフォームに強制的にジャンプする

という動きになるもの作りたいと思います。

ログインしているかどうかを取得するのは以下の命令で可能です。これがtrueであればゲストユーザである、すなわちログインをしていないという状態です。falseであればログインをしているユーザということになります。

Yii::app()->user->isGuest

さらにログイン時に入力したE-mailアドレスは、以下の命令で取得が可能です。

Yii::app()->user->id;

ここで注意してほしいのが、Yii::app()->userの値会員のデータベース(MySQLのテーブルのデータ)の情報を直接引っ張ってきて取得しているというわけではありません。ただ単に、ログインの時に入力したE-mailの値がそのまま、ずっと記憶され、格納されているだけです。なので、会員のデータベースにアクセスしたければ、そのE-mailの情報を元にして、自分で再度データベースに対してアクセスし直す必要があります。

そこでUserContoller.phpactionTest()を以下の内容に書き換えてください。

public function actionTest()
	{
		if(Yii::app()->user->isGuest) {
			$this->redirect('/site/login');
		} else {
			$email = Yii::app()->user->id;
			$user = User::model()->findByAttributes(array('email'=>$email));
			$this->render('test',array('user'=>$user));
		}
	}
  • 条件分岐の部分では、まずログインしていなければhttp://localhost/site/loginに強制的にジャンプさせられます。
  • 次にYii::app()->user->idの中にはE-mailアドレスが入っているので、それを元にUserのデータベースにアクセスして該当する会員のデータを取得しています。そして、それをtest.phpのViewファイルに渡しています。

次にprotected/views/user/test.phpを開いてください。そして以下の内容に変更します。

<h1>テストです</h1>
<p>ようこそ<?php echo $user->name;?>さん</p>

それではhttp://localhost/site/loginに入って再度ログインしてみましょう。ログインに成功したあと、そのあとURLにhttp://localhost/user/testにアクセスしてください。そうしたら以下のような画面になるはずです。

chap5-7

きちんと会員の名前が取得できているので成功です。


Yii::app()->user->idに会員番号が入るように改造しよう

さてYii::app()->user->idの中にログイン時に入力するE-mailアドレスが格納されるというのは分かりましたがidという名前の変数なのにE-mailが入っているのは気持ち悪いので、そこには会員の通し番号が入るように変更をします。すなわちYii::app()->user->idの値と、User(のデータベース)のidの値を一致するようにします。

やり方としてはログインの処理の時にUserのデータベースにアクセスしているので、その時に取得した会員の通し番号(id)を覚えさせるという方法になります。

再びprotected/components/UserIdentity.phpを開きます。そして以下のように変更しましょう。

<?php

/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
class UserIdentity extends CUserIdentity
{
	// プロパティを追加
	private $_id; 
	
	/**
	 * Authenticates a user.
	 * The example implementation makes sure if the username and password
	 * are both 'demo'.
	 * In practical applications, this should be changed to authenticate
	 * against some persistent user identity storage (e.g. database).
	 * @return boolean whether authentication succeeds.
	 */
	public function authenticate()
	{
		// E-mailで検索する
		$user = User::model()->findByAttributes(array('email'=>$this->username));
		// そんなE-mailのユーザは存在しない
		if(!isset($user))
			$this->errorCode=self::ERROR_USERNAME_INVALID;
		// パスワードが異なっている
		elseif(strcmp($user->password,$this->password))
			$this->errorCode=self::ERROR_PASSWORD_INVALID;
		// パスワードが正しい
		else {
			$this->_id = $user->id; // 取得した会員のidを格納する
			$this->errorCode=self::ERROR_NONE;
		}
		return !$this->errorCode;
	}
	// 以下を追加
	public function getID() {
		return $this->_id;
	}
}

具体的には、まず上部でプロパティ$_idを追加しました。

	// プロパティを追加
	private $_id;

さらに、getIDというメソッドを追加して$_idを返すようにします。

	// 以下を追加
	public function getID() {
		return $this->_id;
	}

そしてログインのパスワードが正しくログインに成功した場合に、$_idに会員のidを格納しておきます。

		// パスワードが正しい
		else {
			$this->_id = $user->id; // 取得した会員のidを格納する
			$this->errorCode=self::ERROR_NONE;
		}

以上でYii::app()->user->idに記録される値が会員のid(通し番号)に一致するようになりました。

さて、それにともなって、UserController.phpactionTestも以下のように書き換えます。

	public function actionTest()
	{
		if(Yii::app()->user->isGuest) {
			$this->redirect('/site/login');
		} else {
			$id = Yii::app()->user->id;
			$user = User::model()->findByPk($id);
			$this->render('test',array('user'=>$user));
		}
	}

直接、通し番号から会員の検索ができるので、

$user = User::model()->findByPk($id);

という命令でユーザ情報を取得するように変えました。

これでもう一度http://localhost/user/testにログインしても正しく動作していることが分かるでしょう。


最後に

さて今回は長かったですが以上で解説を終了します。これで会員制ページにだいぶ近づきました。

今回までの成果物をこちらにおいておきますので、必要に応じてお使いください。