dari88's diary

これから趣味にするプログラミング/PHP/javascript/kohana/CMS/web design/

kohanaのテスト11・・・Authでユーザー認証機構を作る

 今日はユーザー認証機構を作りました。セッションを利用しただけの認証機構ですが、いろいろ試してみるとセキュリティー面での問題はあまり感じませんでした。ハッカーならブレーク出来るんですかね?

 今日までの勉強でコミュニティサイトを構築するために最低限必要なパーツは概ね揃ったように思います。今後は画面構成とか飾り付けのお勉強もしてみたいと考えています。では、今日の作業のメモを書いておきます。

 

準備作業

・先ずは bootstrap.php を変更します。
 Auth を使えるようにしますが、今後使うかもしれないので ORM も開きます。
・kohana/application/bootstrap.php

Kohana::modules(array(
    
        'auth'       => MODPATH.'auth',       // Basic authentication
        // 'cache' => MODPATH.'cache', // Caching with multiple backends
        // 'codebench' => MODPATH.'codebench', // Benchmarking tool
        'database'   => MODPATH.'database',   // Database access
        // 'image' => MODPATH.'image', // Image manipulation
        'orm'        => MODPATH.'orm',        // Object Relationship Mapping
        // 'unittest' => MODPATH.'unittest', // Unit testing
        // 'userguide' => MODPATH.'userguide', // User guide and API documentation
        ));

・Auth の定義ファイルを別の場所にコピーして書き換えます。
  kohana/modules/auth/config/auth.php
  kohana/application/config/下にコピーします。

・ドライバーを後で作る login.php に対応する名前にします。
 hash のソルトを 32 文字くらい設定します。
・kohana/application/config/auth.php

<?php defined('SYSPATH') or die('No direct access allowed.');
return array(
        'driver'       => 'login',
        'hash_method'  => 'sha256',
        'hash_key'     => 'j#8L$5m%1F&6u(2oT)8Sn=4Rl@3*Zx+2',
        'lifetime'     => 1209600, // 14 days
        'session_type' => Session::$default,
        'session_key'  => 'auth_user',

        // Username/password combinations for the Auth File driver
        'users' => array(
        ),
); 

・ユーザー管理用のデータベースを準備します。
 kohana/modules/orm/auth-schema-mysql.sql というファイルの中身を phpMyAdmin にて kohana データベースに投入してテーブルを準備します。

 

Auth のドライバを作る

 クラス Auth はデータベース操作部分が未完成交響曲になっています。ユーザーの環境に合わせて自分で作れということのようです。ということで、Auth を継承してアブストラクトメソッドの部分を書きました。
・kohana/application/classes/auth/login.php

<?php defined('SYSPATH') or die('No direct access allowed.');

class Auth_Login extends Auth {

// Logs a user in.
    protected function _login($username, $password, $remember) {

        if (is_string($password)) {
            $password = $this->hash($password);
        }

        $dbpassword = parent::instance()->password($username);

        if ($dbpassword == $password) {
            $this->complete_login($username);
            return TRUE;
        }

        return FALSE;
    }

// Get the stored password for a username.
    public function password($username) {

        $userdata = DB::select_array(array('id', 'username', 'password'))
                ->from('users')
                ->where('username', '=', $username)
                ->execute();

        $dbpassword = $userdata->get('password', FALSE);
        return $dbpassword;
    }

// Compare password with original (hashed). 
    public function check_password($password) {

        $username = $this->get_user();
        if (!$username)
            return FALSE;

        $dbpassword = parent::instance()->password($username);
        if (!$dbpassword)
            return FALSE;

        return ($this->hash($password) == $dbpassword);
    }

}

 

コントローラ

 今回はメインページ、ユーザー登録用、ログイン用とログアウト用の4種類を作ります。それぞれ用事が済んだらメインページにリダイレクトします。

・kohana/application/classes/controller/test11.php

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Controller_Test11 extends Controller {

    public function action_index() {

        $auth = Auth_Login::instance();
        if (!$username = $auth->get_user()) {
            $username = 'ゲスト';
        }

        $message = 'メイン画面';
        $this->response->body(View::factory('test11')
                        ->bind('username', $username)
                        ->bind('message', $message)
        );
    }

}

?>

