PHP и MySQL. Часть 4. Механизм сессий

PHP и MySQL. Часть 4. Механизм сессий

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

Идея управления сеансами заключается в обеспечении отслеживания пользователя в течение одного сеанса связи с web-сайтом. Если же это удастся осуществить, мы сможем легко поддерживать подключение пользователя и предоставление ему содержимого сайта в соответствии с его уровнем прав доступа или персональными настройками. Механизм сессий даст возможность отслеживать поведение пользователя. Кроме того, мы сможем реализовать покупательские тележки (shopping carts).
В более ранних версиях РНР управление сеансами осуществлялось средствами PHPLib — базовой библиотеки РНР, которая и сейчас является полезным набором инструментов. В четвертую версию РНР включены собственные функции управления сеансами. Фактически они подобны PHPLib, но PHPLib еще обеспечивает ряд дополнительных возможностей. В том случае, если собственные функции не вполне отвечают вашим требованиям, ничто не мешает рассмотреть использование в проекте PHPLib.
Для запуска сеанса в РНР используется уникальный идентификатор сеанса, представляющий собой зашифрованное случайное число. Этот идентификатор генерируется РНР и сохраняется на стороне клиента в течение всего времени жизни сеанса. Для хранения идентификатора используется либо URL, либо cookie-набор. Идентификатор сеанса играет роль ключа, обеспечивающего возможность регистрации некоторых специфических переменных в качестве так называемых переменных сеанса. Содержимое этих переменных сохраняется на сервере. Единственной информацией, видимой на стороне клиента, является идентификатор сеанса. Если во время определенного подключения к вашему сайту идентификатор сеанса является видимым либо в cookie-наборе, либо в URL, имеется возможность получить доступ к переменным этого сеанса, которые сохранены на сервере именно для данного сеанса. По умолчанию переменные сеанса хранятся в двумерных файлах на сервере, но при желании способ хранения можно изменить и использовать базу данных. Для этого, однако, потребуется написать собственную функцию.
В жизни же чаще всего приходится иметь дело с сайтами, на которых для хранения идентификатора сеанса используется URL. Если в URL присутствует строка данных, явно не имеющая ничего общего с упорядочиванием, это, скорее всего, свидетельствует об использовании одной из двух разновидностей управления сеансом. Другим вариантом сохранения состояния на протяжении некоторого количества транзакций при наличии чистого внешнего вида URL являются cookie-наборы.

Cookie-набор — это небольшой фрагмент информации, который сценарий сохраняет на клиентской машине. Чтобы установить cookie-набор на машине пользователя, ему отправляется HTTP-заголовок, содержащий данные следующего формата:
Set-Cookie: NAME=VALUE; [expires=DATE;] [path=PATH;] [domain=DOMAIN_NAME;] [secure]
Эта директива создаст набор с именем NAME и значением VALUE. Все остальные параметры являются необязательными и могут не задаваться. В expires задается дата истечения срока действия cookie-набора, после которой набор будет считаться неактуальным. В случае, если дата истечения "срока годности" не задана, то cookie-набор будет действовать, пока его кто-нибудь не удалит вручную — либо вы, либо сам пользователь. Два параметра path и domain применяются для определения одного или нескольких URL, к которым относится данный cookie-набор. Ключевое слово secure означает, что cookie-набор не может отправляться через простое HTTP-соединение.
Когда браузер соединяется с URL, он сначала ищет cookie-наборы, хранящиеся локально. Если какие-либо из них относятся к URL, с которым установлено соединение, они передаются обратно на сервер.
В РНР cookie-наборы можно установить вручную, используя функцию setcookie(), которая имеет следующий прототип:
int setcookie(string name [, string value [, int expire [, string path [, string domain [, int secure]]]]]).
Ее параметры в точности повторяют те, которые используются в описанном выше заголовке Set-Cookie. Если cookie-набор установлен как setcoo-kie("some_cookie","some_value"), то, когда пользователь обращается к следующей странице на вашем сайте (или перегружает текущую страницу), вы получаете доступ к переменной с именем $some_cookie, в которой содержится значение "some_value". Доступ к этой переменной можно получить также через $HTTP_COOKIE_VARS["some_cookie"].
Для удаления набора необходимо вызвать функцию setcookie() с тем же именем переменной, но без указания значения. Если набор устанавливался с другими параметрами (специфический URL, дата истечения), потребуется отправить те же параметры повторно, иначе cookie-набор не будет удален.

