Создание простой системы регистрации пользователей на 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 с нуля с использованием ООП:
Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!
Смотрите также:

