・kohana/application/classes/controller/test11register.php

 入力値の検証を実用レベルに強化しています。ユーザー名とメールアドレスはユニークを要求します。メールアドレスはその形式とドメインの実在を検証しています。それぞれ最大文字数のチェックもしています。

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Controller_Test11 extends Controller {

    public function action_index() {

        $auth = Auth_Login::instance();
        if (!$username = $auth->get_user()) {
            $username = 'ゲスト';
        }

        $message = 'メイン画面';
        $this->response->body(View::factory('test11')
                        ->bind('username', $username)
                        ->bind('message', $message)
        );
    }

}

?>

・kohana/application/classes/controller/test11login.php

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Controller_Test11login extends Controller {

    public function action_index() {

        if (isset($_POST['username'])) {
            $post = Validation::factory($_POST)
                    ->rule('username', 'not_empty')
                    ->rule('username', 'max_length', array(':value', 24))
                    ->rule('username', 'regex', array(':value', '/^[a-z0-9_.]++$/iD'))
                    ->rule('password', 'not_empty')
                    ->rule('password', 'min_length', array(':value', 6))
                    ->rule('password', 'max_length', array(':value', 12));

            $posts = $post->data();
            $username = $posts['username'];
            $password = $posts['password'];
            $autherror = '';

            if ($post->check()) {
                $auth = Auth_Login::instance();
                $result = $auth->login($username, $password);
                if ($result) {
                    $this->request->redirect('test11');
                }
                $autherror = 'ログイン認証エラーです。' .
                        'ユーザー名とパスワードを確認して下さい。';
            }

            $errors = $post->errors('test11');
            $username = $_POST['username'];
        } else {
            $username = "";
            $email = "";
        }

        $this->response->body(View::factory('test11login')
                        ->bind('username', $username)
                        ->bind('errors', $errors)
                        ->bind('autherror', $autherror)
        );
    }

}

?>

・kohana/application/classes/controller/test11logoff.php

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Controller_Test11logoff extends Controller {

    public function action_index() {

        Auth::instance()->logout();
        $this->request->redirect('test11');
    }

}

?>

 

モデル

 必要なモデルはユーザー登録用だけです。

・kohana/application/classes/model/test11register.php

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Model_Test11register extends Model {

    public function register($array) {
        $id = DB::insert(array_keys($array))
                ->values($array)
                ->table('users')
                ->execute();

        return $id;
    }

    public function unique_username($username) {

        $users = DB::SELECT()
                ->from('users')
                ->select('username')
                ->execute();

        foreach ($users as $user) {
            if ($user['username'] == $username) {
                return false;
            }
        }
        return true;
    }
    
    public function unique_email($email) {

        $emails = DB::SELECT()
                ->from('users')
                ->select('email')
                ->execute();

        foreach ($emails as $em) {
            if ($em['email'] == $email) {
                return false;
            }
        }
        return true;
    }

}

?>

 

ビュー

 メイン画面、ユーザ登録用、ログイン用の3種類を作ります。

・kohana/application/views/test11.php

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>kohanaのテスト11</title>
    </head>
    <body>
        
        <p><?php echo "ようこそ ".$username." さん" ?></p><br />
        <h1><?php echo $message ?></h1><br />
        <a href="test11login">ログイン画面へ</a><br />
        <a href="test11register">ユーザー登録画面へ</a><br />
        <a href="test11logoff">ログアウトする</a><br />
        
    </body>
</html>

・kohana/application/views/test11register.php

<?php echo Form::open() ?>

<h1>ユーザー登録画面</h1>
<?php if ($errors): ?>
    <p class="message">入力値はよくチェックして下さい。</p>
    <ul class="errors">
        <?php foreach ($errors as $message): ?>
            <li><?php echo $message ?></li>
        <?php endforeach ?>
    <?php endif ?>

    <dl>
        <dt><?php echo Form::label('username', 'ユーザー名') ?></dt>
        <dd><?php echo Form::input('username', $username,
                array('size' => '30', 'maxlength'=>'24')) ?></dd>
        <dt><?php echo Form::label('email', 'e-Mail') ?></dt>
        <dd><?php echo Form::input('email', $email,
                array('size' => '30', 'maxlength'=>'60')) ?></dd>
        <dt><?php echo Form::label('password', 'パスワード') ?></dt>
        <dd><?php echo Form::password('password', '',
                array('size' => '30', 'maxlength'=>'12')) ?></dd>
        <dd class="help">パスワードは6文字以上必要です</dd>
        <dt><?php echo Form::label('confirm', 'パスワードの確認') ?></dt>
        <dd><?php echo Form::password('confirm', '',
                array('size' => '30', 'maxlength'=>'12')) ?></dd>
    </dl><br />

    <?php echo Form::submit(NULL, '登録する') ?>
    <?php echo Form::close() ?>