Для установки cookie-набора вручную можно также пользоваться функцией Header() и описанным выше синтаксисом представления набора. Однако при этом следует иметь в виду, что заголовки cookie-набора должны отправляться перед всеми другими заголовками, иначе заголовок набора работать не будет.
Но при использовании cookie-наборов возникают некоторые проблемы. Есть браузеры, которые не понимают эти наборы, и есть пользователи, которые запрещают принимать cookie-наборы. Это одна из причин, по которой в сеансах РНР используют двойной метод: cookie_набор/адрес URL. В сеансе РНР нет необходимости задавать cookie-наборы вручную. За вас это сделают функции сеанса.
Для того, чтобы просмотреть содержимое cookie-набора, установленное при управлении сеансом, можно воспользоваться функцией session_get_cookie_params(). Она возвращает ассоциативный массив, содержащий элементы lifetime, path и domain. Можно использовать также session_set_cookie_params($lifetime, $path, $domain); — этот оператор устанавливает параметры набора для сеанса.
В РНР cookie-наборы используются по умолчанию. Если есть возможность установить cookie-наборы, то для хранения идентификатора сеанса будет использован именно этот способ. Другой метод, который может применяться в РНР, заключается в добавлении идентификатора к адресу URL. Можно сделать так, чтобы идентификатор сеанса добавлялся в ссылку автоматически — тогда компилировать РНР необходимо с опцией — enable-trans-sid.
Можно поступить по-другому — встроить идентификатор сеанса в ссылку, чтобы обеспечить его передачу. Идентификатор будет запоминаться в константе SID. Для того, чтобы передать его вручную, его потребуется добавить в конец каждой ссылки аналогично параметру GET:
<A HREF="link.php?<? =SID?> ">

В общем же случае проще компилировать РНР с опцией enable-trans-sid, если только это возможно.
Кстати, константа SID может использоваться только при конфигурировании РНР с ena-ble-track-vars.
В общем, при использовании каждый сеанс проходит следующие жизненные этапы: запуск, регистрацию переменных сеанса, использование переменных (работа), отмену регистрации переменных и закрытие сеанса.
Конечно, не обязательно, чтобы все этапы присутствовали в одном сценарии — они могут быть разбросаны по нескольким файлам сценариев. Рассмотрим каждый из этапов последовательно на примерах.
Прежде чем воспользоваться функциональными возможностями сеанса, его следует запустить. Есть три способа сделать это. Первый, и самый простой, заключается в том, что сценарий начинается с вызова функции session_start(). Эта функция проверяет, существует ли идентификатор текущего сеанса. Если его нет, то он создается. Если идентификатор текущего сеанса уже существует, функция загружает зарегистрированные переменные сеанса, чтобы они стали доступными для использования.
Второй способ заключается в запуске сеанса при попытке зарегистрировать переменные сеанса.
Третий способ запустить сеанс — задать установки РНР, при которых сеанс будет запускаться автоматически, как только кто-то посетит ваш сайт. Для этого следует воспользоваться опцией session.auto_start в файле PHP.INI.
Возможность отслеживать переменные от одного сценария к другому появляется после регистрации этих переменных. Это делается путем вызова функции session_register(). Например, для регистрации переменной $user_name применяется следующий код:
$user_name = "Denver";
session_register("user_name");

Обратите внимание, что в качестве параметра функции передается строка с названием переменной. Эта строка не включает в себя символ $ — впрочем, она и не должна его включать.
Данный оператор регистрирует имя переменной и отслеживает ее значение. Отслеживание переменной будет осуществляться до завершения сеанса либо пока вручную не будет отменена ее регистрация. За один прием можно зарегистрировать более одной переменной, передав функции список имен, разделенных запятыми:
session_register("user_name","user_role","user_encoding");

