Примеры работы с сессиями PHP4

Автор статьи: OneZero
Сайт Автора: getinfo.ru
E-mail Автора: нет
Дата публикации: 11.11.2005


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

В такой задаче надо перед началом проходжения теста взять все номера вопросов, перемешать между собой и сохранить в область данных посетителя массив номеров. Затем поместить в туже область флаг (flag=тестирование), чтобы человек не мог перейти на начало теста (начать его заново), не прорешав тест до конца. Это сделать очень просто. Предположим, что index.php - это главная страница для старта, vopros.php - страница, где выдадут очередной вопрос. Если человек с флагом "тестирование" попытается перейти на index.php, то ему надо написать - "Вы не закончили тестирование". Если человек обиделся, вырубил комп и ушел спать, через некоторый тайм аут (к примеру, через 3 дня), надо записать в историю прохождения тестов: "брошенный тест". Это похоже на игру через Интернет - если человек проигрывает, но не хочет признать это, он может как бы потерять связь с игровым сервером (якобы модем повесил трубку).

Как это все реализовать? Очень просто. Предположим, что у каждого посетителя есть некая область памяти (у каждого - своя), в которую наша программа может что-то записывать и читать. Эта область памяти должна соответствовать всем требованиям, которые мы выдвинули выше. Наша программа не должна заботиться о ней. И все это можно сделать. Эта абстрактная область памяти имеет идеальный интерфейс. Вернее, не имеет его. К примеру, если бы данная область хранилась в файлах, нам надо было бы файлы читать и анализировать. Для записи - надо было бы записывать туда данные в каком-то формате. При хранении в базе данных - нам пришлось в начале программы выполнять SQL-запрос, чтобы извлечь все данные и в самом конце выполнения программы записать их обратно. Все это (затраты на чтение файла или базы данных) - называется интерфейс. Как я сказал, наше общение с базой данных вообще не имеет интерфейса. Это значит, что любые переменные, которые мы пожелаем, будут с некоторого момента считаться областью памяти посетителя. При каждом запуске программы, от разных посетителей, она будет иметь доступ через какие-то переменные только к персональным настройкам. Чтобы сохранить (закрепить) у пользователя какую-либо информацию в переменной, надо просто записать в эту переменную что-нибудь. К примеру, в программе авторизации пользователей, говорим "хочу, чтобы $auth стала частью областью памяти". Если человек вводит свой логин и пароль (их мы проверяем как-то, например по базе данных), то мы записываем в переменную $auth единичку, затем в аналогичную переменную $login его логин. Пароль записывать не надо, т.к. наличие единицы в переменной $auth уже будет означать, что человек прошел авторизацию. На всякий случай будем хранить еще и логин. Пароль хранить совершенно не нужно, т.к. нет случая его применить... кроме как вывести на экран (но это уже будет глупая затея, а не реальная программа). Таким образом, мы один раз устанавливаем $auth, а дальше в любом месте может проверить авторизированность пользователя if ($auth) или if ($auth==1!). Так как переменная $auth будет частью области памяти, то можно не бояться, что какой-нибудь злой хакер запустит нашу программу как programm.php?auth=1 и окажется авторизированным.

Подведем итог нашей мечте о комфортной работе Все, о чем тут было сказано, как не трудно догадаться по названию, называется сессия. Принцим действия следующий (забудьте пока о том, как это все работает): чтобы сохранить о пользователе что-то, выберем для этого переменную и сообщим php-процессору о нашем желании с помощью session_register("auth") и session_register("login"). Это называется регистрация переменной. Эти строчки пишут в начале программы, чтобы PHP-код, идущий ниже в программе, мог обращаться к $auth и $login.

Если мы что либо запишем в $auth и/или $login, то эта переменная будет сохранена в персональную область данных посетителя. Это значит, что при следующем вызове той же программы (или из того же каталога или подкаталогов), перед началом работы php-скрипта, php-процессор узнает пользователя и загрузит сохраненные для него переменные (если когда-либо в них что-то записывалось).

И самое главное - вся информация в сессиях не может быть подделана пользователем. Т.е. наличие единицы в какой-то переменной сессии означает, что туда ее могла записать программа, а не кто-либо.

Еще раз скажем "итак" и подведем итог. Мы хотим, чтобы PHP-процессор умел распознавать пользователей и предоставлял в свободное распоряжение программе область памяти (в виде переменной $auth, $login или $любое_название), которую невозможно никак подделать. Всех новых пользователей PHP-процессор тоже должен распознавать и присваивать им новую область памяти. Единственное, что сможет злой пользователь - прикинуться новым пользователем или вообще бросить наш сайт.

Как устроены сессии: идентификатор посетителя Выше был описан принцим сессий. Их придумали для того, что PHP лишен такой нормального свойства программы - хранить что-либо о пользователе. И виноват тут не PHP. Если взять что-то другое - средневековый язык Perl или язык ASP (для тех, кто решил тормознуть P4 1700Mhz dual), они будут работать по тем же самым принципам. И будут лишены того же - способностью что-то созранить о пользователе. Это происходит из-за убогости Web'a. Но задача программиста не придумывать что-то, а писать программы, работающие в любой среде.

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