・kohana/application/views/test11login.php

<?php echo Form::open() ?>

<h1>ログイン画面</h1>
<?php if ($errors): ?>
    <p class="message">ログインして下さい。</p>
    <ul class="errors">
        <?php foreach ($errors as $message): ?>
            <li><?php echo $message ?></li>
        <?php endforeach ?>
    </ul>
    <?php endif ?>

<?php if ($autherror): ?>
    <ul class="errors">
            <li><?php echo $autherror ?></li>
    </ul>
<?php endif ?>            
            
    <dl>
        <dt><?php echo Form::label('username', 'ユーザー名') ?></dt>
        <dd><?php echo Form::input('username', $username,
                array('size' => '30', 'maxlength'=>'24')) ?></dd>
        <dt><?php echo Form::label('password', 'パスワード') ?></dt>
        <dd><?php echo Form::password('password', '',
                array('size' => '30', 'maxlength'=>'12')) ?></dd>
    </dl><br />

    <?php echo Form::submit(NULL, 'ログインする') ?>
    <?php echo Form::close() ?>

 

エラーメッセージ用ファイル

・kohana/application/messages/test11.php

<?php defined('SYSPATH') or die('No direct script access.');
// kohana/system/messages/validation.phpからコピー

return array(
        'alpha'         => ':field must contain only letters',
        'alpha_dash'    => ':field must contain only numbers, letters and dashes',
        'alpha_numeric' => ':field must contain only letters and numbers',
        'color'         => ':field must be a color',
        'credit_card'   => ':field must be a credit card number',
        'date'          => ':field must be a date',
        'decimal'       => ':field must be a decimal with :param2 places',
        'digit'         => ':field must be a digit',
        'email'         => ':field e-mail の形式が不正です',
        'email_domain'  => ':field e-mail のドメインが不正です',
        'equals'        => ':field must equal :param2',
        'exact_length'  => ':field must be exactly :param2 characters long',
        'in_array'      => ':field must be one of the available options',
        'ip'            => ':field must be an ip address',
        'matches'       => '確認入力が間違っています',
        'min_length'    => ':field は :param2 文字以上にして下さい',
        'max_length'    => ':field は :param2 文字以下にして下さい',
        'not_empty'     => ':field を書き忘れています',
        'numeric'       => ':field must be numeric',
        'phone'         => ':field must be a phone number',
        'range'         => ':field must be within the range of :param2 to :param3',
        'regex'         => ':field が規定の形式になっていません',
        'url'           => ':field must be a url',
    'unique_username' => 'そのユーザー名は既に使われています',
    'unique_email' => 'その e-mail は既に使われています',
);

 

テスト

 以上で作業は終わりです。localhost/kohana/test11 にアクセスして、先ずはユーザー登録から始めます。何件か登録したらログインしてみましょう。メイン画面のトップにユーザー名が表示されたでしょうか? 今回のテストでは以下のような勉強をします。

・ブラウザでセッションクッキーが作られているか調べます。
・C:/xampp/tmp にセッションデータが作られているか調べます。
・クッキーを消したり、セッションデータを消したり、そのユーザー名を書き換えたりして、セッションの挙動を調べます。
・別名でログインしたり、ログアウトするとどうなるかを調べます。

 結論としてユーザー側のクッキーとサーバー側のデータが完全に一致しないとセッションが成立しないようです。セッションが成立しないとセッションからユーザー名をゲットできません。サーバー側のセッションデータを知らない限りハッキングは難しんじゃないでしょうか。

 商売用のサイトは別にして、小規模なコミュニティサイトならこれくらいのユーザー認証でいいんじゃないでしょうか。