DataLife Engine > Версия для печати > Создаем невероятную простую систему регистрации на PHP и MySQL

Процесс создания системы регистрации – это довольно большой объем работы. Вам нужно написать код, который бы перепроверял валидность email-адресов, высылал email-письма с подтверждением, предлагал возможность восстановить пароль, хранил бы пароли в безопасном месте, проверял формы ввода и многое другое. Даже когда вы все это сделаете, пользователи будут регистрироваться неохотно, так как даже самая минимальная регистрация требует их активности.В сегодняшнем руководстве мы займемся разработкой простой системы регистрации, с использованием которой вам не понадобятся никакие пароли! В результаты мы получим, систему, которую можно будет без труда изменить или встроить в существующий PHP-сайт. Если вам интересно, продолжайте чтение.

Суть

Давайте посмотрим, как эта простая система регистрации работает:

* У нас будет использоваться комбинированная форма авторизации/регистрации, где пользователи смогут вводить email и нажимать на кнопку подтверждения;* При подтверждении, если email-адрес не обнаружен в базе данных, в нее вносится новая запись. Генерируется случайная лексема и отправляется пользователю по email в виде кликабельной ссылки, которая будет действовать всего 10 минут;* Клик по ссылке во входящих сообщениях перекинет пользователя снова на страницу сайта. Система определит присутствие лексемы и авторизует пользователя.

Данный подход имеет следующие преимущества:

* Отсутствие необходимости валидировать пароли;* Отсутствие необходимости разрабатывать функцию хранения паролей, секретных вопросов и прочего;* Вы можете быть уверенными в том, что у вас останется способ контакта с пользователем, как только он впервые заходит на вашу страницу.

Однако есть и следующие недостатки:

* Безопасность основывается на безопасности почтового ящика человека. Если кто-то завладеет данными от его почтового ящика, то этот злоумышленник получит доступ и к этой системе. Конечно, подобная проблема существует и с функциями восстановления паролей по email, но вы просто должны об этом знать;* Email не защищен и может быть перехвачен. Учитывайте, что здесь, как и в случае с формой восстановления паролей, не используется HTTPS-передача данных, и данные могут быть перехвачены злоумышленником;* Пока вы не настроите исходящие сообщения, существует вероятность, что ваша система просто «заспамит» чей-то почтовый ящик;Учитывая вышеприведенные преимущества и недостатки, наша система авторизации выходит довольно пригодной для использования, но не очень безопасной, поэтому мы рекомендуем вам использовать ее только в случаях регистрации на форумах, в зоне пользователей на сайте и сервисах, для которых не требуется какая-либо чувствительная информация.

Применение системы регистрации

В том случае, если вы просто хотите использовать нашу систему регистрации на вашем сайте, и не хотите изучать руководство, то вам нужно сделать следующее:* Сначала нужно скачать вышеприведенный zip-файл;* В zip-файле найти файл tables.sql. Импортировать его в базу данных, используя опцию импорта в phpmyadmin. С другой стороны, вы можете открыть файл в текстовом редакторе, скопировать SQL и запустить его;* Откройте includes/main.php, и внесите туда данные для соединения с базой данных, а также комбинацию логин/пароль. В том же файле вам также нужно добавить email, который будет использовать в качестве отправителя сообщений. Некоторые веб-хостинги блокируют исходящие сообщения до тех пор, пока вы не укажите настоящий email-адрес, который можно создать при помощи панели управления этого же хостера;* Загрузите все необходимые файлы посредством FTP или других способов;* Добавьте предпоследний фрагмент кода (см. ниже) на каждую PHP-страницу, которые должны стать доступными только после авторизации;* Веселитесь!Те, кто решил остаться с нами и дочитать статью до конца, предлагаем дальнейшую информацию:

HMTL-код

Первый этап заключается в создании HTML-кода для формы авторизации. Следующий HTML-код находится в index.php. Этот файл также хранит в себе PHP-код, который отвечает за подтверждение формы и другие полезные функции нашей системы авторизации. Вы узнаете об этом подробнее в разделе с PHP-кодом.

Index.php




    
        
        Tutorial: Super Simple Registration System With PHP & MySQL

        
        

        
    

    

        

            

Login or Register


            
            
Enter your email address above and we will send
you a login link. Login / Register

В головной раздел мы включили основные таблицы стилей (они не представлены в руководстве, поэтому вам нужно будет открыть файл в редакторе, чтобы ознакомиться с ними). Перед закрывающих тегом body мы включили jQuery-библиотеку и файл script.js, разработкой которого мы займемся в следующем разделе статьи. 1377080973_1377077836_simple-registration-system-02-9135431 Теперь давайте займемся jQuery!

javascript-код