Запомните главное - если суметь передать идентификатор от одной страницы (PHP-скрипта) до другой (до следующего вызова с нашего сайта), то мы сможем различать всех посетителей. Больше, от не удобного для программиста Web'а, ничего не требуется. Так как мы выбрали очень больше число, то не существует ни малейшего опасения, что злой хакер подделает число, тем самым воспользуется чужой областью данных. Запомните еще одну главную мысль - подделать сессию или идентификатор нельзя. Число из 128 бит (это 2 в 128 степени) в десятичном представлениим имеет 38 нулей. Пердположим, на вашем сайте будет 10 000 посетителей. Тогда у хакера шансы несколько возрастают до 2 в степени 114. Но это по прежнему нереальная вероятность, что либо подделать. Одна из очевидных причин следующая. Чтобы подобрать номер сессии, нужно сделать запрос к вашему веб-серверу. Пусть каждый запрос занимает 1024 байта. Через миллиард обращений к вашему сайту, что составит 1000 ГБ трафика, явно что-то случиться:
ваш сайт отключат из-за дикого трафика или у вас не хватит денег на оплату такого большого трафика (по 36 баксов за ГБ - $36000). Дешевле нанять группу товарищей для взлома сервера или группу товарищей в черной одежде для простого выноса сервера :-)
ваш канал или пропусканая способность провайдера слишком слабая, чтобы в разумные сроки перебрать все номера ваш сервер не сможет хоть как-то быстро работать, имея гигантское количество пустых файлов, из-за создания фиктивных сессий
ваш сервер переполниться лог файлами и файлами фиктивных сессий, перестанет работать
всех хакеров посадят за целенаправленную и длительную атаку

Хоть сессию подделать нельзя, но существуют следующие непредвиденные случаи: на компьютере посетителя побывал злой хакер, который поставил злую программу, ворующую пароли от интернета (статья N##) и номера сессий; между компьютером посетителя и сервером сидит злой хакер и видит все, что передается. Конечно, есть защищенный (зашифрованный) протокол SSL, но это увы везде не внедрить; к компьютеру нашего посетителя подошел злой сосед и стащил этот номер сессии.

Как видите, все основано на том, что кто-то что-то у кого-то стащит. Но это уже не проблема программиста. Представьте, вы написали идеальную программу, в которой нет ошибок и дыр для взлома, а через день злой хакер взломал сервер и скопировал/изменил/удалил все, что посчитал нужным, даже не запуская программу. Еще раз заметим, хоть вариант стащить сессию есть, только это уже проблемы другого рода.

Выглядит этот несчастный идентификатор обычно так: "7f4cbf53fbcd4717792447f32da7dba8", или так: "ac4f4a45bdc893434c95dcaffb1c1811". Это текстовая запись 128-битного числа (занимает 32 байта). По умолчанию, PHP сам заботиться о том, чтобы обнаружить идентификатор или выдать новый новому посетителю.

Как устроены сессии: где хранится информация Выше мы установили, что для нашей мечты нужно постараться передавать идентификатор от вызова к вызову (как именно передают - через главу). Имея идентификтор (число) легко загрузить каждому пользователю его персональные данные. Есть два популярных способа хранинения - база данных и файлы. Еще можно хранить сессии в памяти сервера (ОЗУ), но при выключеннии сервера будут потеряны сессии. Обычно сессии хранят в файлах, чтобы снизить нагрузку на базу данных. В огромном числе задач сессии позволяют полностью отказаться от базы данных. Или использовать ее миниматьльно: в базе данных только список логин/паролей, в сессиях (на файлах) вся рабочая информация.

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

Как устроены сессии: если вы посмотрите на файл сессии (где он находиться - узнаете позже), то увиде нечто такое:

myarray|a:4524:{s:7324:"startup";i:98;s:4:"type";i:1;s:2:"ip";s:13:"1572342";s

:2:"tt";i:2;s:3:"b";i:12312;s:621:"max";i:2380;s:6:"min";i:-8334;s:10:"realmax

";i:1213126;s:10:"realmin";i:0;s:1:"kol";i:1;s:1:"n";i:15;s:9:"timestart";i:98

8387008;s:7:"timeend";i:9883218;s:4:"list";a:16:{i:0;s:7:"1002001";i:1;s:7:"10

02007";i:2;s:7:"1002010";

Представьте, у нас есть огромный массив сложной структуры, к примеру, $myarray. Вам будет интересно узнать, что любую перменную (сложный массив) с любыми символами (произвольные бинарные данные) очень легко запаковать в строку из букв и цифр. Для чего переводить массив в строку? Строку (просто переменная), да еще и текстовую, легко куда-нибудь записать. Например, на листочек бумаги. Или в базу данных... Или в файл сессии. Позже, можно распаковать такую строку в оригинальную переменную и получить то, что было до упаковки. Данный метод (называется - сериализация) упаковки/распокавки переменных или объектов применяется не только в сессиях. Просто PHP, чтобы сохранить все данные пользователя, запаковывает их и кладет как строку в файл сессии.

$myStr=serialize($myVar) - запаковать огромный массив или любую произвольную переменную $myVar в текстовую переменную $myStr.

$myVar=serialize($myStr) - распаковать (восстановить) данные из ранее запакованной переменной, хранящейся в $myStr, и записать результат распаковки (оригинальную переменную) в $myVar.

По умолчанию, программиста не должно волновать, как именно PHP хранит ваши переменные :-) Вы всегда можете перейти от метода хранения сессий в файлах, к хранению в базе данных или любых других местах, просто дописав нужный модуль по сохранению/извлечению сессий. Менять что-либо в программе не придеться!