Простая система регистрации на PHP и MySql

13-08-24 Php MySql, PHP 2

Для создания системы регистрации необходимо проделать много работы. Вы должны написать  код для проверки email адресов, рассылки писем для подтверждения, восстановления пароля, валидации входных данных и т.д. Даже когда вы все это проделаете, посетители не хотят регистрироваться, потому что это требует от них много усилий.

В этом уроке мы сделаем очень простую систему регистрации, которой даже не потребуется хранить пароль! Её очень просто модифицировать и встроить в существующий сайт на PHP.

Идея

Как система регистрации будет работать:

  • Форма для логина и регистрации объединены в одну, где пользователи будут указывать свой email;
  • Если указанного почтового адреса нет в базе, создается новая запись. Генерируется случайный ключ и отправляется на почту пользователя в виде ссылки, которая активна в течении 10 минут;
  • При нажатии на ссылку пользователь переходит на ваш сайт. Система сама авторизует пользователя по переданному в ссылке ключу.

Преимущества такого подхода:

  • Нет необходимости в хранении и валидации паролей;
  • Нет необходимости в системе восстановления паролей;
  • Процесс регистрации очень прост и привлекателен.

Недостатки:

  • Система безопасна до те пор, пока в безопасности почта пользователя. Если у кого-то есть доступ к почте пользователя, он может легко авторизоваться. Но это также относится и к другим системам регистрации (с функционалом восстановления пароля);
  • Email передается открыто и может быть перехвачен (также относится ко всем системам регистрации, не использующим HTTPS);
  • Если не уделить должного внимания к настройке и оформлению исходящих писем, есть шанс, что они попадут в спам.

Исходя из перечисленного, наша система регистрации очень удобна в использовании, но не полностью безопасна, поэтому использовать её лучше для простых вещей, вроде регистрации на форуме и сервисов, которые не используют важную конфиденциальную информацию.

Использование системы регистрации

Если вы просто хотите начать использовать эту систему, не просматривая урок, вам необходимо:

  1. Скачайте исходники по ссылке выше;
  2. В zip файле найдите файл tables.sql. Импортируйте его в свою базу с помощью phpMyAdmin. Также можно просто скопировать SQL код из файла и выполнить его;
  3. Откройте includes/main.php и введите данные для соединения с базой имя пользователя/пароль. В этом же файле надо указать email, который будет использован в качестве отправного. Некоторые почтовые системы блокируют письма без указания это адреса;
  4. Загрузите файлы на сервер;
  5. Добавьте этот код на каждую страницу, если хотите, что бы она была доступна только авторизованным пользователям;

Если хотите знать как работает система регистрации, продолжайте читать! 🙂

HTML

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

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8"/>
        <title>Tutorial: Super Simple Registration System With PHP &amp; MySQL</title>

        <!-- The main CSS file -->
        <link href="assets/css/style.css" rel="stylesheet" />

        <!--[if lt IE 9]>
            <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>

        <form id="login-register" method="post" action="index.php">

            <h1>Login or Register</h1>

            <input type="text" placeholder="your@email.com" name="email" autofocus />
            <p>Enter your email address above and we will send <br />you a login link.</p>

            <button type="submit">Login / Register</button>

            <span></span>

        </form>

        <!-- JavaScript Includes -->
        <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script src="assets/js/script.js"></script>

    </body>
</html>

В теге <head> подключаем основной CSS файл (в уроке он не представлен, посмотрите его в исходниках). Перед закрытием <body> подключаем jQuery и файл script.js, который напишем ниже.

JavaScript

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

$(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()). Этот класс добавляет вращающийся gif индикатор, а также блокирует повторный сабмит формы. CSS класс .loggedIn cдобавляется email был отправлен и блокирует форму перманентно.

Схема базы данных

Наша простая система регистрации использует две MySql таблицы (SQL код для их создания вы найдете в файле tables.sql). Первая таблица содержит данные пользователей, вторая — попытки залогиниться.

Таблица reg_users:

id - integer
email - varchar
rank - varchar (administrator|regular)
registered - timestamp
last_login - timestamp
token - varchar
token_validity - timestamp

Система не использует пароль, что видно по отсутствию соответствующего поля в таблице. Есть поле token и token_validity. token — создается когда пользователь входит в систему и отправляется ему на почту. token_validity — таймстамп, задающий время действия пароля (10 минут).

Каждый раз, когда кто-то пытается войти в систему, во второй таблице создается запись. Это необходимо для ограничения попыток логина, 10 попыток каждые 10 минут и 20 каждый час. Если ограничения нарушаются, то ip адрес блокируется.

Таблица reg_login_attempt:

id - integer
ip - integer
email - varchar
ts - timestamp

ip в базе хранится как число, полученное с помощью PHP функции ip2long.

PHP

Теперь займемся PHP кодом. Основной функционал системы реализован в классе User. Этот класс использует ORM библиотеку idiorm, для связи с базой данных (мы также использовали её в уроке Создание графиков с помощью jQuery и xCharts). Класс User обрабатывает данные из базы данных, генерирует коды и проверяет их. Класс имеет простой интерфейс, который легко встроить в существующий PHP сайт.

class User{

    // Private ORM instance
    private $orm;