jQuery ожидает событие формы submit, запускает e.preventDefault(), и отправляет AJAX-запрос. В зависимости от ответа сервера, он отображает сообщение и предотвращает дальнейшее подтверждение.

assets/js/script.js

$(function(){

    var form = $('#login-register');

    form.on('submit', function(e){

        if(form.is('.loading, .loggedIn')){
            return false;
        }

        var email = form.find('input').val(),
            messageHolder = form.find('span');

        e.preventDefault();

        $.post(this.action, {email: email}, function(m){

            if(m.error){
                form.addClass('error');
                messageHolder.text(m.message);
            }
            else{
                form.removeClass('error').addClass('loggedIn');
                messageHolder.text(m.message);
            }
        });

    });

    $(document).ajaxStart(function(){
        form.addClass('loading');
    });

    $(document).ajaxComplete(function(){
        form.removeClass('loading');
    });
});

CSS-класс .loading добавляется к форме посредством Ajax-запроса в самом начале (это возможно благодаря методам ajaxStart() и ajaxComplete(), которые вы можете видеть практически в конце файла). Этот CSS-класс позволяет отобразить вращающийся индикатор, и вдобавок выступает в роли маркера, предотвращающего двойного подтверждения формы. Класс .loggedIn – это еще один маркер, который устанавливается как только email-сообщение будет отправлено. Он сразу же блокирует любые дальнейшие подтверждения.

Структура базы данных

Нашей простой системе регистрации понадобится 2 MySQL-таблицы (SQL-код вы можете видеть в файле tables.sql в zip-архиве). Первая таблица содержит пользовательские аккаунты, а вторая будет хранить данные о попытках авторизации. 1377081054_1377077764_simple-registration-system-03-2397112 Система не использует паролей, что приводит к отсутствию поля password в структуре. Здесь есть колонка token вместе с колонкой token_validity. Token (лексема) устанавливается, когда пользователь авторизуется в систему, и высылается на указанный пользователем email (подробнее об этом в следующем разделе). Затем token_validity устанавливается на 10 минут, после чего token станет недействительным.Каждый раз, когда кто-нибудь пытается авторизоваться, во вторую таблицу вписывается новая запись. Как видно в нашем PHP-коде, благодаря этому у нас есть возможность реализовать ограничение количества попыток по IP-адресу. Мы устанавливаем ограничение в 10 попыток за 10 минут, и ограничение в 20 попыток в течение часа. Большее количество попыток приведет к блокировке IP-адреса. 1377081070_1377077816_simple-registration-system-04-6823822 В обеих таблицах IP-адрес хранится в виде целого числа с использованием PHP-функции ip2long.

PHP

Теперь мы готовы к тому, чтобы заняться кодом PHP. Основной функционал системы регистрации предоставляется классом User, который вы можете видеть ниже. Класс использует Idorm (документация), представляющую собой минималистскую библиотеку для работы с базами данных. Класс User отвечает за доступ к базам данных, генерирование token-ов для логина и их валидации. Он представляет нам простой интерфейс, который можно без труда включить в систему регистрации на ваших сайтах, основанных на PHP.

User.class.php

class User{

    // Private ORM instance
    private $orm;

    /**
     * Find a user by a token string. Only valid tokens are taken into
     * consideration. A token is valid for 10 minutes after it has been generated.
     * @param string $token The token to search for
     * @return User
     */

    public static function findByToken($token){

        // find it in the database and make sure the timestamp is correct

        $result = ORM::for_table('reg_users')
                        ->where('token', $token)
                        ->where_raw('token_validity > NOW()')
                        ->find_one();

        if(!$result){
            return false;
        }

        return new User($result);
    }

    /**
     * Either login or register a user.
     * @param string $email The user's email address
     * @return User
     */

    public static function loginOrRegister($email){

        // If such a user already exists, return it

        if(User::exists($email)){
            return new User($email);
        }

        // Otherwise, create it and return it

        return User::create($email);
    }

    /**
     * Create a new user and save it to the database
     * @param string $email The user's email address
     * @return User
     */

    private static function create($email){

        // Write a new user to the database and return it

        $result = ORM::for_table('reg_users')->create();
        $result->email = $email;
        $result->save();

        return new User($result);
    }

    /**
     * Check whether such a user exists in the database and return a boolean.
     * @param string $email The user's email address
     * @return boolean
     */

    public static function exists($email){

        // Does the user exist in the database?
        $result = ORM::for_table('reg_users')
                    ->where('email', $email)
                    ->count();

        return $result == 1;
    }

    /**
     * Create a new user object
     * @param $param ORM instance, id, email or null
     * @return User
     */

