Reg.ru: домены и хостинг

Крупнейший регистратор и хостинг-провайдер в России.

Более 2 миллионов доменных имен на обслуживании.

Продвижение, почта для домена, решения для бизнеса.

Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Перейти на сайт->

Бесплатный Курс "Практика HTML5 и CSS3"

Освойте бесплатно пошаговый видеокурс

по основам адаптивной верстки

на HTML5 и CSS3 с полного нуля.

Начать->

Фреймворк Bootstrap: быстрая адаптивная вёрстка

Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.

Верстайте на заказ и получайте деньги.

Получить в подарок->

Бесплатный курс "Сайт на WordPress"

Хотите освоить CMS WordPress?

Получите уроки по дизайну и верстке сайта на WordPress.

Научитесь работать с темами и нарезать макет.

Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!

Получить в подарок->

*Наведите курсор мыши для приостановки прокрутки.


Создание простой системы регистрации пользователей на PHP и MySQL

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

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

В этом уроке, мы создадим очень простую систему регистрации, которая не требует и не хранит паролей вообще! Результат будет легко изменить и добавить к уже существующему PHP-сайту. Хотите выяснить, как это работает? Читайте ниже.


Скачать исходники к уроку


Вот как наша супер простая система будет работать:

- Мы скомбинируем форму авторизации и регистрацию. В этой форме будет поле для ввода email-адреса и кнопка регистрации;
- При заполнении поля email-адресом, по нажатию на кнопку регистрации будет создана запись о новом пользователе, но только в том случае, если введённого email-адреса не было найдено в базе данных.

После этого создаётся некий случайный уникальный набор символов (токен), который отправляется на указанную пользователем почту в виде ссылки, которая будет актуальна в течение 10 минут;
- По ссылке пользователь переходит на наш сайт. Система определяет наличие токена и авторизует пользователя;

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

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

Недостатки:

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

Сравнивая преимущества и недостатки нашей системы, можно сказать, что система имеет высокое юзабилити (максимально удобна для конечного пользователя) и, в то же время, имеет невысокий показатель безопасности.

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

Как пользоваться этой системой

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

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


require_once 'includes/main.php';

$user = new User();

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


- Готово!

Для тех же, кому интересно, как это всё работает - вперёд к чтению ниже!

HTML

Первый шаг - написание HTM- кода формы авторизации. Данный код располагается в файле index.php. Этот файл также содержит PHP-код, обрабатывающий данные формы и другие полезные функции системы авторизации. Узнать об этом больше можно в разделе ниже, посвящённом обзору PHP кода.

index.php


<!DOCTYPE html>
<html>

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

        <!-- Главный CSS файл -->
        <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>

        <!-- Подключение яваскрипт -->
        <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> и </head>) я подключил основные стили (в этом уроке они не разбираются, поэтому вы можете посмотреть их сами. Папка assets/css/style.css). До закрывающего тега </body> я подключил библиотеку jQuery и файл script.js, который мы напишем и разберём чуть ниже.

Форма авторизации/регистрации

JavaScript

jQuery отслеживает состояние кнопки "Зарегистрироваться/авторизоваться" с помощью функции 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');
    });
});

Класс .loading был добавлен в форму для отображения текущего состояния AJAX-запроса ( это стало возможным благодаря методам ajaxStart()) и ajaxComplete(), которые вы сможете найти ближе к концу файла).

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

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

Наша невероятно простая система регистрации использует 2 MySQL таблицы (SQL-код находится в файле tables.sql). Первая хранит данные об аккаунтах пользователей. Вторая хранит информацию о количестве попыток входа.

Схема таблицы пользователей.

Система не использует паролей, что видно на схеме. На ней же можно увидеть колонку token с токенами, соседствующую с колонкой token_validity. Токен устанавливается как только пользователь подключается к системе, задаёт свой email для отправки сообщения (чуть подробнее об этом в следующем блоке). Колонка token_validity устанавливает время на 10 минут позже, после которого токен перестаёт быть актуальным.

Каждый раз, когда кто-то пытается авторизоваться, появляется новая запись во второй таблице (reg_loggin_attempt). Как вы могли заметить, в нашем PHP-коде существует ограничение по количеству попыток авторизации в зависимости от IP-адреса.

Ограничение установлено на 10 попыток авторизации в течение 10 минут и на 20 попыток в течение часа. Большее число попыток приведёт к блокировке IP-адреса, до тех пор, пока не пройдёт необходимое время (10 минут или час соответственно).

Схема таблицы, считающая количество попыток авторизации.

В обоих таблицах IP-адрес хранится в обработанном виде, с помощью функции ip2long в поле типа integer.

PHP

Теперь мы можем написать немножко PHP-кода. Основной функционал системы возложен на класс User.class.php, который вы можете видеть ниже.

Данный класс активно использует idorm (docs), эти библиотеки являются минимально необходимыми инструментами, для работы с базами данных. Он обрабатывает доступ к базе данных, генерацию токенов и их валидацию. Он представляет собой простой интерфейс, позволяющий легко подключить систему регистрации к вашему сайту, если он использует PHP.

