Сессии в 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 в адресной
строке браузера, напримертак:
//www.yoursite.ru/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.
|