Php хранение данных в сессии. PHP сессии под скальпелем. Как работают PHP сессии изнутри. Можно ли написать собственный механизм сессий
Сессии в PHP представляют из себя механизм сохранения на стороне сервера информации о компьютере клиента. На самом деле сессии в PHP - это не такая сложная тема, но для её понимания нужно знать принцип работы cookie в PHP . Так что, если вы не знаете как работают cookie в PHP, то сначала читайте соответствующую статью, а потом уже возвращайтесь сюда.
Слово session с английского переводится как сеанс, так сам смысл сессий в PHP становится более понятным, но у программистов прижился термин "сессии", его и мы будем использовать в этой статье.
Сессии в PHP очень похожи на механизм cookie, те же самые пары ключ => значение, только они хранятся на стороне сервера.
Функция session_start()
Сеанс нам нужно начать, для этого существует функция session_start() . Эта функция стартует сеанс, или сессию, как угодно можно назвать это.
Функция session_start() желательно вызывать в самом начале страницы, но в моих примерах я этого не делаю.
Массив $_SESSION
Сессии - это группы переменных, которые хранятся на сервере, но относятся к одному уникальному посетителю. Повторю, это ключевой момент: сессии хранятся на сервере .
Для того, чтобы обеспечить взаимодействие каждого посетителя с его данными из его сессии используется файл cookie, команду создать который PHP даёт сам, вам об это беспокоится не нужно. Этот cookie имеет значение только для сервера и не может быть использован для получения данных о пользователе.
На сервере данные сессии хранятся в текстовом файле и они доступны в программе PHP в массиве $_SESSION . Чтобы сохранить переменную в сессии нужно присвоить ей значение в этом массиве.
Давайте наконец начнём использовать примеры. Всё очень просто.
Теперь попробуем получить значение из массива $_SESSION в другом примере.
Обратите внимание, если во втором примере мы удалим функцию session_start() то у нас не будет доступа к данным массива $_SESSION .
Функция session_id()
После того, как сессия создана, вы автоматически получаете доступ к уникальномы идентификатору сессии при помощи функции session_id() . Эта функция позволяет как задавать, так и получать значение идентификатора сессии.
Можете посмотреть в панели инструментов для разработчиков вашего браузера (в Chrome для этого нажмите Ctrl + Shift + I, потом Resources, и там найдёте cookie), этот домен положил вашему браузеру cookie с именем PHPSESSID и примерно таким значением: "7g5df9rkd1hhvr33lq1k6c72p7".
Именно по значению PHPSESSID сервер будет определять ваш браузер и работать с соответствующим набором переменных, которые будут доступны скрипту через массив $_SESSION, как уже писалось ранее.
Функция session_name()
Если функции session_id() позволяет получать значение идентификатора сессии, функция session_name() позволяет узнать имя сессии.
Ещё раз про функцию session_start()
Теперь мы знаем больше про мешанизм работы сессий в PHP и нужно ещё раз вернуться к функции session_start() . Эта функция инициализирует механизм сессий для текущего пользователя. Как именно это происходит:
- Если пользователь запустил сайт впервые, то session_start() устанавливает cookie у клиента и создаёт временное хранилище на сервере, связанное с идентификатором пользователя.
- Определяет хранилище, связанное с переданным текущим идентификатором.
- Если в хранилище на сервере есть данные, они помещаются в массив $_SESSION.
- Если register_globals из файла php.ini равен On, то все элементы массива $_SESSION превращаются в глобальные переменные.
Пример использования сессии
Сейчас мы рассмотрим пример, который позволит провести небольшие эксперименты с сессиями.
Счётчик
В текущей сессии вы открыли страницу =$_SESSION["count"]?> раз.
Открыть пример в ">этой вкладке.
Вся работа сессий основана на массиве $_SESSION , это хорошо видно в данном примере.
Если закрыть окно браузера, то сессия прекратится, наш счётчик обнулится. Такое поведение сессий в PHP можно изменить, к этому вопросу мы вернёмся чуть дальше в статье.
Завершение сессии
Для того, чтобы завершить сессию нам нужно:
- Очистить массив $_SESSION.
- Удалить временное хранилище на сервере.
- Удалить сессионный cookie.
Очистить массив $_SESSION можно при помощи функции session_unset() .
Функция session_destroy() удаляет временное хранилище на сервере. Кстати, она больше ничего не делает.
Удалить сессионный cookie нужно при помощи функции setcookie() , которую мы изучили в уроке pабота с cookie в PHP .
Пример завершения сессии:
Сессия завершена.
Теперь можете провести эксперимент: запустить в одном окне пример со счётчиком, накрутить счётчик, а потом запусть пример с удалением сессии и снова обновить страницу со счётчиком.
Удаление файла cookies можно сделать так:
setcookie(session_name(), "", time() - 60*60*24*32, "/")
Ещё раз про функции session_name() и session_id()
Функции session_name() и session_id() на практике используются редко, но я о них пишу, так как в статье нужно раскрыть сам механизм работы сессий в PHP.
При помощи этих функций можно задавать собственные имена и идентификаторы сессий, но делать это не рекомендуется. Если вы захотели задать их, то пропишите эти функции с аргументами перед функцией session_start() , как в примере ниже:
При использовании данного примера всем пользователям будет назначен один и тот же идентификатор сессии.
Тут подробней остановимся, если вы запустите пример из секции про функцию session_name() (вот ссылка) в разных браузерах (например в Chrome и в Internet Explorer), то в каждом браузере будет свой, уникальный идентификатор сессии. Браузеры хранят файлы cookies каждый в своей папке, поэтому функция session_start() даст каждому браузеру создать свой, уникальный идентификатор и, соответственно, для каждого браузера будет создано уникальное хранилище на сервере. Поэтому пример со счётчиком (этот) в каждом браузере будет работать независимо друг от друга.
Если задать одинаковый идентификатор сессии для всех пользователей, то они будут работать с одним хранилищем на сервере. Вот пример счётчика, который будет считать посещения с разных браузеров:
100) { session_unset(); session_destroy(); } ?>
Счётчик №2
Открыли страницу в разных браузерах =$_SESSION["count"]?> раз.
Открыть пример в ">этой вкладке.
Если вы запустите этот пример, то не факт что вы увидите там единицу. Другие посетители могли уже изменить значения в хранилище сессий на сервере. Когда в этом случае сервер удаляет хранилище - я не знаю, поэтому при превышении счётчиком значения 100 буду завершать сессию.
Установка времени ожидания
По умолчанию, сессия "живёт" до тех пор, пока посетитель не закроет окно браузера. Это обусловлено тем, что функция session_start() ложит клиенту такой cookie.
Время жизни сессии можно изменить используя функцию session_set_cookie_params() , вот её синтаксис.
session_set_cookie_params (int lifetime [, string path [, string domain [, bool secure]]])
На практике достаточно использовать только первый параметр (lifetime), сюда записываете время в секундах, определяющее сколько сервер должен помнить состояние сессии после закрытия браузера.
Действие функции session_set_cookie_params() распространяется только на период работы скрипта.
Вот пример использования этой функции:
Счётчик №3
Значение счётчика: =$_SESSION["count"]?>.
Открыть счётчик в ">этой вкладке.
Накрутите счётчик и закройте браузер, через 30 сукунд опять откройте этот пример. Ваша сессия сохранится.
В Интернете можно найти тысячи туториалов о том, что такое сессии, для чего они нужны и как с ними работать. Но, к сожалению, прочитав их, всё равно остаётся множество вопросов. На мой взгляд, самый простой способ разобраться во всём — это посмотреть, как работают сессии изнутри. Т.е. изучить логи обмена браузера и веб-сервера, а также посмотреть, какие данные сохраняются на стороне клиента и на стороне сервера.
После этого многие моменты становятся куда более понятными, а сам механизм работы — более прозрачным.
Работу сессий будем изучать на следующем стандартном скрипте:
Он работает следующим образом:
Блок 1. Функция session_start() создаёт новую сессию или загружает старую, используя уникальный идентификатор сессии PHPSESSID.
Блок 2. Если удалось восстановить сессию, то значение $_SESSION["views"] увеличивается на единицу. Если нет — инициализируется единицей.
По идее, если в браузере включена поддержка кук, механизм должен работать и при каждом обновлении страницы значение счётчика будет увеличиваться на единицу.
Первая загрузка скрипта
Заголовки запроса
GET / HTTP/1.1 Host: firingrange.local User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Cache-Control: max-age=0Заголовки ответа
HTTP/1.1 200 OK Date: Thu, 29 Sep 2011 20:36:15 GMT Server: Apache/2.2.13 (Win32) PHP/5.2.10 X-Powered-By: PHP/5.2.10 Set-Cookie: PHPSESSID=k33en6ccgcia7125mitj5te4u6; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Content-Length: 58 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/htmlКомментарий
В исходном запросе браузер не идентифицировал себя никаким образом, поэтому механизм сессий PHP сгенерировал новый уникальный идентификатор сессии и скомандовал браузеру создать куку, в которой будет храниться этот самый идентификатор.
Сторона сервера
В результате работы скрипта на стороне сервера создаётся файл sess_k33en6ccgcia7125mitj5te4u6 следующего содержания:
Сторона клиента
На стороне клиента создаётся кука PHPSESSID, в которой хранится значение уникального идентификатора сессии.
Примечание. При настройках PHP по умолчанию, время жизни куки PHPSESSID — до закрытия браузера. Т.е. как только браузер будет закрыт, кука будет удалена, а соответственно будет потеряна сессия. Время жизни куки PHPSESSID можно менять, варьируя значение session.cookie_lifetime.
Результат работы скрипта
Вторая загрузка скрипта
Заголовки запроса
GET / HTTP/1.1 Host: firingrange.local User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Cookie: PHPSESSID=k33en6ccgcia7125mitj5te4u6 Cache-Control: max-age=0Заголовки ответа
HTTP/1.1 200 OK Date: Thu, 29 Sep 2011 20:49:41 GMT Server: Apache/2.2.13 (Win32) PHP/5.2.10 X-Powered-By: PHP/5.2.10 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Content-Length: 58 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/htmlКомментарий
Браузер отправляет веб-серверу куку PHPSESSID, используя которую PHP инициализирует массив $_SESSION значениями из файла sess_k33en6ccgcia7125mitj5te4u6. Соответственно, в блоке 2 отрабатывает ветка IF (прямая).
Сторона сервера
В результате работы скрипта содержимое файла sess_k33en6ccgcia7125mitj5te4u6 меняется:
Сторона клиента
На стороне клиента ничего не меняется.
Результат работы скрипта
Что дальше?
Последующие загрузки страницы до закрытия браузера будут работать по аналогии с тем, как работала вторая загрузка скрипта.
Т.к. время жизни куки было ограничено текущей сессией браузера, то после его закрытия уникальный идентификатор сессии будет утерян и при перезапуске процесс пойдёт по новой.
Тем не менее можно вернуться к сохранённой сессии, если явно указать PHPSESSID в качестве параметра скрипта:
Возвращение к сессии довольно условное, т.к. в результате работы скрипта в данном случае кука не создаётся. Заголовки ответа сервера:
HTTP/1.1 200 OK Date: Thu, 29 Sep 2011 21:01:52 GMT Server: Apache/2.2.13 (Win32) PHP/5.2.10 X-Powered-By: PHP/5.2.10 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Content-Length: 58 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html
Т.е. для поддержания работы именно с этой сессией ко всем ссылкам придётся приписывать?PHPSESSID=k33en6ccgcia7125mitj5te4u6.
Примечание. Можно указать PHP, чтобы уникальный идентификатор сессии передавался только через куку. Для этого нужно установить session.use_only_cookies в значение 1. В этом случае трюк, продемонстрированный выше, не пройдёт.
Если куки в браузере отключены, то можно передавать идентификатор сессии через параметры, как мы делали выше. Причём в PHP есть механизм, который будет сам дописывать нужный параметр в ссылки и добавлять скрытые поля в формы. Принцип работы точно такой же, как и с куками, поэтому не будем разбирать этот случай отдельно.
Небольшой вопросник (FAQ)
Где физически хранятся данные сессий?
Данные сессий хранятся на сервере. По умолчанию они записываются в файлы, но можно задать свой собственный механизм хранения данных сессий (например с использованием базы данных). Если хотите подробностей, смотрите функцию session_set_save_handler.
Кто генерирует уникальный идентификатор сессии?
Уникальный идентификатор сессии (PHPSESSID) генерирует сервер.
Можно ли написать собственный механизм сессий?
Да, это вполне возможно. Как видите, PHP не использует ничего сверхъестественного — идентификатор сохраняется между запросами с помощью кук, данные сессий хранятся в файлах на сервере.
Например, собственный механизм работы с сессиями есть в популярном фреймворке CodeIgniter .
Насколько безопасен механизм сессий?
Сессия идентифицируется только с помощью уникального идентификатора сессии, поэтому в общем случае злоумышленнику достаточно украсть его, чтобы запутать сервер. Возьмём тестовый скрипт, который мы использовали выше. Если обращение к нему будет с другого IP (по отношению к создавшему сессию), но PHPSESSID будет передаваться тот же самый, то сессия будет успешно восстановлена и счётчик будет увеличиваться с предыдущего сохранённого значения.
Обеспечивать дополнительную защиту придётся вам самим. Например:
- Можно сохранять в данных сессии IP и User-Agent клиента (будет храниться на стороне сервера), а затем при каждом обращении проверять, что актуальные значения совпадают с сохранёнными. В данном случае приходится искать компромисс между безопасностью и удобством работы пользователя.
К примеру, если у пользователя динамический IP и вы используете сессии для поддержания авторизации, но при этом проверяете совпадение IP, то при каждой смене адреса пользователю придётся заново вводить логин и пароль.
Точно также строка User-Agent может меняться при обновлении версии браузера или при установке некоторых плагинов.
- Одним из рекомендуемых механизмов защиты сессий является повторная генерация идентификатора при каждом обращении к скрипту (см. функцию session_regenerate_id). Посмотреть скрипт и алгоритм работы в разрезе можно ниже.
Примечание. Если верить обсуждению на официальном сайте , то при повторной генерации идентификатора могут возникнуть проблемы с параллельным доступом к данным.
Работа сессий с повторной генерацией идентификатора в разрезе
Скрипт
//
блок
1
session_start();
if
(isset
($_SESSION[
"initiated"
]))
session_regenerate_id();
else
$_SESSION[
"initiated"
] =
true
;
//
блок
2
if
(isset
($_SESSION[
"views"
]))
$_SESSION[
"views"
]++
;
else
$_SESSION[
"views"
] = 1;
//
блок
3
echo
"
<
body>
Количество
просмотров
: ".
$_SESSION[
"views"]."