Авторизация посетителей на PHP



Рассмотрим систему авторизации пользователей (регистрация, login/logout, доступ к закрытым страничкам, поддтвеждение по e-mail).

Нам понадобятся: вебсервер, PHP, MySQL. Предполагается, что вы с этим всем уже знакомы на определенном уровне.

Для начала создадим в базу данных, допустим, "authorize" и таблицу "users" в ней. Работать с БД MySQL очень удобно с помощью инструмента PhpMyAdmin, который можно скачать здесь.

Итак, таблица "users":

ПолеТипПо умолчаниюДополнительно
idmediumint(9)auto_increment
loginvarchar(32)index unique
passvarchar(32)
emailvarchar(64)index unique
statustinyint(2)0
timestampint(10)

Теперь сделаем страничку регистрации и скрипт, заносящий данные о юзере в базу. Нам понадобится html-файл с такой формой (естественно, если хотите иметь больше информации о юзере, можно добавить соответствующие поля, изменить скрипт и таблицу в БД):

<form action="registration.php" method="post">
<table>
<tr>
   <td>Логин* :</td>
   <td><input type="text" name="rLogin" value="" size="25" maxlength="30" /></td>
</tr>
<tr>
   <td>Пароль* :</td>
   <td><input type="password" name="rPass" value="" size="25" maxlength="30" /></td>
</tr>
<tr>
   <td>Повторите пароль* :</td>
   <td><input type="password" name="rPass2" value="" size="25" maxlength="30" /></td>
</tr>
<tr>
   <td>E-mail* :</td>
   <td><input type="text" name="rEmail" value="" size="25" maxlength="30" /></td>
</tr>
<tr>
   <td></td>
   <td><input type="reset" name="reset" value="Очистить" />
       <input type="submit" name="ok" value="Готово" /></td>
</tr>
</table>
</form>

На этом этапе стоит упомянуть о том, что полезно в таких случаях кроме проверок правильности введенных данных на сервере, делать такую проверку средствами Javascript. Например, если юзер ввел повтор пароля неверно, подсвечивать красным соответвующую строку. Это не сложно, поэтому расскажем об этом в следующий раз.

Теперь создадим скрипт "registration.php", который и будет обрабатывать форму регистрации:

<?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
   $rLogin = trim($_POST['rLogin']);
   $rPass  = trim($_POST['rPass']);
   $rPass2 = trim($_POST['rPass2']);
   $rEmail = trim($_POST['rEmail']);
   if ($rLogin == '') {
      die("Поле 'Логин' не заполнено<br />\n");
   // Логин может состоять из букв, цифр и подчеркивания
   }elseif (!preg_match("/^\w{3,}$/", $rLogin)) {
      die("В поле 'Логин' введены недопустимые символы<br />\n");
   }
   if ($rEmail == '') {
      die("Поле 'E-mail' не заполнено<br />\n");
   // Проверяем e-mail на корректность
   }elseif (!preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}$/", $rEmail)) {
      die("Указанный 'E-mail' имеет недопустимый формат<br />\n");
   }
   if ($rPass == '' || $rPass2 == '') {
      die("Поле 'Пароль' не заполнено<br />\n");
   }elseif($rPass !== $rPass2) {
      die("Поля 'Пароль' и 'Повтор пароля' не совпадают<br />\n");
   // Пароль может состоять из букв, цифр и подчеркивания
   }elseif(!preg_match("/^\w{3,}$/", $rPass)) {
      die("В поле 'Пароль' введены недопустимые символы<br />\n");
   }
   // В базе данных у нас будет храниться md5-хеш пароля
   $mdPassword = md5($rPass);
   // А также временная метка (зачем - позже)
   $time = time();
   // Устанавливаем соединение с бд(не забудьте подставить ваши значения сервер-логин-пароль)
   $link = mysql_connect('localhost', $dbuser, $dbpass);
   if (!$link) {
      die("Не могу соединиться с базой данных");
   }else {
      // Выбираем базу данных
      mysql_select_db('authorize', $link);
      // Записываем в базу (не используем addslashes - экранировать нечего)
      mysql_query("INSERT INTO users (login, pass, email, timestamp)
                   VALUES ('$rLogin','$mdPassword','$rEmail',$time)",$link);
      if (mysql_error($link) != "") {
         die("Пользователь с таким логином уже существует, выберите другой<br />\n");
      }
      echo "Юзер добавлен<br />\n";
      mysql_close($link);
   }
}
?>

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

Теперь мы должны выслать юзеру на указанный e-mail письмо со ссылкой для подтверждения регистрации. Для этого вместо строчки echo "Юзер добавлен<br />\n"; вставим такой кусок кода:

// Получаем Id, под которым юзер добавился в базу
$id = mysql_result(mysql_query("SELECT LAST_INSERT_ID()", $link), 0);
// Составляем "keystring" для активации
$key = md5(substr($rEmail, 0 ,2).$id.substr($rLogin, 0 ,2));
$date = date("d.m.Y",$time);
// Компонуем письмо
$title = 'Потвеждение регистрации на сайте Somwhere.net';
$headers  = "Content-type: text/plain; charset=windows-1251\r\n";
$headers .= "From: Администрация Somwhere.net \r\n";
$subject = '=?koi8-r?B?'.base64_encode(convert_cyr_string($title, "w","k")).'?=';
$letter = <<< LTR
   Здравствуйте!
   бла-бла
   
   Ваши регистрационные данные:
      логин: $rLogin
      пароль: $rPass
   
   Для активации аккаунта вам следует пройти по ссылке:
   http://somewhere.net/activation.php?login=$rLogin&key=$key
   
   Данная ссылка будет доступна в течении 5 дней.
   
   $date
LTR;
// Отправляем письмо
if (!mail($rEmail, $subject, $letter, $headers)) {
   // Если письмо не отправилось, удаляем юзера из базы
   mysql_query("DELETE FROM users WHERE login='".$login."' LIMIT 1", $link);
   echo 'Произошла ошибка при отправке письма. Попробуйте зарегистрироваться еще раз.';
}else {
   echo 'Вы успешно зарегистрировались в системе. На указанный вами
   e-mail было отправлено письмо со ссылкой для активации аккаунта.
   У вас 5 дней!';
}

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

Кстати о письме: правила составления ключевой строки "keystring" никем не писаны и каждый может придумать что-нибудь свое. Например, при каждой регистрации можно генерировать случайное слово и, записав в базу, на его основе генерить "keystring".

Теперь у нас в таблице "users" должна быть строка вида:

1   login   76d80224611fc919a5d54f0ff9fba446   eqw@asd.ru   0   1197730343

Теперь создадим скрипт, активирующий пользователей. Система его будет такова:

  • • Пользователь приходит по ссылке с GET-данными "login" и "key"
  • • Берем из базы id, login, e-mail, status и timestamp юзера (если юзер есть). Проверяем статус (активирован уже или нет). Проверяем разницу текущей временной метки и метки из базы, если больше 5*24*60*60 - до свидания
  • • Составляем новую "keystring" по тем же правилам, что и старую. Сравниваем. Не равны - до свидания
  • • Если ключевые строки равны - апдейтим статус до 1.

А код таков:

<?
if (isset($_GET['login']) && isset($_GET['key'])) {
   $login = $_GET['login'];
   $key = $_GET['key'];
   // Делаем проверку login на нехорошие символы
   if (!preg_match("/^\w{3,}$/", $login)) {
      die('Неправильная ссылка!');
   }
   $time = time();
   $link = mysql_connect('localhost',$dbuser,$dbpass);
   if (!$link) {
      die('Не удалось соединиться с БД');
   }else{
      mysql_select_db('authorize', $link);
      $res = mysql_query("SELECT id, email, status, timestamp
      FROM users WHERE login='$login' LIMIT 1", $link);
      // Есть ли пользователь с таким логином?
      if (mysql_num_rows($res) != 1) {
         mysql_close($link);
         die('Такого пользователя нет!');
      }
      $user = mysql_fetch_row($res);
      // Может он уже активен?
      if ($user[2] == 1) {
         mysql_close($link);
         die('Данный логин уже подтвержден!');
      }
      // Успел ли юзер активировать логин? (если нет - удаляем из базы)
      if ($user[3] - $time > 5*24*60*60) {
         mysql_query("DELETE FROM users WHERE login='$login' LIMIT 1", $link);
         mysql_close($link);
         die('Срок активации истёк! Регистрируйтесь заново.');
      }
      $key1 = md5(substr($user[1], 0 ,2).$user[0].substr($login, 0 ,2));
      // Поверяем "keystring"
      if ($key1 != $key) {
         mysql_close($link);
         die('Неправильная контрольная сумма!');
      }
      // Если все проверки пройдены - активируем логин!
      mysql_query("UPDATE users SET status = 1 WHERE login='$login'", $link);
      mysql_close($link);
   }
}
?>

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

<form method="post" action="login.php">
   <table>
   <tr>
      <td>Логин</td>
      <td><input type="text" name="login"></td>
   </tr>
   <tr>
      <td>Пароль</td>
      <td><input type="password" name="password"></td>
   </tr>
   <tr>
      <td colspan="2"><input type="submit" value="Войти"></td>
   </tr>
   </table>
</form>

Вставляйте эту форму в любое место страницы, как того требует дизайн. Обработчиком формы будет у нас файл "login.php" такого вида:

<?
if (isset($_POST['login'])) {
   $passwordHash = md5($_POST['password']);
   $login = $_POST['login'];
   // Проверка логина на плохие смиволы
   if (!preg_match("/^\w{3,}$/", $login)) {
      die('Неправильный логин!');
   }
   $link = mysql_connect('localhost',$dbuser,$dbpass);
   if (!$link) {
      die('Не удалось соединиться с БД');
   }else{
      mysql_select_db('authorize', $link);
      $res = mysql_query("SELECT status FROM users WHERE login='$login'", $link);
      // Есть ли пользователь с таким логином?
      if (mysql_num_rows($res) < 1) {
         mysql_close($link);
         die('Такого пользователя нет!');
      }
      // Какой статус у пользователя?
      if (mysql_result($res, 0) != 1) {
         mysql_close($link);
         die('Логин не активирован!');
      }
      // Стартуем сессию и записываем логин в суперглобальный массив $_SESSION
      session_start();
      $_SESSION['user'] = $login;
      mysql_close($link);
      // Если определена страница с которой мы пришли,
      // на нее и переадресуем, либо на главную
      if (isset($_SERVER['HTTP_REFERER'])) {
         header ("location: ".$_SERVER['HTTP_REFERER']);
      }else {
         header ("location: index.php");
      }
   }
}
?>

Напишем сразу скрипт "logout.php", работающий как "выход из системы":

<?
   session_start();
   if (isset($_SESSION['user'])) {
   // удаляем элемент "user"
      unset($_SESSION['user']);
   }
   if (isset($_SERVER['HTTP_REFERER'])) {
      header ("location: ".$_SERVER['HTTP_REFERER']);
   }else {
      header ("location: index.php");
   }
?>

Ну вот, юзер авторизован. Осталась малость:

  • • В начале каждого документа стартовать сессию функцией session_start()
  • • Проводить проверку на присутствие в массиве $_SESSION элемента user. На основе нее выдавать "секретный" документ или общий.
  • • Вместо формы для авторизации распечатывать приветствие и ссылку на "logout.php"

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

<?
session_start();
if (isset($_SESSION['user'])) {
   $auth = "Привет ".$_SESSION['user']."!<br />\n";
   $auth .= "<a href='logout.php'>Выйти</a>";
   $docum = "Эта информация только для зарегистрированных";
}else {
   $auth = <<< AUTH
   <form method="post" action="login.php">
      <table>
      <tr>
         <td>Логин</td>
         <td><input type="text" name="login"></td>
      </tr>
      <tr>
         <td>Пароль</td>
         <td><input type="password" name="password"></td>
      </tr>
      <tr>
         <td colspan="2"><input type="submit" value="Войти"></td>
      </tr>
      </table>
   </form>
AUTH;
   $docum = "Эта общая информация";
}
?>
<html>
<head>
</head>
<body>
<? echo $auth; ?>
<hr />
<? echo $docum; ?>
</body>
</html>

Ну вот вроде и все, о чем я хотел поведать. Надеюсь этот бред кому-нибудь будет полезен :)

