Сессии в PHP

В разнообразных конференциях посвященных программированию меня в первую очередь всегда интересуют такие разделы как "Web-программирование" и "Скрипты". По большей части вопросы о PHP в таких форумах довольно простые требующие лишь общего понимания PHP тем не менее самый часто задаваемый вопрос по моим наблюдениям это: "Что такое сессии в PHP и с чем/как их можно кушать?". Хотелось бы разъяснить этот вопрос раз и навсегда.

С самого начала PHP все приняли на ура но как только на этом языке стали создавать достаточно крупные проекты разработчики столкнулись с новой проблемой - в PHP отсутствовало понятие глобальных переменных! То есть выполнялся некий скрипт посылал сгенерированную страницу клиенту и все ресурсы используемые этим скриптом уничтожались. Попробую проиллюстрировать: предположим есть две страницы одного сайта index.php и dothings.php. Исходники к этим страницам выглядят так:

- index.php -

<?php

$a = "Меня задали на index.php";

?>

<html><body>

<?php

echo $a;

?>

</body></html>

- dothings.php -

<html><body>

<?php

echo $a;

?>

</body></html>

Если выполнить эти два скрипта то на первой странице мы увидим надпись "Меня задали на index.php" а вторая страница будет пустой.

Разработчики web-сайтов недолго думая стали использовать cookie для хранения глобальных переменных на стороне клиента. Процесс выглядел примерно так: пользователь приходит на главную страницу сайта делает какие-то действия и вся информация связанная с этим пользователем которая может потребоваться на других страницах сайта будет храниться у него в браузере в виде cookie. Этот метод меет довольно серьезные минусы из-за которых от PHP в своё время отвернулось немало разработчиков. Например нам нужно авторизовать пользователя чтобы разрешить ему доступ к закрытым (или принадлежащим только ему) разделам сайта. Придёться <кидать> пользователю cookie который будет служит его последующим идентификатором на сайте. Такой подход становится очень громоздким и не удобным как только сайт начинает собирать всё больше и больше сведений о поведении пользователя ведь всю информацию посылаемую пользователю желательно кодировать чтобы её нельзя было подделать. Ещё совсем недавно подделкой cookie можно было <повалить> не один чат а порой и пробраться в чужую почту. К тому же есть ещё на свете странные люди у которых браузер cookie не поддерживает.

При использовании сессий вся информация хранится не на стороне клиента а на стороне сервера и потому лучше защищена от манипуляций злоумышленников. Да и работать с сессиями куда проще и удобнее так как все данные автоматически проходят через алгоритмы криптографии модуля PHP. В броузере клиента лишь хранится уникальный идентификатор номера сессии либо в форме cookie либо в виде переменной в адресной строке броузера какой из двух способов использовать для передачи идентификатора сессии между страницами интерпретатор PHPвыбирает сам. Это на 100 безопасно так как идентификатор сессии уникален и подделать его практически невозможно (об этом чуть далее в разделе о безопасности сессий).

Я не буду вдаваться в технологические вопросы устройства механизма работы сессий а только опишу как правильно работать с сессиями в PHP.

Как работать с сессиями?

Если вы будете тестировать примеры из статьи (или ваши скрипты) на каком-либо коммерческом хостинге проблем с работой с сессиями быть не должно. Если же вы сами настраивали ваш сервер (будь то реальный сервер или эмулятор) могут появляться ошибки примерно такого содержания:

"Warning: open(/var/state/php/sess_6f71d1dbb52fa88481e752af7f384db0 O_RDWR) failed: No such file or directory (2)".

Это значит всего лишь что у вас неправильно настроен PHP. Решить эту проблему можно прописав правильный путь (на существующую директорию) для сохранения сессий в файле php.ini и перезапустить сервер.

Любой скрипт который будет использовать переменные (данные) из сессий должен содержать следующую строчку:

session_start();

Эта команда говорит серверу что данная страница нуждается во всех переменных которые связаны с данным пользователем (браузером). Сервер берёт эти перемнные (из файла либо из БД) и делает их доступными. Очень важно открыть сессию до того как какие-либо данные будут посылаться пользователю; на практике это значит что функцию session_start() желательно вызывать в самом начале страницы например так:

<?php

session_start();

?>

<html>

<head>

</head>

...

