Введение
Как устроены, и как работают сессии?
Область применения.
Возможные проблемы и их устранение.
Безопасность
Дополнительная информация:
Пример авторизации с помощью сессий
ОПС! Очень Полезные Ссылки:
Comments
Введение
Сессии – это на самом деле очень просто.
Надо только понимать, для чего они нужны и как устроены.
Ответим сначала на первый вопрос.
Как показано в соответствующем разделе этого FAQ, веб-сервер не поддерживает постоянного соединения с клиентом, и каждый запрос обрабатывается, как новый, безо всякой связи с предыдущими.
То есть, нельзя ни отследить запросы от одного и того же посетителя, ни сохранить для него переменные между просмотрами отдельных страниц. Вот для решения этих двух задач и были изобретены сессии.
Собственно, сессии, если в двух словах – это механизм, позволяющий однозначно идентифицировать браузер и создающий для этого браузера файл на сервере, в котором хранятся переменные сеанса.
Подробно расписывать нужду в таком механизме я не буду. Это такие хрестоматийнык случаи, как корзина покупок в е-магазине, авторизация, а так же, и не совсем тривиальные проблемы, такие, например, как защита интерактивных частей сайта от спама.
В принципе, довольно несложно сделать собственный аналог сессий, не такой функциональный, как встроенный в PHP, но похожий по сути. На куках и базе данных.
При запросе скрипта смотрим, пришла ли кука с определенным именем. Если куки нет, то ставим ее и записываем в базу новую строку с данными пользователя. Если кука есть, то читаем из базы данные. Еще одним запросом удаляем из базы старые записи и вот у нас готов механизм сессий. Совсем несложно. Но есть некоторые нюансы, которые делают предпочтительным использование именно встроенного механизма сессий.
Как устроены, и как работают сессии?
Для начала надо как-то идентифицировать браузер. Для этого надо выдать ему уникальный идентификатор и попросить передавать его с каждым запросом. Стыдно признаться, но когда я впервые узнал о сессиях, я думал, что это какой-то особый механизм, некий новый способ общения браузера с сервером – “сессии”. Что идентификатор сессии передается каким-то особым образом. Разочарование было жестоким.
Сессии используют стандартные, хорошо известные способы передачи данных. Собственно, других-то просто и нет.
Идентификатор – это обычная переменная. По умолчанию ее имя – PHPSESSID.
Задача PHP отправить ее браузеру, чтобы тот вернул ее со следующим запросом. Из уже упоминавшегося раздела FAQ ясно, что переменную можно передать только двумя способами: в куках или POST/GET запросом.
PHP использует оба варианта.
За это отвечают две настройки в php.ini:
session.use_cookies
– если равно 1, то PHP передает идентификатор в куках, если 0 – то нет.
session.use_trans_sid
если равно 1, то PHP передает его, добавляя к URL и формам, если 0 – то нет.
Менять эти и другие параметры сессий можно так же, как и другие настройки PHP – в файле php.ini, а так же с помощью команды ini_set()
или в файлах настройки веб-сервера
Если включена только первая, то при старте сессии (при каждом вызове session_start()
) клиенту устанавливается кука. Браузер исправно при каждом следующем запросе эту куку возвращает и PHP имеет идентификатор сессии. Проблемы начинаются, если браузер куки не возвращает. В этом случае, не получая куки с идентификатором, PHP будет все время стартовать новую сессию, и механизм работать не будет.
Если включена только вторая, то кука не выставляется. А происходит то, ради чего, в основном, собственно, и стоит использовать встроенный механизм сессий. После того, как скрипт выполняет свою работу, и страница полностью сформирована, PHP просматривает ее всю и дописывает к каждой ссылке и к каждой форме передачу идентификатора сессии. Это выглядит примерно так:
<a href="/index.php">Index</a>
превращается в
<a href="/index.php?PHPSESSID=9ebca8bd62c830d3e79272b4f585ff8f">Index</a>
а к формам добавляется скрытое поле
<input type="hidden" name="PHPSESSID" value="00196c1c1a02e4c37ac04f921f4a5eec" />
И браузер при клике на любую ссылку, или при нажатии на кнопку в форме, пошлет в запросе нужную нам переменную – идентификатор сессии!
По очевидным причинам идентификатор добавляется только к относительным ссылкам.
Теоретически, в наших с вами самодельных сессиях на куках и базе, можно самому, руками приписать ко всем ссылками передачу ид – и тогда наши собственные сессии будут работать независимо от кук. Но, согласитесь – приятнее, когда эту работу делает кто-то другой? 😉
По умолчанию в последних версиях PHP включены обе опции. Как PHP поступает в этом случае? Кука выставляется всегда. А ссылки автодополняются только если РНР не обнаружил куку с идентификатором сессии. Когда пользователь в првый раз за этот сеанс заходит на сайт, ему ставится кука, и дополняются ссылки. При следующем запросе, если куки поддерживаются, PHP видит куку и перестает дополнять ссылки. Если куки не работают, то PHP продолжает исправно добавлять ид к ссылкам, и сессия не теряется.
Пользователи, у которых работают куки, увидят длинную ссылку с ид только один раз.
Фух. С передачей идентификатора закончили.
Теперь осталось привязать к нему файл с данными на стороне сервера.
PHP это сделает за нас. Достаточно просто написать
1 2 |
session_start(); $_SESSION['test']='Hello world!'; |
И PHP запишет в файл, связанный с этой сессией, переменную test.
Здесь очень важное замечание.
Массив $_SESSION
– особенный.
В нем, собственно, и находятся переменные, которые мы ходим сделать доступными в различных скриптах.
Чтобы поместить переменную в сессию, достаточно присвоить ее элементу массива $_SESSION.
Чтобы получить ее значение – достаточно обратиться к тому же элементу. Пример будет чуть ниже.
Cборкой мусора – удалением устаревших файлов PHP тоже занимается сам. Как и кодированием данных и кучей всяких других нужных вещей. В результате этой заботы работа с сессиями оказывается очень простой.
Вот мы, собственно, и подошли к примеру работы сессий.
Пример очень маленький:
1 2 3 4 5 6 |
<? session_start(); if (!isset($_SESSION['counter'])) $_SESSION['counter']=0; echo "Вы обновили эту страницу ".$_SESSION['counter']++." раз. "; echo "<br><a href=".$_SERVER['PHP_SELF'].">обновить"; ?> |
Мы проверяем, есть ли у нас в сессии переменная counter, если нет, то создаем ее со значением 0, а дальше выводим ее значение и увеличиваем на единицу. Увеличенное значение запишется в сессию, и при следующем вызове скрипта переменная будет иметь значение 1, и так далее.
Все очень просто.
Для того, чтобы иметь доступ к переменным сессии на любых страницах сайта, надо написать ТОЛЬКО ОДНУ(!) строчку в самом начале КАЖДОГО файла, в котором нам нужны сессии:
1 |
session_start(); |
И далее обращаться к элементам массива $_SESSION. Например, проверка авторизации будет выглядеть примерно так:
1 2 3 4 5 |
session_start(); if ($_SESSION['authorized']<>1) { header("Location: /auth.php"); exit; } |
Удаление переменных из сессии.
Если у вас register_globals=off
, то достаточно написать
1 |
unset($_SESSION['var']); |
Если же нет, то тогда рядом с ней надо написать
1 |
session_unregister('var'); |
Область применения.
Очень важно понимать, для чего сессии стоит использовать, а для чего – нет.
Во-первых, помните, что сессии можно применять только тогда, когда они нужны самому пользователю, а не для того, чтобы чинить ему препятствия. Ведь он в любой момент может избавиться от идентификатора!
Скажем, при проверке на то, что заполняет форму человек, а не скрипт, пользователь сам заинтересован в том, чтобы сессия работала – иначе он не сможет отправить форму! А вот для ограничения количества запросов к скрипту сессия уже не годится – злонамеренный скрипт просто не будет возвращать идентификатор.
Во-вторых. Важно четко себе представлять тот факт, что сессия – это сеанс работы с сайтом, так как его понимает человек. Пришел, поработал, закрыл браузер – сессия завершилась. Как сеанс в кино. Хочешь посмотреть еще один – покупай новый билет. Стартуй новый сеанс. Этому есть и техническое объяснение. Гарантированно механизм сессий работает только именно до закрытия браузера. Ведь у клиента могут не работать куки, а в этом случае, естественно, все дополненные идентификатором ссылки пропадут с его закрытием.
Правда, сессия может пропасть и без закрытия браузера. В силу ограничений, рассмотренных в самом главном разделе этого FAQ, механизм сессий не может определить тот момент, когда пользователь закрыл браузер. Для этого используется таймаут – заранее определенное время, по истечении которого мы считаем, что пользователь ушел с сайта. По умолчанию этот параметр равен 24 минутам.
Если вы хотите сохранять пользовательскую информацию на более длительный срок, то используйте куки и, если надо – базу данных на сервере. В частности, именно так работают все популярные системы авторизации:
– по факту идентификации пользователя стартует сессия и признак авторизованности передается в ней.
– Если надо “запомнить” пользователя, то ему ставится кука, его идентифицирующая.
– При следующем заходе пользователя на сайт, для того, чтобы авторизоваться, он должен либо ввести пароль, либо система сама его опознает по поставленной ранее куке, и стартует сессию. Новую сессию, а не продолжая старую.
В-третьих, не стоит стартовать сессии без разбору, каждому входящему на сайт. Это создаст совершенно лишнюю нагрузку. Не используйте сессии по пустякам – к примеру, в счетчиках. То, что спайлог называет сессиями, считается, конечно же, на основе статистики заходов, а не с помощью механизма сессий, аналогичного пхп-шному.
К тому же, возьмем поисковик, который индексирует ваш сайт. Если поисковый робот не поддерживает куки, то пхп по умолчанию будет поставлять к ссылкам PHPSESSID, что – согласистесь – может не сильно понравится поисковику, который, по слухам, и так-то динамические ссылки не жалует, а тут вообще при каждом заходе – новый адрес!
Если сессии используются для ограничения доступа к закрытому разделу сайта, то все просто поисковик и не должен его индексировать.
Если же приходится показывать одну и ту же страницу как авторизованным, так и не авторизованным пользователям, то тут поможет такой трюк – стартовать сессию только тем, кто ввел пароль, или тем, у кого уже стартовала сессия.
Для этого в начало каждой страницы вместо просто session_start()
пишем
1 |
if (isset($_REQUEST[session_name()])) session_start(); |
таким образом, Мы стартуем сессию только тем, кто прислал идентификатор.
Соответственно, надо еще в первый раз отправить его пользователю – в момент авторизации.
Если имя и проль верные – пишем session_start()
!
Возможные проблемы и их устранение.
Самыми распространенными ошибками, которые выдает РНР при попытке работать с сессиями, являются такие:
Две из них,
Warning: Cannot send session cookie - headers already sent
Warning: Cannot send session cache limiter - headers already sent
вызваны одной и той же причиной, решение описано в этом факе здесь
Третья,
Warning: open(/tmp\sess_SID, O_RDWR) failed: No such file or directory (2) in full_script_path on line number
(ранее она выглядела, как Warning: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/tmp)
),
если перевести ее с английского, подробно объясняет проблему: недоступен указанный в php.ini путь к каталогу, в который пишутся файлы сессий. Эту ошибку исправить проще всего. Просто прописать каталог, который существует, и доступен на запись, например,
session.save_path = c:\windows\temp
И не забыть перезагрузить апач после этого.
Как выясняется, сообразительность людская не имеет пределов, и поэтому я вынужден пояснить:
сообщение о третьей ошибке (невозможно найти каталог) НЕИЗБЕЖНО приведет к появлению первых двух, поскольку сообщение об ошибке – это вывод в браузер и после него заголовками пользоваться нельзя. Поэтому не спешите искать преждевременный вывод, а сначала пропишите правильный путь!
Следующей по распространенности проблемой при работе с сессиями является тяжелое наследие register_globals. НЕ давайте переменным скрипта имена, совпадающие с индексами массива $_SESSION!
При register_globals=on значения будут перезаписывать друг друга, и вы запутаетесь.
А при register_globals=off появится другая ошибка: “Your script possibly relies on a session side-effect which existed until PHP 4.2.3.”, в случае, если в скрипте есть переменная сессии не имеющая значения, и глобальная переменная с тем же именем. Чтобы от неё избавиться, надо всегда инициализировать переменные перед использованием (или хотя бы проверять на существование) и не давать глобальным переменным имена, совпадающие с индексами массива $_SESSION.
Если не работает, но и никаких сообщений не выводится, то добавьте в самое начало скрипта две строчки, отвечающие за вывод ВСЕХ ошибок на экран – вполне возможно, что ошибки есть, но вы их просто не видите.
1 2 |
ini_set('display_errors',1); error_reporting(E_ALL); |
или смотрите ошибки в error_log. Вообще, тема отображения сообщений об ошибках выходит за рамки данной статьи, поэтому просто убедитесь хотя бы, что вы можете их видеть. Чуть продробнее о поиске ошибок можно прочитать в этом разделе.
Если вы уверены, что ошибок нет, но приведенный пример не работает все равно, то, возможно, в PHP не включена передача ид через урл, а куки по каким-то причинам не работают.
Смотрите, что у вас с куками.
Вообще, если у вас “не работают” сессии, то сначала попробуйте передать идентификатор сессии руками, то есть, сделать ссылку и приписать к ней идентификатор:
1 2 3 4 5 6 |
<? session_start(); if (!isset($_SESSION['counter'])) $_SESSION['counter']=0; echo "Вы обновили эту страницу ".$_SESSION['counter']++." раз.<br> <a href=".$_SERVER['PHP_SELF'].'?'.session_name().'='.session_id().">обновить</a>"; ?> |
При этом следует убедиться, что не включена директива session.use_only_cookies
, которая запрещает PHP принимать идентификатор сессии, если он был передан через URL
Если этот пример не заработает, то проблема либо в банальных опечатках (половина “проблем” с сессиями происходит от неправильно написанного имени переменной), либо в слишком старой версии PHP: поддержка сессий появилась в версии 4.0, а массив $_SESSION
– в 4.1 (До этого использовался $HTTP_SESSION_VARS
).
Если же заработает – то проблема в куках. Отслеживайте – что за куку ставит сервер браузеру, возвращает ли браузер ее. Искать очень полезно, просматривая просматривая обмен HTTP-заголовками между браузером и сервером.
Объяснение принципа работы кук выходит за рамки этого и так уж слишком большого текста, но хотя бы убедитесь, что сервер куку с идентификатором посылает, а браузер – возвращает. И при этом идентификаторы совпадают друг с другом =)
Установка куки должна выглядеть, как
Set-Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6;
или как
Set-Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6; path=/
(если вы запрашиваете скрипт не из корневого каталога)
Ответ сервера должен выглядеть, как
Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6
либо
Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6; b=b
если браузер возвращает другие куки, кроме идентификатора сессии.
Если браузер куки не возвращает – проверьте, работают ли куки вообще.
Убедитесь, что домен, к которому вы обращаетесь, имеет нормальное имя (в котором есть хотя бы одна точка и не содержится запрещенных символов, например подчеркивания) и почистите кэш браузера – это две основные причины, по которм куки могут не работать.
Если пример отсюда работает, а ваш собственный код – нет, то проблема, очевидно, не в сессиях, а в алгоритме. Ищите, где потеряли переменную, по шагам переносите пример отсюда, отлаживайте свой скрипт.
Еще одна проблема может возникнуть, если вы используете перенаправление через header или навигацию с помощью JavaScript.
Дело в том, что РНР автоматически дописывает идентификатор сессии только к ссылкам вида <a href=>
, но не делает этого для header-ов, яваскрипта, мета-тегов.
Поэтому надо добавлять идентификатор руками, например, так:
1 |
header("Location: /script.php?".session_name().'='.session_id()); |
Следует помнить, что пхп лочит файл сессии. То есть, если один ваш скрипт стартует сессию и долго выполняется, а другой пытается в это время стартовать её с тем же идентификатором, то он зависнет. Поэтому в долго выполняющихся скриптах следует стартовать сессию только тогда, когда она нужна, и тут же закрывать её, с помощью session_write_close()
Так же, весьма редкая, и совершенно непонятно, откуда появляющаяся, проблема бывает в том, что настройка session.save_handler имеет значение, отличное от files. Если это не так – исправляйте.
Безопасность
Безопасность сессий – тема обширная. Поэтому остановлюсь на нескольких основных моментах.
Самый хрестоматийный – не передавать идентификатор через адресную строку. Об этом написано даже в php.ini, но это ограничивает функциональность сессий. Если вы решите последовать этому совету, то кроме session.use_trans_sid = 0 не забудьте session.use_only_cookies = 1
Желательно привязывать сессию к IP адресу: таким образом, если идентификатор будет украден, то злодей все равно не сможет им воспользоваться в большинстве случаев.
Рекомендуется пользоваться директивой session.save_path, с помощью которой задать собственный каталог для сохранения файлов сессий. Это более безопасно, чем когда они хранятся в общем временном каталоге сервера по умолчанию.
- Кроме кук, механизм сессий посылает еще и заголовки, запрещающие кэширование страниц (тот самый cache limiter). Для html это правильно и необходимо. Но вот когда вы пытаетесь скриптом, проверяющим авторизацию, отдать файл, то интернет эксплорер отказывается его скачивать. Именно из-за этого заголовка. Вызов
1 |
session_cache_limiter("private"); |
- перед стартом сессии должен решить проблему.
- Как это ни кажется странным, но в массиве
$_SESSION
нельзя использовать числовые индексы –$_SESSION[1], $_SESSION['10']
– cессии работать не будут. - Где-то между версиями 4.2 и 5.0 невозможно было установить
session.use_trans_sid
с помощьюini_set()
. Начиная с 5.0 уже можно снова. - До версии 4.3.3 куку PHP отправлял куку только если при старте сессии в запросе отсутстввал идентификатор. Теперь же кука посылается при каждом вызове
session_start()
Пример авторизации с помощью сессий
Проиллюстрируем все вышенаписанное небольшим примером:
создадим файл auth.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<? if (isset($_POST['auth_name'])) { $name=mysql_real_escape_string($_POST['auth_name']); $pass=mysql_real_escape_string($_POST['auth_pass']); $query = "SELECT * FROM users WHERE name='$name' AND pass='$pass'"; $res = mysql_query($query) or trigger_error(mysql_error().$query); if ($row = mysql_fetch_assoc($res)) { session_start(); $_SESSION['user_id'] = $row['id']; $_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; } header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); exit; } if (isset($_GET['action']) AND $_GET['action']=="logout") { session_start(); session_destroy(); header("Location: http://".$_SERVER['HTTP_HOST']."/"); exit; } if (isset($_REQUEST[session_name()])) session_start(); if (isset($_SESSION['user_id']) AND $_SESSION['ip'] == $_SERVER['REMOTE_ADDR']) return; else { ?> <form method="POST"> <input type="text" name="auth_name"><br> <input type="password" name="auth_pass"><br> <input type="submit"><br> </form> <? } exit; ?> |
теперь достаточно написать во всех защищаемых скриптах строчку
require “auth.php”;
ОПС! Очень Полезные Ссылки:
http://www.php.net/manual/ru/ref.session.php – самая последняя и свежая информация о поддержке сессий в PHP в официальной документации, плюс многочисленные комментарии пользователей. Настоятельно рекомендуется к прочтению.
http://phpclub.ru/manrus/f/ref.session.html – ВЕСЬМА устаревший перевод этой главы на русский, из документации в переводе Александра Пирамидина.
http://phpclub.ru/detail/article/sessions
Статья с пафосным названием “Правда о сессиях”. Двойственное впечатление оставляет. Вначале автор ОЧЕНЬ доступно рассказывает о механизме сессий, но методы, которые он предлагает к концу статьи – совершенно мутные.
Хрестоматийная статья Дмитрия Бородина с сайта
http://php.spb.ru/ настоятельно НЕ рекомендуется.
Ребята, она страшно устарела. Мало того, что в ней есть фактические неточности, так с сессиями в PHP уже давно просто не работают.
Огромное Диме спасибо за нее, это была первая статья по сессиям на русском языке, я сам по ней учился, но сейчас надо ее отправить на заслуженный отдых.
Так же, устарели к сожалению, и многие другие статьи, лежащие в интернете и не обновлявшиеся годами.
Источник: http://phpfaq.ru/sessions
https://corp2.info/razrabotka-i-sozdanie-sajtov-internet-magazinov-veb-proektov-kiev-1038.html
Leave a Reply