Чтобы сделать переменную сеанса доступной для использования, сначала необходимо запустить сеанс, воспользовавшись одним из описанных выше способов. После этого появляется доступ к переменной. Если у нас в РНР включена опция register_globals, доступ к переменной можно получить просто через сокращенную форму ее имени, например, $user_name. Если же упомянутая опция не включена, получить доступ к переменной можно через ассоциативный массив $HTTP_SESSION_ VARS, например, вот так: $HTTP_SESSION_VARS["user_name"]. Переменные сеанса не могут быть перезаписаны данными GET и POST. Это накладывает определенные ограничения на кодирование, но хорошо с точки зрения обеспечения безопасности. С другой стороны, от программиста в этом случае требуется тщательность при проверке, установлены ли уже переменные сеанса. Выполняется это функциями, к примеру, isset() или empty(). Кроме того, не стоит забывать, что переменные могут быть установлены через методы GET или POST. Тогда, в довершение всех проверок, еще проверяется, является ли переменная зарегистрированной переменной сеанса функцией session_is_registered(). Вызов функции выполняется следующим образом:
$result = session_is_registered("user_name");

Функция проверит, является ли $user_name зарегистрированной переменной сеанса, и вернет результат false или true. Можно поступить по-другому: проверить массив $HTTP_SESSION_VARS на предмет наличия в нем искомой переменной.
После того, как некоторая работа проведена, регистрацию переменной можно отменить, воспользовавшись функцией session_unregister():
session_unregister("user_name");

Подобно функции регистрации, эта функция требует указания имени переменной, регистрацию которой необходимо отменить, в виде строки, не включающей символ $. В противоположность функции регистрации переменных сеанса данная функция за один раз может отменить регистрацию только одной переменной. Если переменных "много", их можно отметить все сразу функцией session_unset().
По завершении сеанса сначала отмените регистрацию всех переменных, а затем вызовите session_destroy() для обнуления идентификатора сеанса.
Так как изложенный выше материал довольно абстрактен, приведем пример, обеспечивающий обработку трех страниц. На первой странице мы запустим сеанс и зарегистрируем переменную $session_user. Код из файла page1.php, позволяющий это сделать, приведен ниже:

<?
session_start();
session_register("session_user");
$session_user = "Some Session User";
echo "значение переменной \ $session_user = $session_user <br> ";
?>
<A HREF="page2.php"> Перейти на следующую</A>

В этом сеансе регистрируется переменная сеанса и устанавливается ее значение. Заметим, что значение переменной было изменено уже после ее регистрации. Можно, впрочем, сделать и наоборот. То значение переменной, которое будет доступно на последующих переменных, называется конечным значением. В конце сценария переменная сериализуется (преобразуется в последовательную форму) или замораживается до своей перезагрузки через вызов session_start().
Следующий сценарий мы также начинаем с вызова session_start(). Сценарий page2.php также приведен ниже:

<?
session_start();
echo "значение переменной \ $session_user = $session_user <br> ";
session_unregister("session_ user");
?>
<A HREF="page3.php"> Перейти на следующую</A>

После вызова session_start() переменная $session_user станет доступной, а ее значением будет то, которое сохранено в предыдущем сеансе. Сделав с переменной все необходимые действия, мы вызываем session_ unregeister() для отмены ее регистрации. При этом сеанс еще существует, но переменная уже не является зарегистрированной.
И наконец переходим к сценарию page3.php — последнему в рассматриваемом примере. Вот его код:

<?
session_start();
echo "значение переменной \ $session_user = $session_user <br> ";
session_destroy();
?>

В результате выполнения вы увидите в окне обозревателя, что доступа к значению $session_user больше нет. В завершение вызывается session_ destroy() для разрушения идентификатора сеанса. Однако представленный пример не имеет никакой ценности, так как не выполняет ничего полезного. Лучше рассмотреть более важный пример использования контроля сеансами. Наиболее часто, пожалуй, управление сеансом применяется в целях отслеживания пользователей после того, как они были аутентифицированы через механизм входной регистрации. В предлагаемом примере можно видеть, как эти функциональные возможности обеспечиваются за счет сочетания аутентификации при помощи базы данных MySQL и использования механизма управления сеансом.
В этом примере воспользуемся базой данных auth, которую создаст следующий сценарий SQL:

create database auth;

use auth;
create table auth (
name varchar(10) not null,
pass varchar(30) not null,
primary key (name)
);