    /**
     * Находим юзера по коду
     * @param string $token
     * @return User
     */

    public static function findByToken($token){

        // находим код и убеждаемся, что он действителен

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

        if(!$result){
            return false;
        }

        return new User($result);
    }

    /**
     * Авторизация или регистрация юзера
     * @param string $email
     * @return User
     */

    public static function loginOrRegister($email){

        // Если есть такой юзер - возвращаем

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

        // иначе - создаем и возвращаем

        return User::create($email);
    }

    /**
     * Создание нового юзера
     * @param string $email
     * @return User
     */

    private static function create($email){

        // Записываем и возвращаем

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

        return new User($result);
    }

    /**
     * Проверка существования пользователя.
     * @param string $email
     * @return boolean
     */

    public static function exists($email){

        $result = ORM::for_table('reg_users')
                    ->where('email', $email)
                    ->count();

        return $result == 1;
    }

    /**
     * Конструктор
     * @param $param ORM instance, id, email или null
     * @return User
     */

    public function __construct($param = null){

        if($param instanceof ORM){

            // передали ORM instance
            $this->orm = $param;
        }
        else if(is_string($param)){

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

            $id = 0;

            if(is_numeric($param)){
                // или id
                $id = $param;
            }
            else if(isset($_SESSION['loginid'])){

                // id не передали, ищем в сессии
                $id = $_SESSION['loginid'];
            }

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

    }

    /**
     * Генерация SHA1 ключей.
     * @return string
     */

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

        // Сохраняем в базу,
        // и делаем активной в течение 10 минут

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

        return $token;
    }

    /**
     * Авторизация
     * @return void
     */

    public function login(){

        // Запоминаем в сессии
        $_SESSION['loginid'] = $this->orm->id;

        // Обновим last_login
        $this->orm->set_expr('last_login', 'NOW()');
        $this->orm->save();
    }

    /**
     * Выход пользователя.
     * @return void
     */

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

    /**
     * Проверка авторизации.
     * @return boolean
     */

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

    /**
     * Администратор?
     * @return boolean
     */

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

    /**
     * Роль пользователя: admin или regular.
     * @return string
     */

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

        return 'regular';
    }

    /**
     * Доступ к полям
     * @param string $key
     * @return mixed
     */

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

        return null;
    }
}

Коды генерируются с помощью алгоритма SHA1 и сохраняются в базе. Для установления действительности кодов мы используем функции MySql.

Мы используем метод __get для обращения к полям из базы данных, как к свойствам класса, например: $user->email$user->token. Пример того как все это использовать — дальше.

В файле functions.php находятся вспомогательные функции.

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

    // Отправка писем

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

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

function get_page_url(){

    // Формирует URL

    $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){

    // Количество попыток логина с ip за час

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

    // за 10 минут

    $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){

    // Создание записи в таблице reg_login_attempt

    $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 появляется новая запись. Эти функции вызываются при сабмите формы для логина.

Код ниже обрабатывает входные данные и возвращает JSON ответ, который обрабатывается в файле assets/js/script.js.

try{

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

        // вывод JSON заголовка

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

        // проверка правильности email адреса

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

        // Проверка количества попыток:
        rate_limit($_SERVER['REMOTE_ADDR']);

        // Запись попытки
        rate_limit_tick($_SERVER['REMOTE_ADDR'], $_POST['email']);

        // Пишем письмо

        $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!\n\n";
        }

        // Авторизация или регистрация
        $user = User::loginOrRegister($_POST['email']);

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

        $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()
    )));
}

При успешной авторизации или регистрации код вышлет письмо со ссылкой для входа. Код для входа будет доступен в глобальном массиве $_GET, т.к. мы передаем его в ссылке.

При переходе по ссылке будет запущен этот код:

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

    // Проверка действительности кода
    $user = User::findByToken($_GET['tkn']);

    if($user){

        // Код действителен, перенаправляем на закрытую страницу.

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

    // Код недействителен.
    redirect('index.php');
}

Функция $user->login() инициализирует сессию это значит что при посещении любой страницы, мы будем знать, что пользователь авторизован.

Выход из системы:

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

    $user = new User();

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

    redirect('index.php');
}

Если пользователь уже вошел, ему не надо видеть форму для логина. Здесь нам пригодится метод $user->loggedIn():

$user = new User();

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

Наконец, код, чтобы сделать любую страницу доступно только после авторизации:

// Для защиты страниц вашего сайта, подключите main.php
// и создайте новый объект типа User.

require_once 'includes/main.php';

$user = new User();

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

После этой проверки вы можете быть уверенным, что пользователь успешно вошел в систему. Вы также можете получить доступ к информации из базы данных. Для вывода почты роли пользователя используйте код:

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

rank() — функция, потому что роли может быть две. В базе они хранятся как 0 или 1, поэтому перед выводом надо конвертировать значение из базы.

Наша система регистрации готова!

Смотрите также: система регистрации пользователей на PHP, MySql и jQuery, Регистрация в один клик от Google.

Хочешь получать статьи на почту?

Подпишись на обновления!
* Ваш email не будет разглашен/продан. Вы сможете отписаться в любое время.

2 Комментария

  1. Роман:

    А видео урок есть по данной теме?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *