Сесії в PHP


Сесії в PHP

 У різноманітних конференціях, присвячених програмуванню мене в першу чергу завжди цікавлять такі розділи, як "Web-програмування" та "Скрипти". 

Здебільшого, питання про PHP в таких форумах досить прості, що вимагають лише загального розуміння PHP, проте, найбільш часто задається питання за моїми спостереженнями, це: "Що таке сесії в PHP і з чим / як їх можна їсти?".  Хотілося б роз'яснити це питання раз і назавжди.

З самого початку PHP все взяли на ура, але як тільки на цій мові стали створювати достатньо великі проекти, розробники зіткнулися з новою проблемою - в PHP було відсутнє поняття глобальних змінних!  Тобто, виконувався якийсь скрипт, посилав згенерувала сторінку клієнту, і все ресурси, що використовуються цим скриптом знищувалися.  Спробую проілюструвати: припустимо є дві сторінки одного сайту, index.php і dothings.php.  Вихідні тексти до цих сторінках виглядають так:

- index.php -

<?php

$a = "Мене задали на index.php";

?>

<html><body>

<?php

echo $a;

?>

</body></html>

- dothings.php -

<html><body>

<?php

echo $a;

?>

</body></html>

Якщо виконати ці два скрипта, то на першій сторінці ми побачимо напис "Мене поставили на index.php", а друга сторінка буде порожній.
Розробники web-сайтів, недовго думаючи, стали використовувати cookie для зберігання глобальних змінних на стороні клієнта. Процес виглядав приблизно так: користувач приходить на головну сторінку сайту, робить якісь дії, і вся інформація, пов'язана з цим користувачем, яка може знадобитися на інших сторінках сайту, буде зберігатися у нього в браузері у вигляді cookie. Цей метод має досить серйозні мінуси, з-за яких від PHP в свій час відвернулося чимало розробників. Наприклад, нам потрібно авторизувати користувача, щоб дозволити йому доступ до закритих (або належать тільки йому) розділів сайту. Прийдеться <кидати> користувачеві cookie, який служить його подальшим ідентифікатором на сайті. Такий підхід стає дуже громіздким і не зручним, як тільки сайт починає збирати все більше і більше відомостей про поведінку користувача, адже всю інформацію, яка посилається користувачеві, бажано кодувати, щоб її не можна було підробити. Ще зовсім недавно підробкою cookie можна було <повалити> не один чат, а часом і пробратися в чужу пошту. До того ж є ще на світі дивні люди, у яких браузер cookie не підтримує.
При використанні сесій вся інформація зберігається не на стороні клієнта, а на стороні сервера, і тому краще захищена від маніпуляцій зловмисників. Та й працювати з сесіями куди простіше і зручніше, так як всі дані автоматично проходять через алгоритми криптографії модуля PHP. В браузері клієнта, лише зберігається унікальний ідентифікатор номери сесії, або у формі cookie, або у вигляді змінної в адресному рядку броузера, який із двох способів використовувати для передачі ідентифікатора сесії між сторінками інтерпретатор РНР вибирає сам. Це на 100 безпечно, так як унікальний ідентифікатор сесії, і підробити його практично неможливо (про це трохи далі, у розділі про безпеку сесій).
Я не буду вдаватися в технологічні питання пристрою механізму роботи сесій, а лише опишу, як правильно працювати з сесіями в PHP.

Як працювати з сесіями?

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

 "Warning: open (/ var / state / php / sess_6f71d1dbb52fa88481e752af7f384db0, O_RDWR) failed: No such file or directory (2)".
Це означає всього лише, що у вас неправильно налаштований PHP.  Вирішити цю проблему можна, прописавши правильний шлях (на існуючу директорію), щоб зберегти сесій у файлі php.ini і перезапустити сервер.
 Будь-скрипт, який буде використовувати змінні (дані) з сесій, повинен містити наступну строку:

session_start();

Ця команда говорить серверу, що дана сторінка має потребу у всіх змінних, які пов'язані з даним користувачем (браузером).  Сервер бере ці змінні (з файлу, або з БД) і робить їх доступними.  Дуже важливо відкрити сесію до того, як будь-які дані будуть надсилатися користувачеві;  на практиці це означає, що функцію session_start () бажано викликати на самому початку сторінки, наприклад так:

<?php

session_start();

?>

<html>

<head>

</head>

Після початку сесії можна задавати глобальні змінні.  Це елементарно: викликаємо функцію session_register ( 'var_name');  і змінна $ var_name стає доступною на всіх сторінках, що використовують сесію.  Для прикладу покорписаємо програмку, наведену на початку статті:

- index.php -
 <? php
 // відкриваємо сесію
 session_start ();
 // задаємо значення змінної
 $ A = "Мене поставили на index.php";
 // реєструємо змінну з відкритою сесією
 // важливо: назви змінних передаються функції session_register ()
 // без знаку $
 session_register ( "a");
 ?>
 <html>
 <body>
 Все ОК.  Сесію завантажили!
 Пройдемо, подивимося що <a href="dothings.php> там: </a>
 </ body>
 </ html>
 - dothings.php -
 <? Php
 // відкриваємо сесію
 session_start ();
 ?>
 <Html>
 <Body>
 <? php
 echo $ a;
 ?>
 </ body>
 </ html>

При запуску цих файлів (в логічній послідовності звичайно), перший скрипт (index.php) видасть наступний результат:
 Все ОК.  Сесію завантажили!  Пройдемо, подивимося що там:
 А другий (dothings.php) ось це:
 Мене поставили на index.php
 Змінна $ a тепер доступна на всіх сторінках даного сайту, які запустили сесії.
 Інші корисні функції для роботи з сесіями:
 session_unregister (string) - сесія <забуває> значення заданої глобальної змінної;
 session_destroy () - сесія знищується (наприклад, якщо користувач покинув систему, натиснувши кнопку <вихід>);
 session_set_cookie_params (int lifetime [ string path [ string domain]]) - за допомогою цієї функції можна встановити, як довго буде <жити> сесія, задавши unix_timestamp визначає час <смерті> сесії.  За замовчуванням, сесія <живе> до тих пір, поки покупець не закриє вікно браузера.

Приклади

Тепер звернемося до практичного застосування механізму сесій.  Давайте розглянемо пару досить простих і в той же час корисних прикладів.
Авторизація Користувача
Питання по авторизації користувачів за допомогою PHP-сесій постійно задаються у конференціях з web-програмування.  Механізм авторизації користувачів в системі за допомогою сесій досить хороший з точки зору безпеки (див. Розділ <Безпека> нижче).
Наш приклад буде складатися з трьох файлів: index.php, authorize.php і secretplace.php.  Файл index.php містить форму, де користувач введе свій логін і пароль.  Ця форма передасть дані файлу authorize.php, який в разі успішної авторизації допустить користувача до файлу secretplace.php, а в іншому випадку видасть повідомлення про помилку. 

Приступимо: - index.php -
 <html>
 <head>
 <title> Введи пароль, смертний </ title>
 </ head>
 <Body>
 <Form action = "authorize.php" method = "post">
 Логін: <input type = "text" name = "user_name"> <br>
 Пароль: <input type = "password" name = "user_pass"> <br>
 <Input type = "submit" name = "Submit">
 </ Form>
 </ Body>
 </ Html>
 - authorize.php -
 <? Php
 // відкриваємо сесію
 session_start ();
 // дані були відправлені формою?
 if ($ Submit) {
 // перевіряємо дані на правильність ... в даному випадку я
 // вписав своє ім'я користувача та пароль прямо в код, доцільніше
 // було б перевірити логін / пароль в базі даних і при сов-
 // падінні дати доступ користувачеві ...
 if (($ user_name == "cleo") && ($ user_pass == "password")) {
 $ Logged_user = $ user_name;
 // запам'ятовуємо ім'я користувача
 session_register ( "logged_user");
 // і переправляємо його на <секретну> сторінку ...
 header ( "Location: secretplace.php");
 exit;
 }
 }
 // якщо щось було не так, то користувач отримає повідомлення про помилку.
 ?>
 <html> <body>
 Ви ввели невірний пароль!
 </ body> </ html>
 - secretplace.php -
 <? Php
 // відкриваємо сесію
 session_start ();
 / *
 просто зайти на цю сторінку не можна ... якщо
 ім'я користувача не зареєстровано, то
 його на сторінку index.php
 для введення логіна і пароля ... тут насправді
 можна багато чого зробити, наприклад запам'ятати
 IP користувача, і після третьої спроби отримати
 доступ до файлів, його закрити.
 * /
 if (! isset ($ logged_user)) {
 header ( "Location: index.php");
 exit;
 }
 ?>
 <html>
 <body>
 Привіт, <? Php echo $ logged_user;  ?>, Ти на секретній сторінці !!!  :)
 </ body>
 </ html>

Безпека
Отже, ми вміємо передавати ідентифікатор від однієї сторінки (PHP-скрипту) до іншого (до наступного виклику з нашого сайту), а значить ми можемо розрізняти всіх відвідувачів сайту. Так як ідентифікатор сесії - це дуже велике число (128 біт), шансів, що його вдасться підібрати перебором, практично немає. Тому зловмиснику залишаються наступні можливості:
на комп'ютері користувача коштує <троян>, який краде номери сесій;
зловмисник відловлює трафік між комп'ютером користувача і сервером. Звичайно, є захищений (зашифрований) протокол SSL, але ним користуються не всі;
до нашого комп'ютера користувача підійшов сусід і стягнув номер сесії.
Такі ситуації, засновані на тому, що хтось у когось поцупить, загалом, не входять до компетенції програміста. Про це повинні дбати адміністратори і самі користувачі.
Втім, PHP дуже часто можна <обдурити>. Давайте розглянемо можливі точки злому в програмі авторизації користувача:
Файл authorize.php - спроба підбору пароля за допомогою стороннього скрипту;
Файл secretplace.php - спроба обдурити програму шляхом вписування значень змінної $logged_user в адресному рядку браузера, наприклад так:
http://www.yoursite.com.ua/secretplace.php?logged_user=hacker
Отже, в нашій програмі явно видно дві <дірки>, одна маленька і не особливо помітна, а от друга - просто величезна, через яку більшість хакерів і лізе туди, куди не треба.
 

Як <залатати> дірку номер 1?
Не будемо писати тонни коду блокування IP-адреси і т. п., а просто перевіримо, звідки приходить запит, а точніше, з якої сторінки прийшов запит, якщо це буде будь-яка сторінка з нашого сайту, то все нормально, а у всіх інших випадках пускати не будемо. Підкоригуємо файл authorize.php:

- authorize.php V2 -
 <? php
 // відкриваємо сесію
 session_start ();
 // повний шлях до кореневої директорії де розташовані скрипти
 $ SERVER_ROOT = "http: // localhost / test1 /";
 // якщо користувач прийшов з будь-якої сторінки нашого сайту
 // то він начебто наш ...
 // Змінна $ HTTP_REFERER завжди доступна за замовчуванням
 // і містить повну адресу сторінки, що посилається ...
 // функція eregi () перевіряє, чи починається адреса сторінки, що посилається
 // зі значення у змінній $ SERVER_ROOT
 if (eregi ( "^ $ SERVER_ROOT", $ HTTP_REFERER)) {
 // дані були відправлені формою?
 if ($ Submit) {
 // далі все як раніше
 if (($ user_name == "cleo") && ($ user_pass == "password")) {
 $ Logged_user = $ user_name;
 // запам'ятовуємо ім'я користувача
 session_register ( "logged_user");
 // і переправляємо його на <секретну> сторінку ...
 header ( "Location: secretplace.php");
 exit;
 }
 }
 }
 ?>
 <html> <body>
 Ви ввели невірний пароль!
 </ body> </ html> 

Як позбутися від <діри> номер 2?
Припустимо, у вас є сайт, де кожен смертний може зареєструватися щоб додавати повідомлення в форум.  Природно, у форумі у деяких користувачів (адмінів, модераторів), можливостей більше ніж у інших, вони, наприклад, можуть видаляти повідомлення інших користувачів.  Рівень доступу користувача ви зберігаєте в сесії, у змінній $ user_status, де $ user_status = 10 відповідає повному доступу до системи.  Що прийшли на сайт зловмиснику досить зареєструватися штатним чином, а потім дописати в адресному рядку браузера? User_status = 10.  Ось і завівся у вас на форумі новий адмін!
 В принципі, будь-яку змінну скрипту можна задати через адресний рядок, просто дописав після повної адреси до скрипту знак і назву змінної з її значенням.  Давайте виправимо наш код, щоб цього уникнути:
 - secretplace.php V2 -
 <? Php
 // прибираємо все зайве з адресного рядка
 // функція unset () <звільняє> змінну
 unset ($ logged_user);
 // відкриваємо сесію
 session_start ();
 // і коригуємо зіпсовані змінні.
 // Важливо: в цьому випадку, змінна реєструється не як нова
 // змінна, а як вже існуюча, а тому знак $ не опускається
 session_register ($ logged_user);
 / *
 просто зайти на цю сторінку не можна ... якщо
 ім'я користувача не зареєстровано, то
 його на сторінку index.php
 для введення логіна і пароля ... тут насправді
 можна багато чого зробити, наприклад запам'ятати
 IP користувача, і після третьої спроби отримати
 доступ до файлів, його перекрити.
 * /
 if (! isset ($ logged_user)) {
 header ( "Location: index.php");
 exit;
 }
 ?>
 <Html>
 <Body>
 Привіт, <? Php echo $ logged_user;  ?>, Ти на секретній сторінці !!!  :)
 </ body>
 </ html>

Підсумки

Механізм сесій - досить вдала особливість мови PHP.  Сесії зростання, дуже гнучкі в використанні.  До речі, є одна, мало де документована можливість сесій PHP (доступна починаючи з версії 4.0.3) - в сесіях можна зберігати не тільки змінні, але і об'єкти.
Додавання від 14.12.2001
З виходом у світ PHP 4.1.0 - робота з сесіями значно полегшилася.  Всі змінні сесій стали доступні з глобального масиву _SESSION [ 'var_name'].  Найприємніше напевно в тому, що при присвоєнні якого-небудь значення будь-якого полю масиву, змінна з таким же ім'ям автоматично реєструється, як змінна сесії, на приклад:
 

<?
 $ _SESSION [ 'counter'] = 12;
 echo $ counter;
 ?>
 виведе на екран браузера число 12.