insert into auth values
('user','pass');

insert into auth values
('testuser', password('test123') );

grant select, insert, update, delete
on auth.*
to denver@localhost
identified by 'denv001';

В приведенном выше сценарии создания БД используется функция MySQL password() — она получает как аргумент строку и применяет к строке необращаемый алгоритм хэширования. Однонаправленный алгоритм хэширования обеспечит дополнительную защиту при незначительных затратах.
Пример будет включать в себя три простых сценария. Первый, authmain.php, обеспечивает форму для входной регистрации и аутентификации пользователей web-сайта. Второй, members_only.php, предоставляет информацию только для тех пользователей, которые успешно прошли входную регистрацию. Третий, logout.php, реализует выход пользователя из системы.
Чтобы понять, как все это работает, рассмотрим следующую схему. Исходная страница authmain.php предоставляет пользователю возможность войти в систему. В случае, если он предпримет попытку получить доступ к секции 'members', не пройдя входной регистрации, будет выдано предупреждающее сообщение. Если же пользователь сначала прошел входную регистрацию (с именем пользователя 'testuser' и паролем 'test123'), а после этого предпринял попытку входа на страницу 'members', он увидит нормальное информационное наполнение страницы.
Так как большая и основная часть кода расположена именно в сценарии authmain.php, приведем его листинг первым:

<?
session_start();
if ($userid && $password)
{
// если пользователь пытается зарегистрироваться
$db = mysql_connect("localhost","denver","denv001");
mysql_select_db("auth",$db);
$query = "select * from auth "
. "where name='$userid' "
. "and pass=password('$password')";
$result = mysql_query($query, $db);
if (mysql_num_rows($result) > 0 )
{
// если пользователь найден в базе данных, зарегистрировать его идентификатор
$valid_user = $userid;
session_register("valid_user");
}}
?>
<html>
<body> <h1> Home Page</h1>
<?
if (session_is_registered("va-lid_user"))
{
echo "Вы вошли под именем: $valid_user <br> ";
echo "<A HREF=\"logout.php\ "> Выход</A> <br> ";
}
else
{
if (isset($userid))
{
// пользователь пытался зарегистрироваться, но возникла ошибка
echo "Ошибка. Не могу Вас идентифицировать.";
}
else
{
// если пользователь либо не пытался зарегистрироваться, либо покинул сайт
echo "Вы не вошли на сайт через страницу регистрации";
}
// форма для аутентификации
echo "<form method=post action=\"authmain.php\"> ";
echo "<table> ";
echo "<tr> <td> Userid: </td> ";
echo "<td> <input type= text name=userid> </td> </tr> ";
echo "<tr> <td> Password: </td> ";
echo "<td> <input type = password name = password> </td> </tr> ";
echo "<tr> <td colspan=2 align=center> ";
echo "<input type=submit value=\"Log in\"> </td> </tr> ";
echo "</table> </form> ";
}
?>
<br>
<a href="members_only.php"> Members section</a>
</body>
</html>

Сценарий отличается сложной, но сложной в разумных пределах, логикой, и иначе нельзя, ведь он осуществляет представление формы для входной регистрации и ее обработку. Работа этого сценария сосредоточена вокруг переменной сеанса $valid_user. Основная идея заключается в следующем: если кто-то успешно прошел процедуру регистрации, мы регистрируем переменную сеанса с именем $valid_user, которая содержит идентификатор пользователя.
Так что же первым делом выполняется в сценарии? Изначально вызывается session_start(). Эта функция загружает переменную сеанса $valid_user, если последняя была зарегистрирована.
При первом проходе по сценарию ни один из условных операторов if не сработает, и неудачливому пользователю к концу сценария останется лишь внимательно прочесть сообщение о том, что он не прошел процедуру входной регистрации. После этого мы предоставляем ему форму для входа:

echo "<form method=post action=\"authmain.php\"> ";
echo "<table> ";
echo "<tr> <td> Userid:</td> ";
echo "<td> <input type=text na-me=userid> </td> </tr> ";
echo "<tr> <td> Password:</td> ";
echo "<td> <input type = password name = password> </td> </tr> ";
echo "<tr> <td colspan=2 align= center> ";
echo "<input type=submit value =\"Log in\"> </td> </tr> ";
echo "</table> </form> ";