    public function __construct($param = null){

        if($param instanceof ORM){

            // An ORM instance was passed
            $this->orm = $param;
        }
        else if(is_string($param)){

            // An email was passed
            $this->orm = ORM::for_table('reg_users')
                            ->where('email', $param)
                            ->find_one();
        }
        else{

            $id = 0;

            if(is_numeric($param)){
                // A user id was passed as a parameter
                $id = $param;
            }
            else if(isset($_SESSION['loginid'])){

                // No user ID was passed, look into the sesion
                $id = $_SESSION['loginid'];
            }

            $this->orm = ORM::for_table('reg_users')
                            ->where('id', $id)
                            ->find_one();
        }

    }

    /**
     * Generates a new SHA1 login token, writes it to the database and returns it.
     * @return string
     */

    public function generateToken(){
        // generate a token for the logged in user. Save it to the database.

        $token = sha1($this->email.time().rand(0, 1000000));

        // Save the token to the database, 
        // and mark it as valid for the next 10 minutes only

        $this->orm->set('token', $token);
        $this->orm->set_expr('token_validity', "ADDTIME(NOW(),'0:10')");
        $this->orm->save();

        return $token;
    }

    /**
     * Login this user
     * @return void
     */

    public function login(){

        // Mark the user as logged in
        $_SESSION['loginid'] = $this->orm->id;

        // Update the last_login db field
        $this->orm->set_expr('last_login', 'NOW()');
        $this->orm->save();
    }

    /**
     * Destroy the session and logout the user.
     * @return void
     */

    public function logout(){
        $_SESSION = array();
        unset($_SESSION);
    }

    /**
     * Check whether the user is logged in.
     * @return boolean
     */

    public function loggedIn(){
        return isset($this->orm->id) && $_SESSION['loginid'] == $this->orm->id;
    }

    /**
     * Check whether the user is an administrator
     * @return boolean
     */

    public function isAdmin(){
        return $this->rank() == 'administrator';
    }

    /**
     * Find the type of user. It can be either admin or regular.
     * @return string
     */

    public function rank(){
        if($this->orm->rank == 1){
            return 'administrator';
        }

        return 'regular';
    }

    /**
     * Magic method for accessing the elements of the private
     * $orm instance as properties of the user object
     * @param string $key The accessed property's name 
     * @return mixed
     */

    public function __get($key){
        if(isset($this->orm->$key)){
            return $this->orm->$key;
        }

        return null;
    }
}

Token-ы генерируются при помощи алгоритма SHA1, и сохраняются в базу данных. Мы используем функции даты и времени из MySQL для установки значения в колонку token_validity, равного 10 минутам. При валидации token, мы сообщаем движку, что нам нужен token, поле token_validity пока еще не истекло. Таким образом мы ограничиваем время, в течение которого token будет валиден.

Обратите внимание на то, что мы используем волшебный метод __get (документация) в конце документа, чтобы получить доступ к свойствам объекта user. Это позволяет нам осуществить доступ к данным, которые хранятся в базе данных в виде свойств: $user->email, $user->token. Для примера давайте посмотрим, как мы можем использовать этот класс в следующем фрагменте кода:

1377081238_1377077855_simple-registration-system-05-6580493 Еще один файл, в котором хранится необходимый функционал, это functions.php. Там у нас есть несколько вспомогательных функций, которые позволяют нам сохранить остальной код более опрятным.

Functions.php

function send_email($from, $to, $subject, $message){

    // Helper function for sending email

    $headers  = 'MIME-Version: 1.0' . "rn";
    $headers .= 'Content-type: text/plain; charset=utf-8' . "rn";
    $headers .= 'From: '.$from . "rn";

    return mail($to, $subject, $message, $headers);
}

function get_page_url(){

    // Find out the URL of a PHP file

    $url = 'http'.(empty($_SERVER['HTTPS'])?'':'s').'://'.$_SERVER['SERVER_NAME'];

    if(isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] != ''){
        $url.= $_SERVER['REQUEST_URI'];
    }
    else{
        $url.= $_SERVER['PATH_INFO'];
    }

    return $url;
}

function rate_limit($ip, $limit_hour = 20, $limit_10_min = 10){

    // The number of login attempts for the last hour by this IP address

    $count_hour = ORM::for_table('reg_login_attempt')
                    ->where('ip', sprintf("%u", ip2long($ip)))
                    ->where_raw("ts > SUBTIME(NOW(),'1:00')")
                    ->count();

    // The number of login attempts for the last 10 minutes by this IP address

    $count_10_min =  ORM::for_table('reg_login_attempt')
                    ->where('ip', sprintf("%u", ip2long($ip)))
                    ->where_raw("ts > SUBTIME(NOW(),'0:10')")
                    ->count();

    if($count_hour > $limit_hour || $count_10_min > $limit_10_min){
        throw new Exception('Too many login attempts!');
    }
}