UPD: продолжение тут

Комментарии

мой ICQ 408014532.

Добавь меня в свой контакт

твой spam kontrol не дает возможность пообщаться по ICQ

Уважаемые Morlok, Almen, 3hrek!
У меня также проблемы с отладкой данного скрипта и с исправлением некоторых багов в нем. Был бы очень благодарен если вы бы кинули исходники на мыло andrew.shvedov@gmail.com

Доброго времени суток.
Такая проблема:
Первый пользователь регистрируется нормально, а последующие не даёт зарегить, говорит "Пользователь с таким логином уже существует, выберите другой" почему?
На сколько я понял там же нет ограничения по времени для предотвращения массовой регистрации

А как подключить к этой штуки куки?

1. при логине пихайте в куки идентификатор сессии или какой-нить другой токен (сохраняя его в базе).
2. при заходе на сайт анонимуса проверяйте идентификатор(токен), и логиньте соответствующего юзера.

это вобщем. в подробнее напишу потом как-нибудь, в отдельной статейке.
удачи!

Очень понравилась статься. Особенно понравилась проверка, if ($_SERVER['REQUEST_METHOD'] == 'POST') , на самом деле важная вещь, про которую забыют.

Со своей стороны, кидаю чуть переработанную версию того чего увидел, просто в целях обмена опытом, Уже в таком маленьком скрипте получается усложненная логика, и куча вложенных if, представте что получится если в форме будет 10 полей?

Предлагаю слегка модифицировать и писать на php5.

class DbException extends Exception{};
try
{
	if ($_SERVER['REQUEST_METHOD'] == 'POST') {
		foreach ( array('rLogin' => 'Логин' , 'rPass' => 'Пароль' , 'rPass2' => 'Повторный пароль', 'rEmail' => 'E-mail' ) as $val => $fieldName) 
			if ( isset($_POST[$val] ) ) 
				$$val = trim($_POST[$val]); //create rLogin, rPass, rPass2, rEmail variables 
        	        else 
				throw new Exceptin("Поле $fieldName не заполнено \n")

   if (!preg_match("/^\w{3,}$/", $rLogin)) 
      throw new Exception("В поле 'Логин' введены недопустимые символы\n");

   if (!preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}$/", $rEmail)) 
      throw new Exception("В поле 'Логин' введены недопустимые символы\n");

   if($rPass !== $rPass2) 
      throw new Exception("Поля 'Пароль' и 'Повтор пароля' не совпадают\n");

   if(!preg_match("/^\w{3,}$/", $rPass)) 
      throw new Exception("В поле 'Пароль' введены недопустимые символы\n");
   
   
   $mdPassword = md5($rPass);
   // А также временная метка (зачем - позже)
   $time = time();
   // Устанавливаем соединение с бд(не забудьте подставить ваши значения сервер-логин-пароль)
   $link = mysql_connect('localhost', $dbuser, $dbpass);

   if (!$link)
      throw new DbException("Не могу соединиться с базой данных");

      // Выбираем базу данных
   mysql_select_db('authorize', $link);
      // Записываем в базу (не используем addslashes - экранировать нечего)
   mysql_query("INSERT INTO users (login, pass, email, timestamp)
                   VALUES ('$rLogin','$mdPassword','$rEmail',$time)",$link);// поле timestamp можно сделать чтобы по умолчанию подставлялось базой, такие вещи лучше перекладывать на базу. 
   if (mysql_error($link) != "")
       DbException("Пользователь с таким логином уже существует, выберите другой\n");
      
    echo "Юзер добавлен\n";
    mysql_close($link);
} 
catch ( DbException $e)
{
    die("DB ERROR: " . $e->getMEssage()); 
}
catch ( Exception $e) 
{
   die( "Form validation error: ". $e->getMessage()); 
}

я бы Добавил бы строчку , непомешает!!! $mdPassword = md5(md5($rPass));

или туго доганяю но не вижу проверки пароля в "login.php"

Уважаемые Morlok, Almen, 3hrek!
Не могли бы вы кинуть исходники скрипта на e-mail sanchos14931@rambler.ru
Заранее благодарен

Весь код и скрипты представлены в статье во вполне понятном виде :)

Спасибо за предоставленный скрип и объяснения.
Но все-таки не помешало бы прислушаться к людям и добавить в скрипт проверку не только логина, но и пароля. А то не совсем понятно, зачем нужно вот это действие:
$passwordHash = md5($_POST['password']);
если потом переменная $passwordHash ни где не используется.

P.S. А может это сделано специально? Такая себе проверка на внимательность. )))