Когда пользователь нажмет кнопку отправки "Submit", сценарий вызывается заново, и вновь все повторяется сначала. На этот раз в нашем распоряжении будут имя пользователя и пароль, позволяющие его аутентифицировать (они хранятся в переменных $userid и $password). Если эти переменные установлены, переходим к блоку аутентификации:
if ($userid && $password)
{
// если пользователь пытается зарегистрироваться
$db = mysql_connect("localhost","denver","denv001");
mysql_select_db("auth",$db);
$query = "select * from auth "
. "where name='$userid' "
. "and pass=password('$password')";
$result = mysql_query($query, $db);

И вот мы подключаемся к базе данных MySQL и проверяем имя пользователя и пароль. Если в базе данных существует соответствие этой паре, мы регистрируем переменную $valid_user, которая содержит идентификатор для конкретного пользователя. Таким образом, мы знаем, кто вошел в систему, и можем его отследить.

if (mysql_num_rows($result) > 0 )
{
// если пользователь найден в базе данных, зарегистрировать его идентификатор
$valid_user = $userid;
session_register("valid_user");
}}

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

if (session_is_registered ("va-lid_user"))
{
echo "Вы вошли под именем: $va-lid_user <br> ";
echo "<A HREF=\"logout.php\"> Выход</A> <br> ";
}

Если же при попытке произвести входную регистрацию пользователя мы по какой-то причине не можем это сделать, то у нас имеется идентификатор пользователя, но нет переменной $valid_user, и ничего не остается, кроме как выдать сообщение об ошибке:

if (isset($userid))
{
// пользователь пытался зарегистрироваться, но возникла ошибка
echo "Ошибка. Не могу Вас идентифицировать.";
}
С основным сценарием, похоже, все становится понятным. Теперь посмотрим на страницу members. Код сценария приведен ниже:
<?
session_start();
echo "<h1> Только для зарегистрированных</h1> ";
// проверим переменную сеанса
if (session_is_registered ("valid_user"))
{
echo "<p> Вы вошли под именем: $valid_user.</p> ";
echo "<p> В этом месте идет информация, доступная только зарегистрированным пользователям</p> ";
}
else
{
echo "<p> Вы не зарегистрированы в системе.</p> ";
echo "<p> Только зарегистрированные могут просматривать эту страницу.</p> ";
}
echo "<a href=\"authmain.php \"> Назад на главную</a> ";
?>

Все, что делает этот код, — запуск сеанса и проверка, содержит ли текущий сеанс зарегистрированного пользователя, с использованием функции session_registered_user(). Если пользователь прошел процедуру входа, мы отображаем ему содержимое сайта, в противном случае сообщаем, что у него нет для этого соответствующих полномочий.
В завершение рассмотрим сценарий logout.php, который завершает регистрацию пользователя в системе:

<?
session_start();
$old_user = $valid_user;
// проверяем, был ли пользователь в системе
$result = session_unregister ("valid_user");
session_destroy();
?>
<html>
<body>
<h1> Log out</h1>
<?
if (!empty($old_user))
{
if ($result)
{
// если входил в систему и не вышел из нее
echo "Вы покинули систему.<br> ";
}
else
{
// если входил в систему и не может ее покинуть
echo "Вы не можете покинуть систему .<br> ";
}
}
else
{
// если не входил, но как-то попал на эту страницу
echo "Вы не входили, поэтому вас и не выпускали.<br> ";
}
?>
<a href="authmain.php"> Назад на главную</a>
</body> </html>

Приведенный код очень прост, но позволяет осуществить несколько интересных моментов. Мы запускаем сеанс, запоминаем старое имя пользователя, отменяем регистрацию переменной $valid_user и завершаем сеанс. После этого мы выдаем пользователю одно из следующих сообщений: он вышел из системы, не может выйти из системы или не может выйти из системы, поскольку даже не зарегистрировался в ней.
В следующей статье приведенный выше простой сценарий послужит основой для более сложных примеров.

Денис "Denver" Мигачев, dtm@tut.by


Компьютерная газета. Статья была опубликована в номере 29 за 2003 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета