PHP Сесії. Детальний опис праці також пояснення механізму.

Вступ
Як влаштовані, і як працюють сесії?
Галузь застосування.
Можливі проблеми і їх усунення.
Безпека
Додаткова інформація:
Приклад авторизації за допомогою сесій
Коментарі

Вступ
Сесії - це насправді дуже просто.
Треба тільки розуміти, для чого вони потрібні і як влаштовані.
Відповімо спочатку на перше питання.
Як показано у відповідному розділі цього 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 () 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 це зробить за нас. Досить просто написати
session_start ();
$_SESSION [ 'test' ]= 'Hello world!' ;
І PHP запише в файл, пов'язаний з цією сесією, змінну test.
Тут дуже важливе зауваження.
Масив $_SESSION - особливий.
У ньому, власне, і знаходяться змінні, які ми ходимо зробити доступними в різних скриптах.
Щоб помістити змінну в сесію, досить привласнити її елементу масиву $ _SESSION.
Щоб отримати її значення - досить звернутися до того ж елементу. Приклад буде трохи нижче.

Збирання сміття - видаленням застарілих файлів PHP теж займається сам. Як і кодуванням даних і купою всяких інших потрібних речей. В результаті цієї турботи робота з сесіями виявляється дуже простий.
Ось ми, власне, і підійшли до прикладу роботи сесій.
Приклад дуже маленький:
<?
session_start
();
if (!isset(
$_SESSION [ 'counter' ])) $_SESSION [ 'counter' ]= 0 ;
echo
"Вы обновили эту страницу " . $_SESSION [ 'counter' ]++. " раз. " ;
echo
"<br><a href=" . $_SERVER [ 'PHP_SELF' ]. ">обновить" ;
?>
<?
session_start
();
if (!isset(
$_SESSION [ 'counter' ])) $_SESSION [ 'counter' ]= 0 ;
echo
"Вы обновили эту страницу " . $_SESSION [ 'counter' ]++. " раз. " ;
echo
"<br><a href=" . $_SERVER [ 'PHP_SELF' ]. ">обновить" ;
?>
Ми перевіряємо, чи є у нас в сесії змінна counter, якщо немає, то створюємо її зі значенням 0, а далі виводимо її значення і збільшуємо на одиницю. Збільшене значення запишеться в сесію, і при наступному виклику скрипта змінна буде мати значення 1, і так далі.
Все дуже просто.

Для того, щоб мати доступ до змінних сесії на будь-яких сторінках сайту, треба написати ТІЛЬКИ ОДНУ (!) Сходинку на самому початку КОЖНОГО файлу, в якому нам потрібні сесії:
session_start ();
І далі звертатися до елементів масиву $ _SESSION. Наприклад, перевірка авторизації буде виглядати приблизно так:
session_start ();
if (
$_SESSION [ 'authorized' ]<> 1 ) {
header ( "Location: /auth.php" );
exit;
}

Видалення змінних з сесії.
Якщо у вас register_globals=off , то досить написати
unset( $_SESSION [ 'var' ]); Якщо ж ні, то тоді поруч з нею треба написати
session_unregister ( 'var' );
Галузь застосування.
Дуже важливо розуміти, для чого сесії варто використовувати, а для чого - ні.

По-перше, пам'ятайте, що сесії можна застосовувати тільки тоді, коли вони потрібні самому користувачеві, а не для того, щоб чинити йому перешкоди. Адже він в будь-який момент може позбутися ідентифікатора!
Скажімо, під час перевірки на те, що заповнює форму людина, а не скрипт, користувач сам зацікавлений в тому, щоб сесія працювала - інакше він не зможе відправити форму! А ось для обмеження кількості запитів до скрипту сесія вже не годиться - зловмисний скрипт просто не буде повертати ідентифікатор.

По-друге. Важливо чітко собі уявляти той факт, що сесія - це сеанс роботи з сайтом, так як його розуміє людина. Прийшов, попрацював, закрив браузер - сесія завершилася. Як сеанс в кіно. Хочеш подивитися ще один - купуй новий квиток. Стартуй новий сеанс. Цьому є і технічне пояснення. Гарантовано механізм сесій працює тільки саме до закриття браузера. Адже у клієнта можуть не працювати куки, а в цьому випадку, природно, все доповнені ідентифікатором посилання пропадуть з його закриттям.
Правда, сесія може пропасти і без закриття браузера. В силу обмежень, розглянутих в найголовнішому розділі цього FAQ, механізм сесій не може визначити той момент, коли користувач закрив браузер. Для цього використовується таймаут - заздалегідь визначений час, після закінчення якого ми вважаємо, що користувач пішов з сайту. За замовчуванням цей параметр дорівнює 24 хвилинам.
Якщо ви хочете зберігати призначену для користувача інформацію на більш тривалий термін, то використовуйте куки і, якщо треба - базу даних на сервері. Зокрема, саме так працюють всі популярні системи авторизації:
- За фактом ідентифікації користувача стартує сесія і ознака авторизованого передається в ній.
- Якщо треба "запам'ятати" користувача, то йому ставиться кука, його ідентифікує.
- При наступному заході користувача на сайт, для того, щоб авторизуватися, він повинен або ввести пароль, або система сама його впізнає по поставленої раніше Кука, і стартує сесію. Нову сесію, а не продовжуючи стару.