После начала сессии можно задавать глобальные переменные. Это элементарно: вызываем функцию session_register('var_name'); и переменная $var_name становится доступной на всех страницах использующих сессию. Для примера поковыряем программку приведенную в начале статьи:

- index.php -

<?php

// открываем сессию

session_start();

// задаём значение переменной

$a = "Меня задали на index.php";

// регистрируем переменную с открытой сессией

// важно: названия переменных передаются функции session_register()

// беззнака $

session_register("a");

?>

<html>

<body>

Всё ОК. Сессию загрузили!

Пройдём посмотрим что <a href="dothings.php>там:</a>

</body>

</html>

- dothings.php -

<?php

// открываем сессию

session_start();

?>

<html>

<body>

<?php

echo $a;

?>

</body>

</html>

При запуске этих файлов (в логической последовательности конечно) первый скрипт (index.php) выдаст следующий результат:

Всё ОК. Сессию загрузили! Пройдём посмотрим что там:

А второй (dothings.php) вот это:

Меня задали на index.php

Переменная $a теперь доступна на всех страницах данного сайта которые запустили сессии.

Другие полезные функции для работы с сессиями:

session_unregister(string) - сессия <забывает> значение заданной глобальной переменной;

session_destroy() - сессия уничтожается (например если пользователь покинул систему нажав кнопку <выход>);

session_set_cookie_params(int lifetime [ string path [ string domain]])-с помощью этой функции можно установить как долго будет <жить> сессия задав unix_timestampопределяющий время <смерти> сессии. По умолчанию сессия <живёт> до тех пор пока клиент не закроет окно браузера.

Примеры

Теперь обратимся к практическому применению механизма сессий. Давайте рассмотрим пару довольно простых и в то же время полезных примеров.

Авторизация Пользователя

Вопросы по авторизации пользователей с помощью PHP-сессий постоянно задаются в конференциях по web-программированию. Механизм авторизации пользователей в системе с помощью сессий довольно хорош с точки зрения безопасности (см. раздел <Безопасность> ниже).

Наш пример будет состоять из трёх файлов: index.php authorize.php и secretplace.php. Файл index.php содержит форму где пользователь введёт свой логин и пароль. Эта форма передаст данные файлу authorize.php который в случае успешной авторизации допустит пользователя к файлу secretplace.php а в противном случае выдаст сообщение об ошибке.

Приступим: - index.php -

<html>

<head>

<title>Введи пароль смертный</title>

</head>

<body>

<form action="authorize.php" method="post">

Логин:<input type="text" name="user_name"><br>

Пароль:<input type="password" name="user_pass"><br>

<input type="submit" name="Submit">

</form>

</body>

</html>

- authorize.php -

<?php

// открываем сессию

session_start();

// данные были отправлены формой?

if($Submit){

// проверяем данные на правильность... в данном случае я

// вписал имя пользователя и пароль прямо в код целесообразней

// было бы проверить логин/пароль в базе данных и при сов-

// падениидатьдоступпользователю...

if(($user_name=="cleo")&&($user_pass=="password")){

$logged_user = $user_name;

// запоминаем имя пользователя

session_register("logged_user");

// и переправляем его на <секретную> страницу...

header("Location: secretplace.php");

exit;

}

}

// если что-то было не так то пользователь получит сообщение об ошибке.

?>

<html><body>

Вы ввели неверный пароль!

</body></html>

- secretplace.php -

<?php

// открываем сессию

session_start();

/*

просто зайти на эту страницу нельзя... если

имя пользователя не зарегистрировано то

перенаправляем его на страницу index.php

для ввода логина и пароля... тут на самом деле

можно много чего сделать например запомнить

IP пользователя и после третьей попытки получить

доступ к файлам его закрыть.

*/

if(!isset($logged_user)){

header("Location: index.php");

exit;

}

?>

<html>

<body>

Привет <?php echo $logged_user; ?> ты на секретной странице!!! :)

</body>

</html>

Безопасность

Итак мы умеем передавать идентификатор от одной страницы (PHP-скрипта) к другой (до следующего вызова с нашего сайта) а значит мы можем различать всех посетителей сайта. Так как идентификатор сессии - это очень большое число (128 бит) шансов что его удастся подобрать перебором практически нет. Поэтому злоумышленнику остаются следующие возможности:

на компьютере пользователя стоит <троян> который ворует номера сессий;

злоумышленник отлавливает трафик между компьютером пользователя и сервером. Конечно есть защищенный (зашифрованный) протокол SSL но им пользуются не все;