User.class.php


	class User{

    // Частный ORM случай
    private $orm;

     /**
     * Найти пользователя по токену. Только валидные токены, приняты к рассмотрению. Токен генерируется только на 10 минут с того момента как был создан
     *  @param string $token. Это искомый токен
     *	@return User. Вернуть значение функции 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. Пользовательский email-адрес
     *	@return User
     */

    public static function loginOrRegister($email){

        // Если такой пользователь уже существует, вернуть значение функции User от заданного email-адреса хранимого в базе

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

        // В противном случае создать нового пользователя в базе и вернуть значение функции User::create от указанного email

        return User::create($email);
    }

     /**
     * Создать нового пользователя и сохранить в базу
     * @param string $email. Пользовательский email-адрес
     * @return User
     */

    private static function create($email){

        // Записать нового пользователя и вернуть результат функции
        User от этих значений

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

        return new User($result);
    }

     /**
     * Проверить, существует ли такой пользователь в базе и вернуть булево значение переменной
     * @param string $email. Пользовательский email-адрес
     * @return boolean
     */

    public static function exists($email){

        // Существует ли пользователь в базе?
        $result = ORM::for_table('reg_users')
                    ->where('email', $email)
                    ->count();

        return $result == 1;
    }

    /**
     * Создать новый пользовательский объект
     * @param экземпляр $param ORM , id, email or 0
     * @return User
     */


    public function __construct($param = null){

        if($param instanceof ORM){

            // ORM проверка пройдена
            $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)){
                // идентификатору пользователя передаётся значение переменной $param
                $id = $param;
            }
            else if(isset($_SESSION['loginid'])){

                // В противном случае смотри сессию
                $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';
    }

    /**
     * Найти тип пользователя, может быть либо administrator либо regular
     * @return string
     */

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

        return 'regular';
    }

    /**
     * Метод позволяющий получить приватную информацию пользователя в 
     *качестве свойств объекта User
     * @param string $key Имя свойства, получающего доступ 
     * @return mixed
     */

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

        return null;
    }
}

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

Когда токен проходит процедуру валидации, мы напрямую говорим обработчику, что мы рассматриваем только токены, у которых ещё не истёк срок годности, хранимый в столбце token_validity.

Обратите внимание, что я использую волшебный метод __get библиотеки docs в конце файла, чтобы перехватить доступ к свойствам объекта User.

Благодаря этому становится возможным получить доступ к информации, хранящейся в базе, благодаря свойствам $user->email, $user->token и др. В следующем фрагменте кода рассмотрим для примера, как использовать эти классы.

Защищённая страница

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

functions.php


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

    // Хелпер, отправляющий email
    $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 PHP-файла

    $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 минут по этому IP-адресу
    $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){

    // Создать новую запись в таблице, считающей количество попыток входа
    $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');

        // Является ли этот email-адрес валидным
        if(!isset($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)){
            throw new Exception('Please enter a valid email.');
        }

        // Проверка. Позволено ли пользователю авторизоваться, не превысил ли он количество допустимых подключений? (файл functions.php для большей информации)
        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 с маркером tkn

Переход по полученной ссылке послужит триггером для запуска кода ниже:

index.php


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

    // Является ли этот токен валидным для авторизации?
    $user = User::findByToken($_GET['tkn']);

    if($user){

        // Да, является. Осуществить редирект на защищённую страницу
        $user->login();
        redirect('protected.php');
    }

    // Нет, токен не валидный. Осуществить редирект, на страницу с формой авторизации/регистрации
    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


// Чтобы защитить каждую страницу на вашем сайте подключите к ней файл
// main.php и создайте новый объект User. Вот как это просто!

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() используется здесь потому что в базе обычно хранятся номера (0 для обычного пользователя, 1 для администратора) и нам нужно преобразовать эти данные в статусы, к которым они относятся, в чём нам и помогает этот метод.

Чтобы сделать из обычного пользователя администратора, просто отредактируйте пользовательскую запись через phpMyAdmin (или любую другую программу, позволяющую управлять базами данных). Статус администратора не даёт каких-либо привилегий, в данном примере на странице будет выведено, что вы администратор - и всё.

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

Мы закончили!

С этой невероятно супер квази простой формой мы закончили! Вы можете использовать её в ваших PHP-сайтах, это достаточно просто. Также вы можете модифицировать её под себя и сделать её такой, как вы хотите.

Материал подготовил Денис Малышок специально для сайта CodeHarmony.ru

P.S. Хотите двигаться дальше в освоении PHP и ООП? Обратите внимание на премиум-уроки по различным аспектам сайтостроения, включая программирование на PHP, а также на бесплатный курс по созданию своей CMS-системы на PHP с нуля с использованием ООП:

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!


Смотрите также:

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

CodeIgniter: жив или мертв?

CodeIgniter: жив или мертв?

Функции обратного вызова, анонимные функции и механизм замыканий

Функции обратного вызова, анонимные функции и механизм замыканий

Применение функции к каждому элементу массива

Применение функции к каждому элементу массива

Слияние массивов. Преобразование массива в строку

Слияние массивов. Преобразование массива в строку

Деструктор и копирование объектов с помощью метода __clone()

Деструктор и копирование объектов с помощью метода __clone()

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.)

Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.)

PHP: Удаление элементов массива

PHP: Удаление элементов массива

Ключевое слово final (завершенные классы и методы в PHP)

Ключевое слово final (завершенные классы и методы в PHP)

50 классных сервисов, программ и сайтов для веб-разработчиков

50 классных сервисов, программ и сайтов для веб-разработчиков

Наверх