Использование сессий в php. Сессии в PHP

14.07.2019

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

Что такое сессия? Сессии позволяют сделать своего рода связь между самим сайтом и пользователем, при помощи идентификатора сессии. Все переменные сессии и их значения хранятся исключительно на сервере. У пользователя, а также на сервере хранятся только идентификаторы сессии, которые генерируются рандомным образом, они как раз таки и позволяют установить эту самую связь сервер - клиент.

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

Давайте перейдем непосредственно к практике.

Создаем сессию:

Самый элементарный способ здесь, применить функцию session_start:

1 2 // Запустить сессию session_start () ;

// Запустить сессию session_start();

Данная функция проверяет, есть ли идентификатор сессии, если его нет, то создает его. А если же, он имеется, то загружает зарегистрированные переменные из имеющейся сессии.

Такая конструкция должна быть вызвана только один раз для каждой страницы и до любого вывода (данное правило также подходит и для setcookie()).

Приведу пример, когда сессия создается в браузере, то кука там имеет следующий вид

Когда создается сессия в браузер ‛прилетает“ кука следующего вида:

1 2 echo "Название сессии: " . session_name () . " Идентификатор сессии: " . session_id () ; // Название сессии: PHPSESSID Идентификатор сессии: mceu371l97id3sa0vcbjqnht06

echo "Название сессии: ".session_name(). " Идентификатор сессии: ".session_id(); // Название сессии: PHPSESSID Идентификатор сессии: mceu371l97id3sa0vcbjqnht06

Создадим переменную сессии:

Переменную сессии можно создать добавив какое-то значение суперглобальному элементу массива $_SESSION:

unset($_SESSION["login"]);

Способ выше хорош, но можно очистить весь массив $_SESSION , тем самым мы удалим все переменные из сессии:

1 2 // Чистим наш массив $_SESSION $_SESSION = array () ;

// Чистим наш массив $_SESSION $_SESSION = array();

2. Теперь нам необходимо сделать файл куки недействительным (где имя сессии ссылается при обращении на session id в куках и URL):

1 2 3 if (isset ($_COOKIE [ session_name () ] ) ) { // session_name() - вытаскиваем название текущей сессии setcookie (session_name () , "" , time () - 86400 , "/" ) ; }

if (isset($_COOKIE)) { // session_name() - вытаскиваем название текущей сессии setcookie(session_name(), "", time()-86400, "/"); }

3. Ну и следом уничтожим сессию (закроем её):

session_start(); ob_start();

Вот более подробный пример кода с использованием ранее рассмотренных кодов и функций:

1 2 3 4 5 6 7 8 9 if (isset ($_SESSION [ "login" ] ) ) { echo "Привет, " . $_SESSION [ "name" ] . " " ; unset ($_SESSION [ "login" ] ) ; if (isset ($_COOKIE [ session_name () ] ) ) { setcookie (session_name () , "" , time () - 86400 , "/" ) ; // содержимым нашей сессии является пустая строка } ob_end_flush () ; // Отправляем браузеру вывод session_destroy () ;

if (isset($_SESSION["login"])) { echo "Привет, " . $_SESSION["name"] . " "; unset($_SESSION["login"]); if (isset($_COOKIE)) { setcookie(session_name(), "", time()-86400, "/"); // содержимым нашей сессии является пустая строка } ob_end_flush(); // Отправляем браузеру вывод session_destroy();

Однако стоит обратить внимание, что использование функции ob_end_flush() не всегда является обязательным. А всё потому что интерпретатор PHP автоматически чистит ваш буфер при выполнении какого-то сценария.

Пересоздадим идентификатор сессии:

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

Принцип работы сессий:

На скриншоте ниже вы можете узреть небольшой обзор самого механизма сессий.

Уменьшим время жизни сессии:

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

1 2 3 4 5 6 7 8 $_SESSION [ "start" ] = time () ; // Начало времени, когда пользователь авторизовался $timezon = time () ; // Данное время (то которое есть сейчас) $time_limit = 2000 ; // Это максимальное время не активности пользователя if ($timezon & gt; $_SESSION [ "start" ] + $time_limit ) { echo "Время закончилось" ; } else { $_SESSION [ "start" ] = time () ; } // если всё хорошо, то обновляем

$_SESSION["start"] = time(); // Начало времени, когда пользователь авторизовался $timezon= time(); // Данное время (то которое есть сейчас) $time_limit = 2000; // Это максимальное время не активности пользователя if ($timezon> $_SESSION["start"] + $time_limit) { echo "Время закончилось"; } else {$_SESSION["start"] = time();} // если всё хорошо, то обновляем

Иногда возникает у людей вопрос "Как реализовать бесконечное время жизни сессии?" , тут ответ я дам вам такой. Этого не стоит делать, это в корне идеи уже есть не правильно. Сессия на то и дана, чтобы пользователь зашел на сайта - она открылась, он ушел - она закрылась (уничтожилась). Когда он опять зашел сессия открылась новая. Однако можно использовать для сессии данные из куки, которые могут храниться достаточно долго, к примеру при использовании галочки "Запомнить меня" (Помните ведь такую на сайтах?)...

Используем сессии при отключенном cookie:

Скажите такого не бывает? Увы и такое иногда встречается. Например если для настройки session.use_trans_sid мы установим значение 1 , то при отсутствии использования cookie, PHP будет передавать параметры PHPSESSID с использованием GET метода в вашей строке запроса.

Вот и всё, статья закончена. Если у вас еще остались вопросы касательно использования сессий, а может есть какие-то дополнения или замечания, то можете оставить всё в комментариях к данной статье.

Приветствую, уважаемое сообщество.

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

Цель этой статьи - осветить подводные камни использования сессий в PHP. Конечно, есть документация по PHP и масса примеров, и данная статья не претендует на полное руководство. Она призвана раскрыть некоторые ньюансы работы с сессиями и оградить разработчиков от ненужной траты времени.

Самым распространенным примером использования сессий является, конечно, авторизация пользователей. Начнем с самой базовой реализации, чтобы последовательно развивать ее по мере появления новых задач.

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

Function startSession() { // Если сессия уже была запущена, прекращаем выполнение и возвращаем TRUE // (параметр session.auto_start в файле настроек php.ini должен быть выключен - значение по умолчанию) if (session_id()) return true; else return session_start(); // Примечание: До версии 5.3.0 функция session_start()возвращала TRUE даже в случае ошибки. // Если вы используете версию ниже 5.3.0, выполняйте дополнительную проверку session_id() // после вызова session_start() } function destroySession() { if (session_id()) { // Если есть активная сессия, удаляем куки сессии, setcookie(session_name(), session_id(), time()-60*60*24); // и уничтожаем сессию session_unset(); session_destroy(); } }

Примечание: Подразумевается, что базовые знания о сессиях PHP у читателя имеются, поэтому принцип работы функций session_start() и session_destroy() освещать здесь не будем. Задачи верстки формы входа и аутентификации пользователя не относятся к теме статьи, поэтому их мы также опустим. Напомню только, что для идентификации пользователя в каждом последующем запросе, нам необходимо в момент успешного входа сохранить в сессионной переменной (с именем userid, например) идентификатор пользователя, который будет доступен во всех последующих запросах в пределах жизни сессии. Также необходимо реализовать обработку результата нашей функции startSession(). Если функция вернула FALSE - отобразить в браузере форму входа. Если функция вернула TRUE, и сессионная переменная, содержащая идентификатор авторизованного пользователя (в нашем случае - userid), существует - отобразить страницу авторизованного пользователя (подробнее об обработке ошибок см. дополнение от 2013-06-07 в разделе о сессионных переменных).

Пока все понятно. Вопросы начинаются, когда требуется реализовать контроль отсутствия активности пользователя (session timeout), дать возможность одновременной работы в одном браузере нескольких пользователей, а также защитить сессии от несанкционированного использования. Об этом и пойдет речь ниже.

Контроль отсутствия активности пользователя встроенными средствами PHP

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

Function startSession() { // Таймаут отсутствия активности пользователя (в секундах) $sessionLifetime = 300; if (session_id()) return true; // Устанавливаем время жизни куки ini_set("session.cookie_lifetime", $sessionLifetime); // Если таймаут отсутствия активности пользователя задан, устанавливаем время жизни сессии на сервере // Примечание: Для production-сервера рекомендуется предустановить эти параметры в файле php.ini if ($sessionLifetime) ini_set("session.gc_maxlifetime", $sessionLifetime); if (session_start()) { setcookie(session_name(), session_id(), time()+$sessionLifetime); return true; } else return false; }

Немного пояснений. Как известно, PHP определяет, какую именно сессию нужно запустить, по имени куки, передаваемом браузером в заголовке запроса. Браузер же, в свою очередь, получает этот куки от сервера, куда помещает его функция session_start(). Если время жизни куки в браузере истекло, он не будет передан в запросе, а значит PHP не сможет определить, какую сессию нужно запустить, и расценит это как создание новой сессии. Параметр настроек PHP session.gc_maxlifetime, который устанавливается равным нашему таймауту отсутствия активности пользователя, задает время жизни PHP-сессии и контролируется сервером. Работает контроль времени жизни сессии следующим образом (здесь рассматривается пример хранилища сессий во временных файлах как самый распространенный и установленный по умолчанию в PHP вариант).

В момент создания новой сессии в каталоге, установленном как каталог для хранения сессий в параметре настроек PHP session.save_path, создается файл с именем sess_, где - идентификатор сессии. Далее, в каждом запросе, в момент запуска уже существующей сессии, PHP обновляет время модификации этого файла. Таким образом, в каждом следующем запросе PHP, путем разницы между текущим временем и временем последней модификации файла сессии, может определить, является ли сессия активной, или ее время жизни уже истекло. (Механизм удаления старых файлов сессий более подробно рассматривается в следующем разделе).

Примечание: Здесь следует отметить, что параметр session.gc_maxlifetime действует на все сессии в пределах одного сервера (точнее, в пределах одного главного процесса PHP). На практике это значит, что если на сервере работает несколько сайтов, и каждый из них имеет собственный таймаут отсутствия активности пользователей, то установка этого параметра на одном из сайтов приведет к его установке и для других сайтов. То же касается и shared-хостинга. Для избежания подобной ситуации используются отдельные каталоги сессий для каждого сайта в пределах одного сервера. Установка пути к каталогу сессий производится с помощью параметра session.save_path в файле настроек php.ini, или путем вызова функции ini_set(). После этого сессии каждого сайта будут храниться в отдельных каталогах, и параметр session.gc_maxlifetime, установленный на одном из сайтов, будет действовать только на его сессии. Мы не станем рассматривать этот случай подробно, тем более, что у нас в запасе есть более гибкий вариант контроля отсутствия активности пользователя.

Контроль отсутствия активности пользователя с помощью сессионных переменных

Казалось бы, предыдущий вариант при всей своей простоте (всего пару дополнительных строк кода) дает все, что нам нужно. Но что, если не каждый запрос можно расценивать как результат активности пользователя? Например, на странице установлен таймер, который периодически выполняет AJAX-запрос на получение обновлений от сервера. Такой запрос нельзя расценивать как активность пользователя, а значит автоматическое продление времени жизни сессии является не корректным в данном случае. Но мы знаем, что PHP обновляет время модификации файла сессии автоматически при каждом вызове функции session_start(), а значит любой запрос приведет к продлению времени жизни сессии, и таймаут отсутствия активности пользователя не наступит никогда. К тому же, последнее примечание из предыдущего раздела о тонкостях работы параметра session.gc_maxlifetime может показаться кому-то слишком запутанным и сложным в реализации.

Для решения этой проблемы откажемся от использования встроенных механизмов PHP и введем несколько новых сессионных переменных, которые позволят нам контролировать время отсутствия активности пользователей самостоятельно.

Function startSession($isUserActivity=true) { $sessionLifetime = 300; if (session_id()) return true; // Устанавливаем время жизни куки до закрытия браузера (контролировать все будем на стороне сервера) ini_set("session.cookie_lifetime", 0); if (! session_start()) return false; $t = time(); if ($sessionLifetime) { // Если таймаут отсутствия активности пользователя задан, // проверяем время, прошедшее с момента последней активности пользователя // (время последнего запроса, когда была обновлена сессионная переменная lastactivity) if (isset($_SESSION["lastactivity"]) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) { // Если время, прошедшее с момента последней активности пользователя, // больше таймаута отсутствия активности, значит сессия истекла, и нужно завершить сеанс destroySession(); return false; } else { // Если таймаут еще не наступил, // и если запрос пришел как результат активности пользователя, // обновляем переменную lastactivity значением текущего времени, // продлевая тем самым время сеанса еще на sessionLifetime секунд if ($isUserActivity) $_SESSION["lastactivity"] = $t; } } return true; }

Подытожим. В каждом запросе мы проверяем, не достигнут ли таймаут с момента последней активности пользователя до текущего момента, и если он достигнут - уничтожаем сессию и прерываем выполнение функции, возвращая FALSE. Если же таймаут не достигнут, и в функцию передан параметр $isUserActivity со значением TRUE - обновляем время последней активности пользователя. Все, что нам остается сделать - это определять в вызывающем скрипте, является ли запрос результатом активности пользователя, и если нет - вызывать функцию startSession со значением параметра $isUserActivity, равным FALSE.

Дополнение от 2013-06-07
Обработка результата функции sessionStart()

В комментариях обратили внимание на то, что возврат FALSE не дает полного понимания причины ошибки, и это абсолютно справедливо. Я не стал публиковать здесь подробную обработку ошибок (объем статьи и так не маленький), поскольку это не относится напрямую к теме статьи. Но учитывая комментарии, внесу ясность.

Как видно, функция sessionStart может вернуть FALSE в двух случаях. Либо сессию не удалось запустить из-за каких-то внутренних ошибок сервера (например, неправильные настройки сессий в php.ini), либо время жизни сессии истекло. В первом случае мы должны перебросить пользователя на страницу с ошибкой о том, что есть проблемы на сервере, и формой обращения в службу поддержки. Во втором случае мы должны перевести пользователя на форму входа и вывести в ней соответствующее сообщение о том, что время сессии истекло. Для этого нам необходимо ввести коды ошибок и возвращать вместо FALSE соответствующий код, а в вызывающем методе проверять его и действовать соответствующим образом.

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

Примечание: А что произойдет, если браузер был закрыт, и куки с именем сессии был автоматически уничтожен? Запрос к серверу при следующем открытии браузера не будет содержать куки сессии, и сервер не сможет открыть сессию и проверить таймаут отсутствия активности пользователя. Для нас это равносильно созданию новой сессии и никак не влияет на функционал и безопасность. Но возникает справедливый вопрос - а кто же тогда уничтожит старую сессию, если до сих пор ее уничтожали мы по истечении таймаута? Или она теперь будет висеть в каталоге сессий вечно? Для очистки старых сессий в PHP существует механизм под названием garbage collection. Он запускается в момент очередного запроса к серверу и чистит все старые сессии на основании даты последнего изменения файлов сессий. Но запуск механизма garbage collection происходит не при каждом запросе к серверу. Частота (а точнее, вероятность) запуска определяется двумя параметрами настроек session.gc_probability и session.gc_divisor. Результат от деления первого параметра на второй и есть вероятностью запуска механизма garbage collection. Таким образом, для того, чтобы механизм очистки сессий запускался при каждом запросе к севреру, эти параметры нужно установить в равные значения, например «1». Такой подход гарантирует чистоту каталога сессий, но, очевидно, является слишком накладным для сервера. Поэтому в production-системах по умолчанию устанавливается значение session.gc_divisor, равное 1000, что означает, что механизм garbage collection будет запускаться с вероятностью 1/1000. Если вы поэкспериментируете с этими настройками в своем файле php.ini, то сможете заметить, что в описанном выше случае, когда браузер закрывается и очищает все свои куки, в каталоге сессий какое-то время все еще остаются старые сессии. Но это не должно вас волновать, т.к. как уже было сказано, это ни коим образом не влияет на безопасность нашего механизма.

Дополнение от 2013-06-07

Предотвращение зависания скриптов из-за блокировки файла сессии

В комментариях подняли вопрос о зависании одновременно выполняющихся скриптов из-за блокировки файла сессии (как самый яркий вариант - long poll).

Для начала отмечу, что эта проблема напрямую не зависит от загруженности сервера или количества пользователей. Конечно, чем больше запросов, тем медленнее выполняются скрипты. Но это коссвенная зависимость. Проблема появляется только в пределах одной сессии, когда серверу приходит несколько запросов от имени одного пользователя (например, один из них long poll, а остальные - обычные запросы). Каждый запрос пытается получить доступ к одному и тому же файлу сессии, и если предыдущий запрос не разблокировал файл, то последующий будет висеть в ожидании.

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

В нашем примере это означает, что сразу после открытия сессии, проверки времени ее жизни и существования авторизованного пользователя, мы должны считать и сохранить все дополнительные необходимые приложению сессионные переменные (если такие существуют), после чего закрыть сессию с помощью вызова session_write_close() и продолжить выполнение скрипта, будь то long poll или обычный запрос.

Защита сессий от несанкционированного использования

Представим себе ситуацию. Один из ваших пользователей цепляет троян, который грабит куки браузера (в котором хранится наша сессия) и отправляет его на указанный email. Злоумышленник получает куки и использует его для подделки запроса от имени нашего авторизованного пользователя. Сервер успешно принимает и обрабатывает этот запрос, как если бы он пришел от авторизованного пользователя. Если не реализована дополнительная проверка IP-адреса, такая атака приведет к успешному взлому аккаунта пользователя со всеми вытекающими последствиями.

Почему это стало возможным? Очевидно, потому что имя и идентификатор сессии всегда одни и те же на все время жизни сессии, и если получить эти данные, то можно беспрепятственно слать запросы от имени другого пользователя (естественно, в пределах времени жизни этой сессии). Возможно, это не самый распространенный вид атак, но теоретически все выглядит вполне реализуемым, особенно учитывая, что подобному трояну даже не нужны права администратора, чтобы грабить куки браузера пользователя.

Как же можно защититься от атак подобного рода? Опять-таки, очевидно, ограничив время жизни идентификатора сессии и периодически изменяя идентификатор в пределах одной сессии. Мы можем также изменять и имя сессии, полностью удаляя старую и создавая новую сессию, копируя в нее все сессионные переменные из старой. Но на суть подхода это не влияет, поэтому для простоты ограничимся только идентификатором сессии.

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

(Опустим ту часть кода, которая уже рассмотрена).

Function startSession($isUserActivity=true) { // Время жизни идентификатора сессии $idLifetime = 60; ... if ($idLifetime) { // Если время жизни идентификатора сессии задано, // проверяем время, прошедшее с момента создания сессии или последней регенерации // (время последнего запроса, когда была обновлена сессионная переменная starttime) if (isset($_SESSION["starttime"])) { if ($t-$_SESSION["starttime"] >= $idLifetime) { // Время жизни идентификатора сессии истекло // Генерируем новый идентификатор session_regenerate_id(true); $_SESSION["starttime"] = $t; } } else { // Сюда мы попадаем, если сессия только что создана // Устанавливаем время генерации идентификатора сессии в текущее время $_SESSION["starttime"] = $t; } } return true; }

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

Примечание: Новый идентификатор сессии попадает в куки браузера при вызове функции session_regenerate_id(), которая отправляет новый куки, аналогично функции session_start(), поэтому нам нет необходимости обновлять куки самостоятельно.

Если мы хотим максимально обезопасить наши сессии, достаточно установить время жизни идентификатора в единицу или же вообще вынести функцию session_regenerate_id() за скобки и убрать все проверки, что приведет к регенерации идентификатора в каждом запросе. (Я не проверял влияние такого подхода на быстродействие, и могу только сказать, что функция session_regenerate_id(true) выполняет по сути всего 4 действия: генерация нового идентификатора, создание заголовка с куки сессии, удаление старого и создание нового файла сессии).

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

Возможность одновременной работы в одном браузере от имени нескольких пользователей

Последняя задача, которую хотелось бы рассмотреть - возможность одновременной работы в одном браузере нескольких пользователей. Эта возможность особенно полезна на этапе тестирования, когда нужно эмулировать одновременную работу пользователей, и желательно делать это в своем любимом браузере, а не использовать весь доступный арсенал или открывать несколько экземпляров браузера в режиме «инкогнито».

В наших предыдущих примерах мы не задавали явно имя сессии, поэтому использовалось имя, установленное в PHP по умолчанию (PHPSESSID). Это значит, что все сессии, которые создавались нами до сих пор, отправляли браузеру куки под именем PHPSESSID. Очевидно, что если имя куки всегда одинаковое, то нет возможности в пределах одного браузера организовать две сессии с одинаковым именем. Но если бы мы для каждого пользователя использовали собственное имя сессии, то проблема была бы решена. Так и сделаем.

Function startSession($isUserActivity=true, $prefix=null) { ... if (session_id()) return true; // Если в параметрах передан префикс пользователя, // устанавливаем уникальное имя сессии, включающее этот префикс, // иначе устанавливаем общее для всех пользователей имя (например, MYPROJECT) session_name("MYPROJECT".($prefix ? "_".$prefix: "")); ini_set("session.cookie_lifetime", 0); if (! session_start()) return false; ... }

Теперь осталось позаботиться о том, чтобы вызывающий скрипт передавал в функцию startSession() уникальный префикс для каждого пользователя. Это можно сделать, например, через передачу префикса в GET/POST параметрах каждого запроса или через дополнительный куки.

Заключение

В заключение приведу полный конечный код наших функций для работы с сессиями PHP, включающий все рассмотренные выше задачи.

Function startSession($isUserActivity=true, $prefix=null) { $sessionLifetime = 300; $idLifetime = 60; if (session_id()) return true; session_name("MYPROJECT".($prefix ? "_".$prefix: "")); ini_set("session.cookie_lifetime", 0); if (! session_start()) return false; $t = time(); if ($sessionLifetime) { if (isset($_SESSION["lastactivity"]) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) { destroySession(); return false; } else { if ($isUserActivity) $_SESSION["lastactivity"] = $t; } } if ($idLifetime) { if (isset($_SESSION["starttime"])) { if ($t-$_SESSION["starttime"] >= $idLifetime) { session_regenerate_id(true); $_SESSION["starttime"] = $t; } } else { $_SESSION["starttime"] = $t; } } return true; } function destroySession() { if (session_id()) { session_unset(); setcookie(session_name(), session_id(), time()-60*60*24); session_destroy(); } }

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

Автор: V. Nagaradjane
Перевод: А.Панин

Сессии на основе кук

PHP предоставляет реализацию механизма управления сессиями на основе кук. Массив $_SESSION используется для хранения данных сессии. PHP автоматически генерирует идентификаторы сессий и отправляет куки, содержащие эти идентификаторы клиентам. Функции PHP для управления сессиями описаны в таблице ниже.

Таблица 1: Функции PHP для управления сессиями

Стандартный процесс создания сессии начинается с вывода страницы с двумя полями ввода для имени пользователя и пароля. Следующий HTML-код используется для формирования страницы входа в систему (внешний вид этой страницы показан на Рисунке 3):

Login


Рисунок 3: Страница входа в систему

Вход в систему

Имя пользователя и пароль передаются сценарию PHP с именем login.php . Этот сценарий использует глобальную переменную $_POST для получения строк, введенных в поля имени пользователя и пароля. После этого устанавливается соединение с базой данных "session" и из нее извлекаются соответствующие имени пользователя идентификатор пользователя и пароль. Если имя пользователя обнаруживается, пароль, хранимый в базе данных, сравнивается с паролем, введенным пользователем. Если пароли не совпадают, попытка входа в систему отклоняется. В противном случае осуществляется вход. Ниже представлен код сценария login.php :

User %s not found


Go to login page
", $username); return false; } if(mysql_result($result, 0, "pass") != $password) { printf("

Login attempt rejected for %s!


Go to login page
", $username); return false; } session_start(); $_SESSION["username"] = $username; $_SESSION["id"] = mysql_result($result,0,"id"); mysql_close($conn); return true; } if(check_login($_POST["username"], $_POST["passwd"])) printf("

Welcome %s!



n",$_SESSION["username"]); print("Check status!
View Protected Image
Logout
n"); ?>

В случае, если имя пользователя и пароль корректны, вызывается функция session_start() , которая в свою очередь отправляет куки с идентификатором сессии пользователя клиенту. Данные кук показаны на Рисунке 4. После этого становится возможным доступ к данным с помощью вызовов $_SESSION["username"] или $_SESSION["id"] для получения или сохранения данных сессии. В данном случае в массиве $_SESSION сохраняются имя и идентификатор пользователя.


Рисунок 4: Куки сессии (PHPSESSID) показаны в списке кук браузера Firefox

Созданный с помощью функции session_start() идентификатор сессии хранится в куках на машине клиента. Вы можете исследовать куки, воспользовавшись пунктами меню Firefox "Правка->Настройки", выбрав вкладку "Приватность" и нажав на ссылку "удалить отдельные куки". После этого будет выведен список кук, отсортированный по имени сервера. В данном случае в качестве имени сервера используется адрес 127.0.0.1 и в качестве имени переменной кук используется строка "PHPSESSID" - вы можете заметить ее в поле "Содержимое" ("Content") в области вывода информации. Страница приветствия, показанная после входа в систему, изображена на Рисунке 5.


Рисунок 5: Страница приветствия, выводящаяся после успешного входа

Состояние сессии

После открытия сессии вы можете проверить постоянство значений имени и идентификатора пользователя. Давайте создадим для этого небольшой сценарий с именем status.php . Этот сценарий вызывает функцию session_start() . Так как куки сессии доступны на клиентской машине, при вызове функции session_start() происходит получение идентификатора сессии и загрузка соответствующих переменных сессии вместе с их значениями на стороне сервера. Следовательно, вызов $_SESSION["username"] или $_SESSION["id"] вернет данные, сохраненные в файле сценария login.php . Сценарий status.php выглядит следующим образом:

invalid session!


nLogin"); exit(); } printf("
Welcome %s! Your id is: %d",$_SESSION["username"],$_SESSION["id"]); printf("
Logout %s
",$_SESSION["username"]); ?>

Этот сценарий проверки состояния доступен при переходе по ссылке с именем "Check Status!" на странице приветствия. Он выводит имя и идентификатор пользователя, полученные из массива данных сессии. Таким образом, выполняется основное требование, предъявляемое к сессиям и заключающееся в постоянстве данных при переходе между различными страницами после входа в систему.

Создание защищенных страниц

Главной целью механизма сессий является создание защищенных страниц. Простой сценарий PHP, приведенный ниже, позволяет защитить изображения от публичного доступа. Файл сценария protectedimage.php вызывает функцию require_once("status.php") в самом начале. С помощью нее происходит однократное исполнение сценария проверки состояния. Сценарий проверки состояния проверяет работоспособность сессии и позволяет выполнение последующих функций в случае действительной сессии, либо прекращает работу сценария в ином случае. Код для защиты изображения показан ниже:

"); ?>

Защищенное изображение, которое выводится после корректного открытия сессии, показано на Рисунке 6, а на Рисунке 7 показана та же самая страница (http://127.0.0.1/protectedimage.php ), загруженная без предварительного открытия сессии. Посмотрите на URL в адресной строке браузера на обоих рисунках - страница с одним и тем же URL содержит картинку в случае доступных данных сессии и запрещает доступ к картинке если данные сессии недоступны.


Рисунок 6: Картинка является защищенным содержимым страницы


Рисунок 7: Доступ к защищенному содержимому страницы запрещен если данные сессии недоступны

Завершение сессий

Теперь, когда мы разобрались в том, как осуществляется защита содержимого страниц, самое время рассмотреть операцию закрытия сессии. Операция закрытия сессии реализована в сценарии с именем logout.php . Сценарий вызывает функцию session_destroy() , которая удаляет куки и переменные сессии. Страница завершения сессии показана на Рисунке 8. Сценарий завершения сессии выглядит следующим образом:

Good Bye %s!


Go to login page!
Get Statusn",$_SESSION["username"]); session_destroy(); ?>


Рисунок 8: Страница завершения сессии

Мы можем проверить, действительно ли сессия завершилась, вызвав файл status.php и узнав в том, доступны ли все еще имя и идентификатор пользователя. Сообщение, приведенное на Рисунке 9, указывает на то, что сессия недействительна. Имя и идентификатор пользователя недоступны после завершения сессии. Следовательно, включение сценария status.php в начало каждой защищенной страницы позволяет быть уверенным в том, что доступ к странице будет возможен только после корректного входа в систему, в противном случае все запросы с данным URL будут завершены в самом начале сценария.


Рисунок 9: Сообщение о состоянии сессии после ее завершения

Достоинства и недостатки механизма управления сессиями на основе кук

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

В следующей статье будет описан механизм управления сессиями на стороне сервера с использованием таблиц базы данных.

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

index.php dothings.php

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

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

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

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

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

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

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

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

Session_start();

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

Session_start(); ?> ... Для задания директории в которой будут сохраняться файлы сессий используется функция session_save_path() : session_save_path($_SERVER["DOCUMENT_ROOT"]."/session"); session_start();

После начала сессии можно задавать глобальные переменные. Ари присвоении какого-либо значения любому полю массива $_SESSION, переменная с таким же именем автоматически регистрируется, как переменная сессии. Этот массив доступен на всех страницах, использующих сессию. Для примера разберем програму:

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

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

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

А второй "dothings.php" вот это:

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

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

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

  • unset($_SESSION["a"]) - сессия "забывает" значение заданной сессионой переменной;
  • session_destroy () - сессия уничтожается (например, если пользователь покинул систему, нажав кнопку "выход");
  • session_set_cookie_params (int lifetime [, string path [, string domain]]) - с помощью этой функции можно установить, как долго будет "жить" сессия, задав unix_timestamp определяющий время "смерти" сессии. По умолчанию, сессия "живёт" до тех пор, пока клиент не закроет окно браузера.
  • session_write_close () - запись переменных сесии и закрытие ее. Это необходимо для открытия сайта в новом окне, если страница выполняет длительную обработу и заблокировала для вашего браузера файл сессий.

Примеры

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

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

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

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

Примеры: index.php Вводи пароль

Логин:
Пароль:
authorize.php страницу... header("Location: secretplace.php"); exit; } } // если что-то было не так, то пользователь получит // сообщение об ошибке. ?> Вы ввели неверный пароль! secretplace.php Привет, , ты на секретной странице!!! :)

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

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

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

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

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

  • Файл authorize.php - попытка подбора пароля с помощью стороннего скрипта;
  • Файл secretplace.php - попытка обмануть программу путём вписывания значений переменной $logged_user в адресной строке браузера, например так:
    "http://www.yoursite.ru/secretplace.php?logged_user=hacker "

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

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

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

authorize.php V2 страницу... header("Location: secretplace.php"); exit; } } } ?> Вы ввели неверный пароль!
Как избавиться от "дыры" номер 2?

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

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

secretplace.php V2 переменную unset($_SESSION["logged_user"]); // открываем сессию session_start(); /* просто зайти на эту страницу нельзя... если имя пользователя не зарегистрировано, то перенаправляем его на страницу index.php для ввода логина и пароля... тут на самом деле можно много чего сделать, например запомнить IP пользователя, и после третьей попытки получить доступ к файлам, его перекрыть. */ if(!isset($_SESSION["logged_user"])){ header("Location: index.php"); exit; } ?> Привет, , ты на секретной странице! Итоги

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

Примеры

Автоматическая вставка SID в форму. ini_set("session.use_trans_sid", true); session_start(); ?>
?>
// Автоматическая вставка SID в ссылки. ini_set("session.use_trans_sid", true); session_start(); ?> Click here!
Click here!!

// Пример работы с сессиями. session_start(); // Если на сайт только-только зашли, обнуляем счетчик. if (!isset($_SESSION["count"])) $_SESSION["count"] = 0; // Увеличиваем счетчик в сессии. $_SESSION["count"] = $_SESSION["count"] + 1; ?>

Счетчик

раз(а).
Закройте браузер, чтобы обнулить счетчик.
" target="_blank"> Открыть дочернее окно браузера.
// Простой пример использования сессий без Cookies. session_name("test"); session_start(); $_SESSION["count"] = @$_SESSION["count"] + 1; ?>

Счетчик

В текущей сессии работы с браузером Вы открыли эту страницу раз(а).
Закройте браузер, чтобы обнулить этот счетчик.
?">Нажмите сюда для обновления страницы!

Мы рассмотрели принцип работы с куки в PHP. Назначение сессий практически такое же - сохранение некоторых данных после завершения работы скрипта для последующего использования. Однако, у двух этих методов есть существенное различие. Куки хранятся на стороне клиента и поэтому пользователь может без труда прочитать и даже изменять их. Допустим, на нашем сайте существует форма авторизации. Войдя под администраторской учетной записью пользователь получает дополнительные привилегии на сайте, к примеру, доступ к администраторской панели. Чтобы после каждого перехода пользователю не вводить заново пароль, необходимо каким-то образом запомнить факт того, что данный пользователь авторизован с административными правами. Используя куки задача решается следующим образом: после удачной авторизации пользователю устанавливается некая куки admin=1 . При серфинге по страницам сайта проверяемся, есть ли у пользователя данная куки. Если есть, то пользователь - администратор. С точки зрения безопасности такое решение является в корне неправильным, так как любой посетитель может самостоятельно добавить в своем браузере куки admin=1 и автоматически стать администратором на вашем сайте. Используя сессии такой номер не пройдет, потому что сессионные параметры хранятся на стороне веб-сервера.

Рассмотрим принцип работы сессий в PHP. Для инициализации новой или возобновления ранее созданной необходимо вызвать PHP-функцию session_start() . Данную функцию необходимо вызывать до вывода контента в коде. Сессионные параметры находятся в глобальном массиве $_SESSION . Рассмотрим пример:

session_start () ; if ( ! isset ($_SESSION [ "test" ] ) ) { echo "Сохраняем значение...
"
; $_SESSION [ "test" ] = "Hello, world" ; } echo $_SESSION [ "test" ] ;

При первом выполнении скрипта создастся новая сессия функцией session_start() . Условие выполнится, так как сессионной переменной test не существует и при этом будет создана данная сессионная переменная. При последующих выполнениях функция session_start() будет возобновлять ранее созданную сессию, условие выполняться не будет, а будет лишь выводиться значение переменной test.

При создании сессии PHP создает специальный временный файл, в котором хранит сессионные переменные. Для каждой сессии создается отдельный файл. Чтобы определить, какая сессия к какому клиенту относится, при создании сессии клиенту устанавливается куки вида:

PHPSESSID=

Это так называемый идентификатор сессии . Существует также способ передачи идентификатора сессии в GET-параметрах, но данный способ я считаю слишком устаревшим. Имя параметра можно изменить в конфигурационном файле PHP php.ini:

Session.name = PHPSESSID

По умолчанию сессия уничтожается при закрытии клиентом браузера. Но можно задать и конкретное время жизни сессии в php.ini (указывается время в секундах):

Session.cookie_lifetime = 0

В скрипте сессию можно принудительно уничтожить функцией session_destroy() .

И напоследок рассмотрим еще один пример. Небольшой скрипт, эмулирующий авторизацию пользователя на сайте. Формы ввода логина и пароля нет, есть просто две кнопки "войти" и "выйти".

//--Создаем новую либо возобновляем созданную сессию session_start () ; //--Процедуры входа и выхода if ( isset ($_POST [ "do" ] ) ) { if ( $_POST [ "do" ] == "login" ) { $_SESSION [ "logined" ] = "yes" ; header ("location: " . $_SERVER [ "PHP_SELF" ] ) ; exit () ; } if ( $_POST [ "do" ] == "logout" ) { session_destroy () ; header ("location: " . $_SERVER [ "PHP_SELF" ] ) ; exit () ; } } //--Форма авторизации $login_form = << END ; //--Форма выхода $logout_form = << END ; //--Если пользователь авторизован if ( isset ($_SESSION [ "logined" ] ) && $_SESSION [ "logined" ] == "yes" ) { echo "Приветствуем Вас, авторизованный пользователь!
"
; //--Выводим форму выхода echo $logout_form ; } else { echo "Приветствуем Вас, гость!
"
; //--Выводим форму выхода echo $login_form ; } ?>
Похожие статьи