к компьютеру нашего пользователя подошел сосед и стащил номер сессии.

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

Впрочем PHP очень часто можно <обмануть>. Давайте рассмотрим возможные точки взлома в программе авторизации пользователя:

Файл authorize.php - попытка подбора пароля с помощью стороннего скрипта;

Файл secretplace.php - попытка обмануть программу путём вписывания значений переменной $logged_user в адресной строке браузера например так:

yoursite/secretplace.php?logged_user=hacker

Итак в нашей программе явно видны две <дыры> одна маленькая и не особо заметная а вот вторая - просто огромная через которую большинство хакеров и лезет туда куда не надо.

Как <залатать> дыру номер 1?

Не будем писать тонны кода по блокировке IP-адреса и т.п. а просто проверим откуда приходит запрос а точнее с какой страницы пришёл запрос если это будет любая страница с нашего сайта то всё нормально а во всех остальных случаях пускать не будем. Подкорректируем файл authorize.php:

- authorize.php V2 -

<?php

// открываем сессию

session_start();

// полный путь к корневой директории где расположены скрипты

$SERVER_ROOT = "localhost/test1/";

// если пользователь пришёл с любой страницы нашего сайта

// то он вроде наш...

// Переменная $HTTP_REFERER всегда доступна по умолчанию

// и содержит полный адрес ссылающейся страницы...

// функция eregi() проверяет начинается ли адрес ссылающейся страницы

// со значения в переменной $SERVER_ROOT

if(eregi("^$SERVER_ROOT" $HTTP_REFERER)){

// данные были отправлены формой?

if($Submit){

// далеевсекакраньше

if(($user_name=="cleo")&&($user_pass=="password")){

$logged_user = $user_name;

// запоминаем имя пользователя

session_register("logged_user");

// и переправляем его на <секретную> страницу...

header("Location: secretplace.php");

exit;

}

}

}

?>

<html><body>

Вы ввели неверный пароль!

</body></html>

Как избавиться от <дыры> номер 2?

Предположим у вас есть сайт где каждый смертный может зарегистрироваться чтобы добавлять сообщения в форум. Естественно в форуме у некоторых пользователей (админов модераторов) возможностей больше чем у других они например могут удалять сообщения других пользователей. Уровень доступа пользователя вы храните в сессии в переменной $user_status где $user_status = 10 соответствует полному доступу к системе. Пришедшему на сайт злоумышленнику достаточно зарегистрироваться штатным образом а потом дописать в адресной строке браузера ?user_status=10. Вот и завёлся у вас на форуме новый админ!

В принципе любую переменную скрипта можно задать через адресную строку просто дописав после полного адреса к скрипту вопросительный знак и название переменной с её значением. Давайте поправим наш код чтобы этого избежать:

- secretplace.php V2 -

<?php

// убираем всё лишнее из адресной строки

// функция unset() <освобождает> переменную

unset($logged_user);

// открываем сессию

session_start();

// и корректируем испорченные перменные.

// Важно: в этом случае переменная регистрируется не как новая

// переменная а как уже существующая а потому знак $ не опускается

session_register($logged_user);

/*

просто зайти на эту страницу нельзя... если

имя пользователя не зарегистрировано то

перенаправляем его на страницу index.php

для ввода логина и пароля... тут на самом деле

можно много чего сделать например запомнить

IP пользователя и после третьей попытки получить

доступ к файлам его перекрыть.

*/

if(!isset($logged_user)){

header("Location: index.php");

exit;

}

?>

<html>

<body>

Привет <?php echo $logged_user; ?> ты на секретной странице!!! :)

</body>

</html>

Итоги

Механизм сессий - довольно удачная особенность языка PHP. Сессии росты очень гибки в использовании. Кстати есть одна мало где документированная возможность сессий PHP (доступна начиная с версии 4.0.3) - в сессиях можно хранить не только переменные но и объекты.

Добавление от 14.12.2001

С выходом в свет PHP 4.1.0 - работа с сессиями значительно облегчилась. Все переменные сессий стали доступны из глобального массива _SESSION['var_name']. Самое приятное наверное в том что при присвоении какого-либо значения любому полю массива переменная с таким же именем автоматически регистрируется как переменная сессии на пр:

<?

$_SESSION['counter'] = 12;

echo $counter;

?>

выведетнаэкранброузерачисло 12.