Механизм сессий в PHP. Автор: Пучкова Д.М. План: 1. Сущность механизма сессии. 2. Область применения сессий. 3. Принципы разработки программ с использованием механизмов сессий. П.1. Сущность механизма сессий. Основной проблемой веб-приложений является их неспособность сохранять данные, введенные пользователем, в процессе работы. Каждая страница генерируется заново, массивы перезаписываются, поэтому некоторая информация теряется, и это создает довольно большие неудобства в процессе работы с пользовательскими запросами. Данную проблему возможно решить несколькими способами: при помощи записи данных в базу данных или временный файл, откуда их впоследствии можно будет извлечь; при помощи создания cookie-файлов, но они являются довольно небезопасными, поскольку уязвимы в силу своей простой структуры; и, наконец, третье решение – использование сессий. Сессии можно рассматривать как контейнер, позволяющий хранить переменные для одного сеанса работы конкретного пользователя. В PHP при помощи некоторых функций можно создавать подобные контейнеры, содержимое которых может быть использовано как набор глобальных переменных, доступных в любом модуле веб-приложения. Для каждого посетителя сайта этот набор будет индивидуален. Сам механизм сессий довольно прост. Все созданные разработчиком глобальные переменные для каждого пользователя система хранит на сервере. А пользователь во время работы с сайтом получает cookie-файл, в котором хранится только идентификатор его сеанса работы (Session ID). Идентификаторы генерируются на PHP таким образом, чтобы исключить возможность доступа к чужой сессии. Проще говоря, в качестве идентификаторов выступают большие последовательности символов, генерируемые произвольным образом. Таким образом, система, получая искомый идентификатор сессии, может понять, от какого именно посетителя пришел запрос, и использовать соответствующие значения для переменных, хранящихся в сессии. Чтобы идентификатор сессии был доступен при каждом запуске сценария, PHP помещает его в cookies браузера. Зная идентификатор (далее для краткости будем называть его SID), PHP может определить, в каком файле находятся данные пользователя. Для сохранения данных в сессии существует глобальный массив $_SESSION, который PHP сохраняет особым образом. Поместив в него некоторые данные, можно быть уверенными, что при следующем запуске сценария с тем же пользователем массив $_SESSION получит то же самое значение, которое было у него при предыдущем завершении программы. Это произойдет потому, что при завершении сценария массив $_SESSION автоматически сохраняется во временном хранилище, имя которого хранится в SID. В самой программе $_SESSION ничем не отличается от обычного ассоциативного массива, однако, его содержимое сохраняется между запусками сценариев одного и того же сайта. 1 Временное хранилище – это либо файлы, под которое выделено специальное место на диске (например, папка \tmp в Unix), либо процедуры или функции, написанные самим пользователем. Работа с сессией начинается с инициализации сессии и заканчивается ее закрытием. Инициализация сессии может быть произведена при помощи вызова специальной функции session_start(). Эта функция инициализирует механизм сессий для текущего пользователя, запустившего сценарий. (ОВТ-09-9-1) По ходу инициализации она выполняет ряд действий: - если посетитель запускает программу впервые, у него устанавливается cookies с уникальным идентификатором, и создается временное хранилище, ассоциированное с этим идентификатором; - определяется, какое хранилище связано с текущим идентификатором пользователя; - если в хранилище имеются какие-то данные, они помещаются в массив $_SESSION; - если параметр register_globals из файла php.ini равен on, то все ключи в массиве $_SESSION и соответствующие им значения превращаются в глобальные переменные. Уничтожение сессии может быть произведено при помощи функции session_destroy(). При этом массив $_SESSION не очищается. Для того, чтобы полностью удалить сессию, необходимо выполнить следующую последовательность команд: // очистить данные сессии для текущей формы $_SESSION = array(); // удалить cookie, соответствующие SID @unset($_COOKIE[session_name()]); // уничтожить хранилище сессии Session_destroy(); П.2. Область применения сессий. Очень важно понимать, для чего сессии стоит использовать, а для чего – нет. Во-первых, необходимо помнить, что сессии можно применять только тогда, когда они нужны самому пользователю, а не для того, чтобы чинить ему препятствия. Ведь он в любой момент может избавиться от идентификатора! Скажем, при проверке на то, что заполняет форму человек, а не скрипт, пользователь сам заинтересован в том, чтобы сессия работала – иначе он не сможет отправить форму. А вот для ограничения количества запросов к скрипту сессия уже не годится – злонамеренный скрипт просто не будет возвращать идентификатор. Во-вторых, важно четко себе представлять тот факт, что сессия – это сеанс работы с сайтом, так как его понимает человек. Пришел, поработал, закрыл браузер – сессия завершилась. Как сеанс в кино. Хочешь посмотреть еще один – покупай новый билет. Стартуй новый сеанс. Этому есть и техническое объяснение. Гарантированно механизм сессий работает только именно до закрытия браузера. Ведь у клиента могут не работать cookie, а в этом случае, естественно, все дополненные идентификатором ссылки пропадут с его закрытием. Правда, сессия может пропасть и без закрытия браузера. В силу некоторых ограничений, механизм сессий не может определить тот момент, когда пользователь закрыл браузер. Для этого используется таймаут – заранее определенное время, по истечении которого мы считаем, что пользователь ушел с сайта. По умолчанию этот параметр равен 24 минутам. Если необходимо сохранять пользовательскую информацию на более длительный срок, то 2 можно использовать cookie и, если надо – базу данных на сервере. В частности, именно так работают все популярные системы авторизации: - по факту идентификации пользователя стартует сессия и признак авторизованности передается в ней. - если надо «запомнить» пользователя, то ему ставится cookie, его идентифицирующая. - при следующем заходе пользователя на сайт, для того, чтобы авторизоваться, он должен либо ввести пароль, либо система сама его опознает по поставленной ранее cookie, и стартует новую сессию. В-третьих, не стоит стартовать сессии без разбору, каждому входящему на сайт. Это создаст совершенно лишнюю нагрузку. Не нужно использовать сессии по пустякам – к примеру, в счетчиках. То, что спайлог называет сессиями, считается, конечно же, на основе статистики заходов, а не с помощью механизма сессий, аналогичного PHP. К тому же, возьмем поисковик, который индексирует сайт. Если поисковый робот не поддерживает cookie, то PHP по умолчанию будет поставлять к ссылкам PHPSESSID, что может не сильно понравится поисковику, который, по слухам, и так-то динамические ссылки не жалует, а тут вообще при каждом заходе – новый адрес. Если сессии используются для ограничения доступа к закрытому разделу сайта, то все просто: поисковик и не должен его индексировать. Если же приходится показывать одну и ту же страницу как авторизованным, так и не авторизованным пользователям, то тут поможет такой прием – стартовать сессию только тем, кто ввел пароль, или тем, у кого уже стартовала сессия. Для этого в начало каждой страницы вместо просто session_start() пишется: if (isset($_REQUEST[session_name()])) {session_start();} Таким образом, сессия запускается только тем, кто прислал идентификатор. Соответственно, надо еще в первый раз отправить его пользователю – в момент авторизации. Если имя и пароль верные – пишется session_start(). П.3. Принципы разработки программ с использованием механизмов сессий. Данный пункт начнем с примера использования сессии, который позволит увидеть, как работают сессии и поэкспериментировать с ними. Данный пример называется «счетчик посетителей страницы». <?php ## счетчик посетителей session_start(); // если на сайт только-только зашли, обнуляем счетчик if (!isset($_SESSION[‘count’])) $_SESSION[‘count’] = 0; // включаем счетчик в сессии $_SESSION[‘count’] = $SESSION[‘count’] + 1; ?> <h2>Счетчик</h2> В текущей сессии работы с браузером Вы открыли эту страницу <?=$_SESSION[‘count’]?> раз(а).<br> Закройте браузер, чтобы обнулить счетчик.<br> <a href=”<?=$_SERVER[‘SCRIPT_NAME’]?>”> target=”_blank”>Открыть браузера</a> дочернее окно Данный скрипт «крутится» вокруг массива $_SESSION. При каждом запуске скрипта один из элементов массива $_SESSION увеличивается на единицу. Отметим некоторые особенности сценария: 3 - при закрытии браузера, счетчик обнуляется; - при открытии нескольких окон и нажатии кнопок “Обновить” в разных окнах, счетчики увеличиваются независимо друг от друга; - при открытии нового окна щелчком по ссылке на некоторой странице данные сессии совместно используются этим окном с его родителем. В общем и целом, работа с сессиями сводится к присвоению некоторым элементам массива $_SESSION некоторых значений, а затем вызова значений этих элементов в нужный момент (в том же или в другом сценарии PHP). Поговорим о функциях работы с сессиями. Как ранее упоминалось, для начала сессии используется функция session_start(), а для закрытия сессии используется функция session_destroy(). Как вариант существует также функция session_unregister(). Этой функции в качестве строкового параметра передается имя переменной, которую надо удалить из сессии. Если удаление прошло успешно, функция возвращает значение True, в противном случае возвращается значение False. Все переменные, включенные в состав сессии, следует рассматривать, как элементы массива $_SESSION. Поэтому для удаления переменной с именем variable следует воспользоваться следующей конструкцией: unset($_SESSION[‘variable’]); Каждая сессия при старте получает идентификатор, который позволяет системе различать сессии. Этот идентификатор, как ранее уже отмечалось, хранится в автоматически создаваемой константе SID. Но все-таки лучше для получения идентификатора использовать специализированную функцию session_id(). Она возвращает строковое значение, в котором указывается идентификатор действующей сессии. Также функция может принимать необязательный строковый параметр с новым идентификатором сессии. В этом случае значением аргумента будет заменен текущий идентификатор. Если необходимо заново сгенерировать идентификатор текущей сессии, следует воспользоваться функцией session_regenerate_id(). После вызова она автоматически заменяет идентификатор сессии сгенерированным значением, при этом все данные сессии сохраняются. В случае успешного выполнения функция возвращает значение True. Если требуется не просто добавить переменную в состав данных сессии, а изменить ее, для начала необходимо проверить, существует ли вообще такая переменная. Для этого можно применить функцию session_is_registered(). В качестве параметра функции передается имя переменной, наличие которой надо проверить. Если такая переменная входит в состав данных, хранящихся в сессии, функция возвращает значение True, в ином случае возвращается значение False. Учитывая, что доступ к данным сессии осуществляется как к элементам массива, можно воспользоваться следующей конструкцией: isset($_SESSION[‘stored’]); Как правило, PHP сохраняет данные сессий в файлах, которые располагаются в заданном каталоге. Имя этого каталога задается директивой конфигурационного файла php.ini session.save_path. Если разработчику необходимо получить имя каталога, в котором хранятся эти файлы, он может воспользоваться функцией session_save_path(). Она возвращает строку, в которой указывается имя исходного каталога. Если функции передать 4 строковый параметр с названием другого каталога, то все данные сессии будут записываться именно в него. Для смены каталога необходимо обладать соответствующими правами. ОВТ-09-(9)-2 Если сессии реализованы за счет cookie-файлов, то необходимо узнать параметры сохранения этих файлов. Это можно сделать при помощи функции session_get_cookie_params(). Она возвращает ассоциированный массив и не принимает параметров. В элементе полученного массива с именем path указывается путь к каталогу, в котором хранится информация. В элементе domain указывается домен для этого cookie-файла. Элемент lifetime указывает время жизни cookie-файла, а в элементе secure указывается, следует ли использовать для передачи cookie-файла безопасное соединение. Далее снова рассмотрим пример, реализующий данную теорию. <?php ?> session_start(); $cookie_info = session_get_cookie_params(); printf(‘Путь - ’); printf(‘$cookie_info[‘path’]’); printf(‘<br>’); printf(‘Домен - ’); printf(‘$cookie_info[‘domain’]’); printf(‘Время жизни - ’); printf(‘$cookie_info[‘lifetime’]’); printf(‘Безопасность - ’); printf(‘$cookie_info[‘secure’]’); printf(‘<br>’); Результат выполнения этого кода представлен ниже. Путь - / Домен – Время жизни – 0 Безопасность – Для того, чтобы задать параметры cookie-файла, необходимо использовать функцию session_set_cookie_params(). В качестве обязательного параметра она принимает целочисленное значение, задающего время жизни файла. Вторым параметром указывается путь хранения файла. Третий параметр задает домен, в рамках которого будет действовать cookie, а четвертый параметр логического типа указывает, нужно ли использовать безопасные каналы передачи файла. Следует отметить, что эту функцию надо вызывать до инициализации сессии. Параметры, заданные этой функцией, будут распространяться на cookie-файл, созданный текущим сценарием. В остальных случаях будут применяться параметры, заданные в файле конфигурации. Далее приведем измененный код программы, где перед стартом сессии добавим вызов функции, задающей параметры файла. <?php Session_set_cookie_params(0,‘tmp’,’localhost’,TRUE); session_start(); $cookie_info = session_get_cookie_params(); printf(‘Путь - ’); printf(‘$cookie_info[‘path’]’); printf(‘<br>’); printf(‘Домен - ’); printf(‘$cookie_info[‘domain’]’); printf(‘Время жизни - ’); printf(‘$cookie_info[‘lifetime’]’); printf(‘Безопасность - ’); 5 printf(‘$cookie_info[‘secure’]’); printf(‘<br>’); ?> Результат выполнения этого кода представлен ниже. Путь - /tmp Домен – localhost Время жизни – 0 Безопасность – 1 Данные сессии хранятся на сервере в отдельных файлах в сериализованном, т.е. строковом виде. Операция сериализации обратима, поэтому когда необходимо воспользоваться сохраненными данными, разработчик их получает, даже не задумываясь о необходимости провести десериализацию, потому что она также выполняется автоматически. Однако программист все же имеет возможность провести сериализацию данных самостоятельно. Для этого можно воспользоваться функцией session_encode(). Она возвращает строку, в которой записаны данные сессии в сериализованном виде. Пример использования этой функции приведен ниже: <?php ?> session_start(); $_SESSION[‘stored’] = ‘Хранимый текст’; $_SESSION[‘number’] = 15; $serialized_data = session_encode(); printf($serialized_data); Результат выполнения этого кода представлен ниже: stored|s:14:”Хранимый текст”;number|i:15; Нетрудно заметить, что при сериализации в строку записываются имя сохраняемой переменной, ее тип в сокращенном виде и значение. Переменные отделяются друг от друга точкой с запятой. В сессии можно хранить и сериализовать не только переменные, но и объекты. Далее рассмотрим код примера, как это можно сделать. <?php class MyClass { var $field1; var $field2; // конструктор function MyClass($field1,$filed2) { $this -> field1 = $field1; $this -> filed2 = $field2; } function sum() { return($this -> field1 + $this -> field2); } } session_start(); $obj = new MyClass(1,2); $_SESSION[‘stored’] = $obj; $serialized_data = session_encode(); 6 ?> printf($serialized_data); Результат выполнения этого кода представлен ниже: stored|O:7:”MyClass”:2:{s:6:”field1”;i:1;s:6:”field2”;i:2;} Поговорим о защите сессий. Часто возникает ситуация, когда несколько сайтов, использующих PHP, запущены на одном сервере. При этом файлы, хранящие данные сессий всех этих сайтов, чаще всего находятся на одном каталоге. В этом случае злоумышленник, обладающий правами администратора, может получить доступ к этой папке и прочитать данные всех сессий. Поэтому необходимо подумать о защите своих сессий. Одним из вариантов защиты является создание альтернативного варианта обработки сессий. Для этого разработчик может использовать, скажем, базу данных, в которой для хранения данных сессии будет выделена отдельная таблица. Для того, чтобы воспользоваться данным способом, необходимо явно указать системе, что в процессе работы с сессиями она должна использовать функции, созданные программистом. Для этого используется функция session_set_save_handler(), объявляемая как: Bool session_set_save_handler(string write, string destroy, string gc); open, string close, string read, string В качестве параметров этой функции указываются имена функций, созданные программистом для работы с сессиями. Последовательно задаются функции для открытия сессии, закрытия сессии, чтения и записи данных, уничтожения данных, которые уже считаются мусором, и функция, которая рассчитывает необходимость вызова сборщика мусора. Далее выясним вопрос, использовать ли cookie в сессиях. Из многочисленных источников можно сделать вывод – да, использовать. Для этого в php.ini параметр session.use_cookies = true. Если пользователь отключил у себя cookies, то PHP автоматически добавляет идентификатор сессии ко всем ссылкам и формам, которые он встретит, и сценарии будут продолжать работать. Правда, их URL несколько удлинится. При работе с сессиями PHP объединяет их в группы – как соответствующие тому или иному сценарию. Сессии, принадлежащие одному сценарию, должны быть выделены в группу сессий, которая имеет уникальное имя. Таким образом можно избежать пересечения названия переменных, принадлежащих разным сценариям. Устанавливает или возвращает имя группы сессии функция: string session_name([string $newname]); Если параметр $newname не задан, то просто возвращается текущее имя, а его смены не происходит. Если же этот параметр указан, то имя группы будет изменено на $newname, при этом функция вернет предыдущее имя. Если функция session_name не была вызвана до инициализации, PHP будет использовать имя по умолчанию – PHPSESSID. Для определения идентификатора сессии SID существует функция string session_id([string $sid]); Функция возвращает текущий идентификатор сессии SID. Если задан параметр $sid, то у активной сессии изменяется идентификатор на $sid. Вызвав session_id() до 7 session_start(), возможно переключиться к любой сессии на сервере, если известен ее идентификатор. Разделять сессии на группы иногда оказывается не лучшей идеей. Действительно, если авторизация на сайте происходит при помощи механизма сессии, смена имени группы автоматически ее аннулирует. Поэтому в большинстве ситуаций изменять имя группы сессий вообще не рекомендуется. Вместо этого лучше хранить данные не во всем массиве $_SESSION, а в некотором его подмассиве. Например, система управления форумом может работать только с элементом $_SESSION[‘ForumSubsystem’], а система авторизации – с $_SESSION[‘AuthSubsystem’]. Такую работу можно организовать следующим образом: // создадим копию элемента $_SESSION[‘ForumSubSystem’] и будем обращаться к // нему, как к ассоциативному массиву $ForumSession = & $_SESSION[‘ForumSubSystem’]; $ForumSession[‘count’] = @$ForumSession[‘count’] + 1; ... // теперь для системы авторизации $AuthSession = & $_SESSION[‘authSubSystem’]; $AuthSession[‘count’] = $AuthSession[‘count’] + 1; $AuthSession[‘isauthorized’] = true; Форум работает только в пределах $_SESSION[‘ForumSubSystem’], а подсистема авторизации – в пределах $SESSION[‘AuthSubSystem’]. Пересечения данных в сессии не происходит – в обоих случаях используются элементы с одним и тем же именем count, но это разные элементы. Теперь осталось описать несколько функций, связанных с сессиями, которые PHP вызывает в тот или иной момент работы механизма обработки сессий. Им передаются различные параметры, необходимые для работы. Всего таких функций шесть: • • • bool $save_path, string $session_name); - функция вызывается session_start(). Обработчик должен взять на себя handler_open(string запускается, когда всю работу, связанную с открытием базы данных для группы сессий с именем $session_name. В параметре save_path передается то, что было указано при вызове session_save_path(), или путь к файлам-хранилищам данных сессий по умолчанию. bool handler_close(); - вызывается, когда данные сессии уже записаны во временное хранилище и его нужно закрыть. string handler_read(); - для прочтения данных сессии с идентификатором $sid из временного хранилища. Функция должна возвращать данные сессии в специальном формате, который выглядит так: имя1=значение1;имя2=значение2;имя3=значение3;... Здесь имяN задает имя очередной переменной, зарегистрированной значениеN – результат вызова функции serialize() для значения этой в сессии, а переменной. Например, запись может иметь вид: foo|i:1;count|i:10; • Она говорит о том, что из временного хранилища были прочитаны две целые переменные, первая из которых равна 1, а вторая – 10. string handler_write(string $sid, string $data); Этот обработчик предназначен для записи данных сессии с идентификатором $sid во временное хранилище, например, открытое ранее обработчиком handler_open(). Параметр $data задается в точно таком же формате, который был описан выше. Фактически, чаще всего действия этой функции сводятся к записи в базу данных строки $data без каких-либо изменений. • bool handler_destroy(string $sid); – обработчик вызывается, когда сессия с идентификатором $sid должна быть уничтожена. 8 • – обработчик вызывается каждый раз при завершении работы сценария; он уничтожает данные сессии во временном хранилище. Функции передается в параметрах то время (в секундах), по прошествии которого PHP принимает решение о необходимости «собрать мусор», т.е. это максимальное время существование сессии. bool handler_gc(int $maxlifetime); В PHP также представляется возможным создавать собственные обработчики. Для регистрации обработчиков существует функция регистрации, которая сообщает интерпретатору, какую функцию он должен вызвать при наступлении того или иного события: void session_set_save_handler($open,$close,$read,$write,$destroy,$gc); Эта функция регистрирует процедуры, имена которых переданы в ее параметрах, как обработчики текущей сессии. Параметр $open содержит имя функции, которая будет вызвана при инициализации сессии, а $close – функции, вызываемой при ее закрытии. В $read и $write нужно указать имена обработчиков, соответственно, для чтения и записи во временное хранилище. Функция с именем, заданным в destroy(), будет вызвана при уничтожении сессии. Обработчик, определяемый параметром $gc, используется как сборщик мусора. Эту функцию можно вызвать только до инициализации сессии, иначе она будет игнорироваться. Таким образом, в данной лекции были рассмотрены основные моменты работы с сессиями как с мощным механизмом обработки пользовательских запросов. Были охвачены вопросы механизма работы с сессиями, области применения сессий и принципы разработки программ с использованием механизмов сессий. 9 Литература. 1. www.php.net 2. Д.Котеров, А.Костарев, PHP 5. – СПб.: БХВ-Петербург, 2005. 3. И.В.Шапошников, PHP 5.1. Учебный курс. – СПб.: Питер, 2007. – 192 с. 10