Создание простой системы регистрации пользователей на PHP и MySQL
Создание системы регистрации - это большая работа. Вам приходится писать код, который осуществляет валидацию email-адресов, отправляет сообщение на почту с подтверждением регистрации, а также осуществляет валидацию остальных полей формы, и много ещё всего.
И даже после того как вы всё это напишите, пользователи будут регистрироваться неохотно, т.к. это требует определённых усилий с их стороны.
В этом уроке, мы создадим очень простую систему регистрации, которая не требует и не хранит паролей вообще! Результат будет легко изменить и добавить к уже существующему PHP-сайту. Хотите выяснить, как это работает? Читайте ниже.
Вот как наша супер простая система будет работать:
- Мы скомбинируем форму авторизации и регистрацию. В этой форме будет поле для ввода email-адреса и кнопка регистрации;
- При заполнении поля email-адресом, по нажатию на кнопку регистрации будет создана запись о новом пользователе, но только в том случае, если введённого email-адреса не было найдено в базе данных.
После этого создаётся некий случайный уникальный набор символов (токен), который отправляется на указанную пользователем почту в виде ссылки, которая будет актуальна в течение 10 минут;
- По ссылке пользователь переходит на наш сайт. Система определяет наличие токена и авторизует пользователя;
Преимущества такого подхода:
- Не нужно хранить пароли и осуществлять валидацию полей;
- Нет необходимости в восстановлении пароля, секретных вопросов и т.д.;
- С момента как пользователь зарегистрировался/авторизовался вы можете всегда быть уверены, что этот пользователь будет в вашей зоне доступа (что email-адрес является истинным);
- Невероятно простой процесс регистрации;
— Регулярная проверка качества ссылок по более чем 100 показателям и ежедневный пересчет показателей качества проекта.
— Все известные форматы ссылок: арендные ссылки, вечные ссылки, публикации (упоминания, мнения, отзывы, статьи, пресс-релизы).
— SeoHammer покажет, где рост или падение, а также запросы, на которые нужно обратить внимание.
SeoHammer еще предоставляет технологию Буст, она ускоряет продвижение в десятки раз, а первые результаты появляются уже в течение первых 7 дней. Зарегистрироваться и Начать продвижение
Недостатки:
- Безопасность аккаунта пользователя. Если кто-то имеет доступ к почте пользователя, он может авторизоваться.
- 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 с нуля с использованием ООП:
Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!
Смотрите также: