Использование уникального uid в php сессии. Использование сессий - Работа с сессиями PHP

Мы рассмотрели принцип работы с куки в 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 ; } ?>

С помощью сессии PHP сервер идентифицирует вас и позволяет выполнять необходимые операции: изменение информации на различных веб-страницах, добавление новой информации и т.д. После завершения работы на сайте вы удаляете текущую сессию, нажав на кнопку «Выйти »:

Что такое сессия PHP?

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

Эта информация, хранимая на протяжении сессии, доступна для всех веб-страниц ресурса. На сервере расположение временного файла определяется параметром session.save_path в конфигурационном файле php.ini .

При создании PHP-сессии выполняются следующие три действия:

  • Когда создается сессия, PHP генерирует уникальный идентификатор, который представляет собой случайную строку из 32 шестнадцатеричных чисел. Идентификатор времени жизни сессии PHP выглядит примерно так: 9c8foj87c3jj973actop1re472e8774 ;
  • Сервер отправляет на компьютер пользователя куки, называемые PHPSESSID , для хранения строки уникального идентификатора сессии;
  • Сервер генерирует в указанном временном каталоге файл, который содержит имя уникального идентификатора сессии с префиксом sess _g. sess_9c8foj87c3jj973actop1re472e8774 .

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

Пользователь может завершить сеанс, нажав кнопку выхода из системы, которая вызывает функцию session_destroy() . Когда пользователь закрывает браузер, сессия PHP закрывается автоматически. Иначе сервер завершит сессию по истечении заданного периода времени.

Синтаксис сессий в PHP

При PHP авторизации через сессию она создается с помощью функции session_start() и удаляется с помощью функции session_destroy() . Глобальная переменная PHP , известная под именем $_SESSION , используется для установки значений переменных сессии. Сбросить все значения, установленные для переменных сессии, можно с помощью функции session_unset() .

Операции сессии

Мы рассмотрим следующие операции с использованием сессии PHP , а также их примеры.

  • Запуск сессии PHP и установка ее переменных сессии: новая сессия PHP запускается с помощью функции session_start() . После того, как сессия была создана, можно установить значения ее переменных сессии с помощью $_SESSION . Мы установили значения для переменных “userID ” — “php_user ” и “password ” — “tutorials ”:

PHP-сессии - создание Сессия PHP начата и переменные сессии заданы!"; ?>

Результат : в результате запуска приведенного выше PHP-кода на сервере будет выведено следующее сообщение:


  • Получение значений переменных сессии PHP : Можно получить значения переменных, которые мы установили во время последней PHP сессии авторизации. Когда мы открываем PHP-сессию в начале каждой страницы (session_start () ), должен указываться код, приведенный ниже. Мы извлекаем и выводим эти значения с помощью глобальной переменной $_SESSION :

PHP-сессия - получение значений
"; echo "Пароль - " . $_SESSION["password"] . "."; ?>

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


  • Обновление значений переменных сессии PHP : Во время сессии можно обновить значения ее переменных. Сначала нам нужно открыть PHP-сессию в начале каждой страницы (session_start () ). В приведенном ниже коде мы обновляем значения переменных “userID ” — “new_php_user ” и “password ” — “education ”.

Можно вывести массив переменных сессии и их значений с помощью функции print_r($ _SESSION) , как показано ниже:

PHP-сессия - изменение значений
"; print_r($_SESSION); ?>

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


  • Удаление сессии PHP и сброс всех значений переменных сессии: Можно сбросить сессию PHP с помощью функции session_unset() и удалить текущую сессию с помощью функции session_destroy() :

PHP-сессия - удаление
Сессия PHP и все переменные сессии были успешно удалены!

"; ?>

Результат: когда мы запустим на веб-сервере приведенный выше PHP-код , в результате он выведет следующее сообщение:


Заключение

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

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

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

Цель этой статьи - осветить подводные камни использования сессий в 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.

С самого начала 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(); ?> 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; ?>

Счетчик

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

Сессии позволяют установить связь между посетителем и сайтом при помощи идентификатора сессии .

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

Идентификатор сессии, хранящийся на компьютере пользователя, это файл cookie .

Cookie хранится в браузере пользователя, при этом соответствующий файл создается и на сервере.

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

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

// инициировать сессию session_start();

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

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

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

PHP

echo "Имя сессии: ".session_name(). " Идентификатор сессии: ".session_id(); // Имя сессии: PHPSESSID Идентификатор сессии: mceu820l02id3ds0vcvtgnht04

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

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

$_SESSION["nick"] = $_POST["name"];

Уничтожаем переменные сессии и саму сессию

1 . Удалить переменную сессии можно следующим образом:

Unset($_SESSION["nick"]);

Этим мы предотвратим повторное использование информации.

Но лучше просто очистить массив $_SESSION (удалить все переменные сессии):

//очистить массив $_SESSION $_SESSION = array();

2 . Также необходимо сделать следующее:

Установить файл cookie сессии недействительным:

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

Имя сессии ссылается на session id в куках и URL

3 . Уничтожить (завершить) сессиию

Session_destroy();

Буферизация вывода

Если у вас уже есть какой-либо вывод, вы можете воспользоваться фукцией ob_start() . Данная функция делает буферизацию вывода. Теперь вывод не будет отправлен браузеру, пока вы не отправите его принудительно при помощи функции ob_end_flush() .

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

С другой стороны, не всегда необходимо использовать функцию ob_end_flush() , так как интерпретатор PHP автоматически очистит буфер по выполнению сценария.

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

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

Механизм работы сессий


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

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

$_SESSION["start"] = time(); // временная метка авторизации пользователя $now = time(); // текущее время $time_limit = 1000; // максимально допустимое время бездействия пользователя в сек. if ($now > $_SESSION["start"] + $time_limit) { echo "Ваше время истекло"; //....... // и удаляем сессию и ее cookie } else { $_SESSION["start"] = time(); } // если порядок, обновляем

Как сделать время жизни сессии вечной?

Для того чтобы клиент оставался всегда залогиненным, не надо делать время жизни сессии вечной. Сессия - на то она и сессия, чтобы начаться, когда клиент пришел, и завершиться, когда клиент ушел.

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

Работа с сессией при отключенных куках

Если для настройки session.use_trans_sid установить 1 , то при отключенных куках PHP будет передавать PHPSESSID методом GET в строке запроса.