function rate_limit_tick($ip, $email){

    // Create a new record in the login attempt table

    $login_attempt = ORM::for_table('reg_login_attempt')->create();

    $login_attempt->email = $email;
    $login_attempt->ip = sprintf("%u", ip2long($ip));

    $login_attempt->save();
}

function redirect($url){
    header("Location: $url");
    exit;
}

Функции rate_limit и rate_limit_tick позволяют нам ограничивать число попыток авторизации на определенный промежуток времени. Попытки авторизации записываются в базу данных reg_login_attempt. Эти функции запускаются при проведении подтверждения формы авторизации, как можно видеть в следующем фрагменте кода.Нижеприведенный код был взят из index.php, и он отвечает за подтверждение формы авторизации. Он возвращает JSON-ответ, который управляется кодом jQuery, который мы видели в assets/js/script.js.

index.php

try{

    if(!empty($_POST) && isset($_SERVER['HTTP_X_REQUESTED_WITH'])){

        // Output a JSON header

        header('Content-type: application/json');

        // Is the email address valid?

        if(!isset($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)){
            throw new Exception('Please enter a valid email.');
        }

        // This will throw an exception if the person is above 
        // the allowed login attempt limits (see functions.php for more):
        rate_limit($_SERVER['REMOTE_ADDR']);

        // Record this login attempt
        rate_limit_tick($_SERVER['REMOTE_ADDR'], $_POST['email']);

        // Send the message to the user

        $message = '';
        $email = $_POST['email'];
        $subject = 'Your Login Link';

        if(!User::exists($email)){
            $subject = "Thank You For Registering!";
            $message = "Thank you for registering at our site!nn";
        }

        // Attempt to login or register the person
        $user = User::loginOrRegister($_POST['email']);

        $message.= "You can login from this URL:n";
        $message.= get_page_url()."?tkn=".$user->generateToken()."nn";

        $message.= "The link is going expire automatically after 10 minutes.";

        $result = send_email($fromEmail, $_POST['email'], $subject, $message);

        if(!$result){
            throw new Exception("There was an error sending your email. Please try again.");
        }

        die(json_encode(array(
            'message' => 'Thank you! We've sent a link to your inbox. Check your spam folder as well.'
        )));
    }
}
catch(Exception $e){

    die(json_encode(array(
        'error'=>1,
        'message' => $e->getMessage()
    )));
}

При успешной авторизации или регистрации, вышеприведенный код отсылает email человеку с ссылкой для авторизации. Token (лексема) становится доступной в качестве $_GET-переменной ‘tkn’ ввиду сгенерированного URL.Использование ссылки авторизации запустит этот код:

index.php

if(isset($_GET['tkn'])){

    // Is this a valid login token?
    $user = User::findByToken($_GET['tkn']);

    if($user){

        // Yes! Login the user and redirect to the protected page.

        $user->login();
        redirect('protected.php');
    }

    // Invalid token. Redirect back to the login form.
    redirect('index.php');
}

Запуск $user->login() создаст необходимые переменные для сессии, что позволит пользователю оставаться авторизованным при последующих входах.Выход из системы реализуется примерно таким же образом:

Index.php

if(isset($_GET['logout'])){

    $user = new User();

    if($user->loggedIn()){
        $user->logout();
    }

    redirect('index.php');
}

В конце кода мы снова перенаправляем пользователя на index.php, поэтому параметр ?logout=1 в URL исключается.Нашему файлу index.php также потребуется защита – мы не хотим, чтобы уже авторизованные пользователи видели форму. Для этого мы используем метод $user->loggedIn():

Index.php

$user = new User();

if($user->loggedIn()){
    redirect('protected.php');
}

Наконец, давайте посмотрим, как можно защитить страницу вашего сайта, и сделать ее доступной только после авторизации:

protected.php

// To protect any php page on your site, include main.php
// and create a new User object. It's that simple!

require_once 'includes/main.php';

$user = new User();

if(!$user->loggedIn()){
    redirect('index.php');
}

После этой проверки вы можете быть уверены в том, что пользователь успешно авторизовался. У вас также будет доступ к данным, которые хранятся в базе данных в качестве свойств объекта $user. Чтобы вывести email пользователя и их ранг, воспользуйтесь следующим кодом:

echo 'Your email: '.$user->email;
echo 'Your rank: '.$user->rank();

Здесь rank() – это метод, так как колонка rank в базе данных обычно содержит числа (0 для обычных пользователей и 1 для администраторов), и нам нужно преобразовать это все в названия рангов, что реализуется при помощи данного метода. Чтобы преобразовать обычного пользователя в администратора, просто отредактируйте запись о пользователе в phpmyadmin (либо в любой другой программе по работе с базами данных). Будучи администратором, пользователь не будет наделен какими-то особыми возможностями. Вы сами в праве выбирать, каким правами наделять администраторов.Готово!

Исходники в архиве.