По-третє, не варто стартувати сесії без розбору, кожному хто входить на сайт. Це створить абсолютно зайве навантаження. Не використовуйте сесії через дрібниці - наприклад, в лічильниках. Те, що спайлог називає сесіями, вважається, звичайно ж, на основі статистики заходів, а не за допомогою механізму сесій, аналогічного пхп-шному.
До того ж, візьмемо пошуковик, який індексує ваш сайт. Якщо пошуковий робот не підтримує куки, то пхп за замовчуванням буде поставляти до посилань PHPSESSID, що - согласістесь - може не сильно сподобається пошуковику, який, за чутками, і так-то динамічні посилання не шанує, а тут взагалі при кожному заході - нова адреса !
Якщо сесії використовуються для обмеження доступу в Закриту частину сайту, то все просто пошуковик і не повинен його індексувати.
Якщо ж доводиться показувати одну і ту ж сторінку як авторизованим, так і не авторизованим користувачам, то тут допоможе такий трюк - стартувати сесію тільки тим, хто ввів пароль, або тим, у кого вже стартувала сесія.
Для цього в початок кожної сторінки замість просто session_start () session_start () пишемо
if (isset( $_REQUEST [ session_name ()])) session_start (); таким чином, Ми стартуємо сесію тільки тим, хто надіслав ідентифікатор.
Відповідно, треба ще в перший раз відправити його користувачеві - в момент авторизації.
Якщо ім'я і пролити вірні - пишемо 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.

Якщо не працює, але і ніяких повідомлень не виводиться, то додайте в самий початок скрипта два рядки, що відповідають за виведення ВСІХ помилок на екран - цілком можливо, що помилки є, але ви їх просто не бачите.
ini_set ( 'display_errors' , 1 );
error_reporting ( E_ALL );
або дивіться помилки в error_log. Взагалі, тема відображення повідомлень про помилки виходить за рамки даної статті, тому просто переконайтеся хоча б, що ви можете їх бачити. Трохи продробнее про пошук помилок можна прочитати в цьому розділі .

Якщо ви впевнені, що помилок немає, але наведений приклад не працює все одно, то, можливо, в PHP не включена передача ід через урл, а куки з якихось причин не працюють.
Дивіться, що у вас з куками.
Взагалі, якщо у вас "не працюють" сесії, то спочатку спробуйте передати ідентифікатор сесії руками, тобто, зробити посилання і приписати до неї ідентифікатор:
<?
session_start
();
if (!isset(
$_SESSION [ 'counter' ])) $_SESSION [ 'counter' ]= 0 ;
echo
"Вы обновили эту страницу " . $_SESSION [ 'counter' ]++. " раз.<br>
<a href="
. $_SERVER [ 'PHP_SELF' ]. '?' . session_name (). '=' . session_id (). ">обновить</a>" ;
?>
<?
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=/ Set-Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6; path=/ (якщо ви запитуєте скрипт не з кореневого каталогу)
Відповідь сервера повинен виглядати, як
Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6 або
Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6; b=b Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6; b=b якщо браузер повертає інші куки, крім ідентифікатора сесії.

Якщо браузер куки не повертає - перевірте, чи працюють куки взагалі.
Переконайтеся, що домен, до якого ви звертаєтеся, має нормальне ім'я (в якому є хоча б одна точка і не міститься заборонених символів, наприклад підкреслення) і почистіть кеш браузера - це дві основні причини, по котрому куки можуть не працювати.

Якщо приклад звідси працює, а ваш власний код - немає, то проблема, очевидно, не в сесіях, а в алгоритмі. Шукайте, де втратили змінну, по кроках.

Ще одна проблема може виникнути, якщо ви використовуєте перенаправлення через header або навігацію за допомогою JavaScript.
Справа в тому, що РНР автоматично дописує ідентифікатор сесії тільки до посилань виду <a href=> , але не робить цього для header-ів, яваскрипт, мета-тегів.
Тому треба додавати ідентифікатор руками, наприклад, так:
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 це правильно і необхідно. Але от коли ви намагаєтеся скриптом, перевіряючим авторизацію, віддати файл, то інтернет експлорер відмовляється його завантажувати. Саме через це заголовка. виклик
    session_cache_limiter ( "private" ); перед стартом сесії має вирішити проблему.
  • Як це не здається дивним, але в масиві $_SESSION не можна використовувати числові індекси - $_SESSION [ 1 ], $_SESSION [ '10' ] $_SESSION [ 1 ], $_SESSION [ '10' ] - Сесія працювати не будуть.
  • Десь між версіями 4.2 і 5.0 неможливо було встановити session.use_trans_sid за допомогою ini_set () ini_set () . Починаючи з 5.0 вже можна знову.
  • До версії 4.3.3 куку PHP відправляв куку тільки якщо при старті сесії в запиті отсутстввал ідентифікатор. Тепер же кука надсилається при кожному виклику session_start () session_start ()

    Приклад авторизації за допомогою сесій
    Проілюструємо все вищенаписане невеликим прикладом:
    створимо файл auth.php:
    <?
    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;
    ?>