almen
по ходу ошибка в синтаксисе , вместо header ("location: ".$_SERVER['HTTP_REFERER']);
попробуй header ("location: ".$_SERVER['HTTP_REFERER'])."";}

Скрипт полностью работоспособен, только одну дырень заткноте
в файле login.php вместо строки
$res = mysql_query("SELECT status FROM users WHERE login='$login'", $link);
поставьте
$res = mysql_query("SELECT status FROM users WHERE login='$login' AND pass='$passwordHash' ", $link);

Дырень огромная : в login.php
вместо
$res = mysql_query("SELECT status FROM users WHERE login='$login'", $link);
поставьте
$res = mysql_query("SELECT status FROM users WHERE login='$login' AND pass='$passwordHash' ", $link);
А вцелом - скрипт - СУПЕР. Автору СПС!!!

Проблема в кодировке при приходе письма с кодом активации на почту.
Решил проблему с помощью:
$subject = mb_encode_mimeheader($subject,"UTF-8", "B", "\n");

Но теперь заголовок состоит из кода! того что стоит под *** в предыдущей строке:
$subject = base64_encode(convert_cyr_string('***', "w","k"));

Спасибо автору, отличный исходник.

а у меня при авторизации первого пользователя пишет
Пользователь с таким логином уже существует, выберите другой
хотя MySQL вернула пустой результат (т.е. ноль строк). ( запрос занял 0.0002 сек. ) (SELECT * FROM users)

разобрался :)

а серьезно где проверка пароля ??? чтот я не догоняю

да уж. не первая ошибка.....

кстати. не проще ли вместо
die('описание ошибки!');
ставить что-то на подобие:
$error = $error."

  • Описание ошибки!\n";
    потом проверять:
    if (empty($error))
    {
    Положительное действие (например добавить запись)
    }

    if (!empty($error))
    {
    echo "";
    echo "Во время активации пользователя произошли следующие ошибки:\n";
    echo "

      \n";
      echo "$error";
      echo "
    \n";
    echo "";
    }

    при этом в начале указать $error = "";

    тогда ошибки можно выводить списком в любом месте. а главное ничего не прирывается.

  • $res = mysql_query("SELECT status FROM users WHERE login='$login'", $link);
    // Есть ли пользователь с таким логином?
    лишняя проверка.
    автору стоит научится обрабатывать sql ошибки.

    не обратил внимания указали ли уже...вот еще ошибка:
    mysql_query("DELETE FROM users WHERE login='".$login."' LIMIT 1", $link);
    на
    mysql_query("DELETE FROM users WHERE login='".$rLogin."' LIMIT 1", $link);

    помогите пожалуйста! у меня при первом же логине говорит, что существует такой логин

    У меня тож при первой регистрации говорит что сущаствует такой логин выберитt другой, плизз если знаете в чём дело скажите на мыло!!! kostya.kamshilov@gmail.com

    добрго времени суток. извините конечно за тупой вопрос, но мне кажется Вы тут не указали самое интересное: как закрывать странички, которые должны быть доступны только зарегиным пользователям.
    логика скрипта сейчас :
    ввели в форму данные, проверили их по if на всякие штучки.. вытащили данные из БД, тоже их проверили, если все ок редиректим на скрытый контент НО что мешает узеру зайти на скрытый контент по прямой ссылке?

    Спасибо чувак

    Отправить комментарий

    Содержание этого поля является приватным и не предназначено к показу.
    • Разрешаю теги: <a> <em> <strong> <pre> <ul> <ol> <li>
    • Строки и параграфы переносятся автоматически.
    • Адреса страниц и электронной почты автоматически преобразуются в ссылки.

    Подробнее о форматировании

    Image CAPTCHA
    Регистр символов учитывается.