Простая система регистрации на PHP и MySql
Для создания системы регистрации необходимо проделать много работы. Вы должны написать код для проверки email адресов, рассылки писем для подтверждения, восстановления пароля, валидации входных данных и т.д. Даже когда вы все это проделаете, посетители не хотят регистрироваться, потому что это требует от них много усилий.
В этом уроке мы сделаем очень простую систему регистрации, которой даже не потребуется хранить пароль! Её очень просто модифицировать и встроить в существующий сайт на PHP.
Идея
Как система регистрации будет работать:
- Форма для логина и регистрации объединены в одну, где пользователи будут указывать свой email;
- Если указанного почтового адреса нет в базе, создается новая запись. Генерируется случайный ключ и отправляется на почту пользователя в виде ссылки, которая активна в течении 10 минут;
- При нажатии на ссылку пользователь переходит на ваш сайт. Система сама авторизует пользователя по переданному в ссылке ключу.
Преимущества такого подхода:
- Нет необходимости в хранении и валидации паролей;
- Нет необходимости в системе восстановления паролей;
- Процесс регистрации очень прост и привлекателен.
Недостатки:
- Система безопасна до те пор, пока в безопасности почта пользователя. Если у кого-то есть доступ к почте пользователя, он может легко авторизоваться. Но это также относится и к другим системам регистрации (с функционалом восстановления пароля);
- Email передается открыто и может быть перехвачен (также относится ко всем системам регистрации, не использующим HTTPS);
- Если не уделить должного внимания к настройке и оформлению исходящих писем, есть шанс, что они попадут в спам.
Исходя из перечисленного, наша система регистрации очень удобна в использовании, но не полностью безопасна, поэтому использовать её лучше для простых вещей, вроде регистрации на форуме и сервисов, которые не используют важную конфиденциальную информацию.
Использование системы регистрации
Если вы просто хотите начать использовать эту систему, не просматривая урок, вам необходимо:
- Скачайте исходники по ссылке выше;
- В zip файле найдите файл tables.sql. Импортируйте его в свою базу с помощью phpMyAdmin. Также можно просто скопировать SQL код из файла и выполнить его;
- Откройте includes/main.php и введите данные для соединения с базой имя пользователя/пароль. В этом же файле надо указать email, который будет использован в качестве отправного. Некоторые почтовые системы блокируют письма без указания это адреса;
- Загрузите файлы на сервер;
- Добавьте этот код на каждую страницу, если хотите, что бы она была доступна только авторизованным пользователям;
Если хотите знать как работает система регистрации, продолжайте читать! 🙂
HTML
Как всегда, начнем с HTML разметки формы для регистрации. Следующий код расположен в файле index.php. Этот файл так же содержит PHP код, который обрабатывает входные данные и другие полезные функции системы. Об этом поговорим ниже.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Tutorial: Super Simple Registration System With PHP & 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.
Не плохо!
А видео урок есть по данной теме?