- Подробности
-
Категория: PHP. Поиск. Авторизация
Автор : Орлов . Статья по ходу древняя
Глава 8. PHP: авторизация доступа
Если вы хотя бы иногда посещаете сайты, на которых есть "защи-щенная зона" — часть, доступ на которую возможен лишь по опреде-ленным логину и паролю (например, почтовые службы с web-интерфейсом или сервисы хостинга), то вас наверняка интересовало, как все-таки вся эта авторизация происходит. И наверняка вам хоте-лось устроить то же самое и на вашем сайте — ведь необходимость в этом иногда возникает. Ниже будет рассказано о технологиях автори-зации доступа, основанных на средствах web-сервера и технологии PHP.
Думается, значение слова "авторизация" вам понятно — это не что иное, как обеспечение возможности доступа к чему-либо тем и только тем пользователям, которые знают определенные кодовые слова — ло-гин и пароль.
Авторизация средствами web-сервера
Для того, чтобы к файлам, находящимся в какой-либо директории, могли иметь доступ лишь определенные посетители, знающие два ко-довых слова — логин и пароль, можно использовать встроенные в Web-сервер Apache средства ограничения доступа.
В конфигурационном файле Apache (он именуется httpd.conf, а просматривать и редактировать его могут только администраторы web-сервера) есть специальная строчка — AccessFileName. Там указано имя, найдя файл с которым в той или иной папке, Apache выполнит по отношению к ней указания, содержащиеся в нем. По традиции это имя — .htaccess, и именно таким оно установлено на всех серверах хос-тинга. В файл .htaccess можно поместить команды ограничения дос-тупа к той папке, в которой это файл находится.
Выглядят эти команды так. Вначале указывается название защи-щенной зоны — AuthName. Именно это название будет впоследствии выводиться в запросе посетителю (рис.8.1).
AuthName "Private Zone"
AuthType Basic
В следующем параметре — AuthUserFile — указывается путь к файлу с логинами и паролями посетителей. Этот файл должен быть создан в особом формате, так как пароли в нем хранятся в зашифрованном ви-де. Для создания файлов с паролями применяются специальные про-граммы — такую программу вы можете взять, например, в разделе тех-нической поддержки компании Valuehost по адресу http://support.valuehost.ru/bbs/files/69-htpasswd.exe. Запускать ее следу-ет из командной строки в формате
69-htpasswd.exe -c имя_файла_паролей логин
а в открывшемся окне ввести пароль (используя только латинские буквы). Чтобы добавить новые логины и пароли в уже имеющийся файл, эту программу следует запускать без параметра -с.
По традиции файл с паролями посетителей принято называть .htpasswd. Обычно Apache настраивается так, что файлы с именами .htaccess и .htpasswd невозможно просмотреть через Web — при такой попытке будет выдаваться лишь сообщение о запрещении доступа. Однако выполнение такой настройки (для этого надо указать не-сколько параметров в httpd.conf — конфигурационном файле Apache) — целиком на совести администраторов web-сервера.
Обратите внимание, что путь к файлу паролей следует указывать абсолютный — то есть от корневого каталога сервера с указанием всего дерева каталогов. На серверах хостинга он обычно имеет вид /pub/home/имя аккаунта/..../имя файла паролей, а на вашем локальном компьютере зависит от местоположения web-сервера и его настроек, например, может выглядеть и как f:/www/exper/cov/.htpasswd.
AuthUserFile /pub/home/exper/cov/.htpasswd
require valid-user
Комментарий:
То, что требуется указывать именно абсолютный путь к файлу с паролями, не должно вас удивлять. Это сделано для того, чтобы можно было использовать один и тот же файл паролей для организации ограничения доступа сразу к нескольким пап-кам и даже нескольким аккаунтам. В результате в том случае, если на сервер добав-ляется еще одна папка с защитой, то не требуется вновь раздавать посетителям но-вые логины и пароли для доступа уже к ней — достаточно прописать путь к уже имеющемуся файлу с паролями в файле .htaccess, и все указанные в нем пароли ав-томатически станут действительными и для входа в новосозданную папку.
Итак, пожелав "запаролить" доступ к ресурсам какой-либо папки, создайте файл с именем .htaccess с вышеуказанными параметрами.
Комментарий:
Сделать это командой "Проводника" или "Нортона" вам не удастся, так как эти программы не допускают создание файлов без имени (а .htaccess они воспринимают именно как расширение без имени!), поэтому наберите соответствующий текст в какой-нибудь программе, позволяющей это совершить (например, ViewText Геор-гия Гуляева, http://www.altailand.ru).
Создав файл .htaccess, загрузите программу для создания файла па-ролей и поработайте с нею. После этого загрузите оба файла на свой сайт: .htaccess — в закрываемую папку, а файл с паролями — в соответ-ствии с прописанным в .htaccess путем к нему (рис.8.2).
Вот и все! Теперь при попытке запроса любого ресурса из защи-щенной папки (в том числе и картинок, включенных в другие страни-цы тэгом <img…>) посетителю будет выдан стандартный запрос ло-гина и пароля (рис.8.1). Если логин и пароль совпадают с хранящими-ся в файле паролей (по умолчанию есть три попытки ввода), то доступ разрешается, если нет — средствами web-сервера выводится соответст-вующее сообщение.
Рис.8.1. Запрос на вход в папку.
Рис.8.2. Пароль на папку средствами web-сервера?
Достаточно двух файлов — .htaccess и .htpasswd…
Доступ открывается "для определенного окна браузера и всех его дочерних окон". Иными словами, если посетитель однажды ввел пра-вильные логин и пароль, то он, работая в одном и том же окне браузе-ра, может не только свободно путешествовать по всем ресурсам в за-пароленной папке, но и, выйдя из нее, свободно вновь в нее войти. То же самое верно и для всех окон браузера, открытых из исходного с помощью команды "открыть в новом окне". А вот если он откроет но-вое окно браузера и зайдет уже в нем в эту папку, то запрос на ввод логина и пароля появится вновь (разумеется, если страница не была взята из кэша браузера — в последнем случае достаточно ее обновить).
Использовать данный способ удается не всегда, — администрация сервера иной раз не позволяет это делать посетителям, да и програм-ма для создания файла паролей не всегда под рукой. Однако средства PHP позволяют обойтись без применения файлов .htaccess.
Авторизация с помощью заголовка
В PHP есть команда Header — она позволяет отправить браузеру по-сетителя, запросившему страницу с содержащим эту команду сцена-рием, определенную служебную информацию — так называемый "за-головок". Существует довольно много вариантов заголовков (напри-мер, заголовок "Location: http://адрес" приведет к перенаправлению на указанный URL; то же самое, что и при использовании мета-тэга http-equiv с параметром "Refresh"), однако для авторизации нам потребу-ется заголовок "WWW-Authenticate".
Примечание:
Заголовок — это данные, передаваемые браузеру до передачи самой web-страницы, сообщающие ему некоторые параметры передаваемого файла или опре-деленные команды. Список всех возможных заголовков, которые обязаны поддер-живать современные браузеры, можно найти в спецификациях протокола HTTP — они есть, например, на сайте http://www.w3.org. PHP-команда Header (параметр) выполняет всего одно действие — она просто передает то, что указано в ее парамет-ре, в браузер, запросивший страницу, на которой она находится, в качестве заголов-ка.
Следует помнить, что заголовок должен передаваться в браузер до любого дру-гого вывода в него, за исключением установки cookie.
В том случае, если браузер получает заголовок "WWW-Authenticate", то он выдает посетителю стандартное окно для ввода логина и пароля, которое вы наверняка много раз видели (как на рис.8.1). Как только посетитель нажимает кнопку Ok этого окна, браузер вновь заходит на ту страницу, с которой этот заголовок был ему послан, но на этот раз уже передает сценарию на этой странице две переменные — то, что было введено в поля ввода логина и пароля. Web-сервер дает этим переменным имена $PHP_AUTH_USER и $PHP_AUTH_PW, и их становится можно использовать в остальных сценариях на странице как любые другие переменные — использовать в выражениях, сравнивать с каким-либо эталоном, присваивать им какие-либо другие значения, наконец.
Если посетитель нажимает кнопку Cancel в диалоговом окне запро-са логина и пароля, то выполнение кода страницы просто продолжа-ется со следующей строчки за командой Header. Никакие перемен-ные странице не передаются.
Однако переменные $PHP_AUTH_USER и $PHP_AUTH_PW — не простые. Если они один раз были определены, то впоследствии они передаются всем web-страницам, которые загружаются в то же самое окно браузера, где произошла авторизация! Иными словами, если по каким-то причинам требуется проверять логин и пароль посетителя на каждой из страниц сайта (скажем, выводить разную информацию ав-торизованным и неавторизованным посетителям), то каждый раз за-прашивать эти данные не нужно — достаточно использовать значения переменных $PHP_AUTH_USER и $PHP_AUTH_PW. Значения дан-ных переменных теряются в случае закрытия окна браузера, в кото-ром изначально произошла авторизация (а в другие окна они и не пе-редаются). При выходе за пределы виртуального сервера, на котором произошла авторизация (обычно его границы совпадают с границами аккаунта), данные переменные перестают передаваться страницам, однако при повторном входе на исходный адрес вновь становятся доступными (это обеспечение безопасности — за пределами вашего виртуального сервера логины и пароли ваших посетителей никто уз-нать из их браузеров не сможет).
Кстати, при использовании предыдущего способа — средствами Apache — в переменные $PHP_AUTH_USER и $PHP_AUTH_PW тоже помещаются значения логина и пароля, введенные пользователем. В принципе вы можете найти им какое-нибудь применение.
К примеру, вспомним содержание седьмой главы, в которой рас-сматривалась программа для самостоятельной загрузки посетителями файлов на сайт. Помните, в чем была проблема — проверка пароля и сама загрузка файлов совершались сценарием на одной и той же стра-нице, и в случае ошибки при вводе пароля посетитель все равно был вынужден ждать окончания загрузки файла на сайт, чтобы тот был сразу же оттуда удален? Так вот — используя данный способ авториза-ции (и предыдущий — средствами Apache — тоже), можно разделить авторизацию и закачку файлов, предоставив посетителю возможность вначале ввести логин с паролем, а только потом, если они правиль-ные, выдать ему форму для закачки файла. Если добавить на страницу обработки закачанного файла краткую программу для проверки пере-менных $PHP_AUTH_USER и $PHP_AUTH_PW, то можно не бояться захода на страницу загрузки неавторизованных посетителей (скажем, по закладке или путем прямого набора ее адреса в браузере) — таковые будут отсеяны, а запросы легальных обработаны.
Ниже приводится сценарий, который запрашивает у пользователя логин и пароль, проверяет их по наличию в определенном файле, а затем выводит либо приглашение к загрузке файла, либо сообщение об отказе в авторизации.
Итак, начало сценария. Обратите внимание, что для того, чтобы он сработал, до команды Header в выдаваемый документ не должно ничего выводиться: ни результат выполнения команд PHP, ни про-стое содержимое страницы, — так уж воспринимают web-страницы браузеры. В частности, данный сценарий должен располагаться в са-мом начале страницы, и символы <?php должны быть на ней самыми первыми, перед ними не должно быть даже пробела.
<?php
Поскольку после выдачи окна авторизации браузер вновь вызывает web-страницу, передавая ей авторизационные данные, то можно про-верить их еще до отправки браузеру заголовка WWW-Authenticate. В самом деле — если окно авторизации не выводилось вообще, то пере-менные $PHP_AUTH_USER $PHP_AUTH_PW будут пустыми (вер-нее, вообще не определены), а если выводилось — то в них окажется информация, введенная посетителем (то есть логин и пароль).
Наиболее простым вариантом будет указание логина и пароля в тексте самой программы авторизации — ведь все равно код на PHP, размещающийся на странице, посетители увидеть не смогут (Вернее, смогут лишь в том случае, если данный код располагается в файле не с тем расширением, которое указано в настройках web-сервера как признак страниц с программами на PHP). В этом случае команда проверки содержимого переменных $PHP_AUTH_USER и $PHP_AUTH_PW на соответствие указанным будет выглядеть как
if (($PHP_AUTH_USER!="login")||($PHP_AUTH_PW!= "parol"))
{
Дальше идет тот код, который выполняется в случае несоответст-вия содержимого переменных указанным в команде логину и паролю. В случае самой первой загрузки страницы он, естественно, тоже вы-полнится — переменные $PHP_AUTH_USER и $PHP_AUTH_PW в та-ком случае еще не будут определены.
Итак — выдаем окно авторизации, для чего посылаем браузеру со-ответствующий заголовок:
Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\"");
Браузер, получив такое, выдаст посетителю окно (такое же, как на рис.8.1) с запросом логина с паролем. После нажатия кнопки Ok стра-ница будет загружена вновь и в том случае, если логин и пароль соот-ветствовали указанным в ее тексте, будет выводиться остальной ее текст — тот, что последует за командой if, за ее закрывающей фигур-ной скобкой. Ну, а если логин и пароль будут введены неправильно, то окно авторизации выскочит вновь — и у посетителя появится еще один шанс правильно авторизоваться. И так до тех пор, пока не будут введены правильные логин и пароль.
Однако в выдаваемом окне есть еще кнопка "Отмена"! И в том случае, если посетитель нажмет ее, то код просто начнет выполняться дальше, со следующей после Header команды. Следовательно, в этот код и нужно вставить те команды, которые сообщают посетителю, так и не сумевшему ввести правильные логин с паролем, что он не может войти в защищенную зону. (Не забудьте, что все эти команды должны находиться в пределах блока оператора if — ведь нам надо, чтобы они выполнились только в случае нажатия кнопки "Отмена"!).
Выдадим браузеру заголовок, сообщающий об отказе в авториза-ции (требуется некоторым браузерам, а заодно и обнуляет перемен-ные $PHP_AUTH_USER и $PHP_AUTH_PW):
Header("HTTP/1.0 401 Unauthorized");
…а затем — укажем тот текст, который должен быть выдан посети-телю в случае отказа в авторизации — то есть нажатия им кнопки "От-мена" в диалоговом окне:
echo ("<p>Доступ закрыт!</p>");
(При желании вы можете сделать целую web-страницу, на которой подробно рассказать, например, как можно достать правильные логин с паролем, и вставлять ее текст сюда в случае отказа в авторизации с помощью команды include:
include ("noauth.php");
Например, так стоит сделать, если код этой страницы весьма большой или одна такая страница будет использоваться для несколь-ких мест авторизации.)
И, наконец, завершим работу с текущей страницей — нам ведь не нужно выполнять тот код, что дальше; он ведь должен быть доступен только авторизованным посетителям. Для выхода используем комаду exit:
exit;
}
Она полностью прекращает как вывод web-страницы посетителю, так и выполнение какого-либо кода.
Вот, собственно, и все:
?>
Для наглядности — все строчки кода вместе:
<?php
if (($PHP_AUTH_USER!="login")||($PHP_AUTH_PW!= "parol"))
{
Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\"");
Header("HTTP/1.0 401 Unauthorized");
…команды вывода текста страницы, выдающейся посетителю в случае нажатия им кнопки "Отмена"…
exit;
}
?>
Указание логина и пароля в самом тексте PHP-сценария — простой, но не очень удобный способ их хранения. В самом деле — если вы планируете предоставить доступ нескольким посетителям, то вам придется давать им одну и ту же пару логина и пароля. Это чревато тем, что при необходимости отказать кому-нибудь из них в доступе придется менять пароль, о чем придется сообщать всем остальным.
Ниже приводится небольшой код, реализующий проверку содер-жимого авторизационных переменных на совпадение с какой-нибудь парой "логин-пароль" из специального файла паролей. Допустим, файл, содержащий логины и пароли, располагается в папке passw и называется passwr, а формат его прост — запись типа "логин пароль" (через пробел) на каждой строчке (см.рис.8.3).
Рис.8.3. Простейший файл паролей.
Комментарий:
Для того, чтобы какой-либо файл, с которым работает сценарий (в частности, файл с паролями в разбираемом примере), нельзя было загрузить через web-интерфейс, просто набрав его имя (и тем самым получив на экран все его содержи-мое), можно предпринять следующее:
1. Сделать это имя как можно более длинным и заковыристым (все равно оно фигурирует только в программном коде, то есть из Сети его узнать будет никак нельзя).
2. Поместить в папку с файлом (в нашем примере — в папку passw) файл с име-нем .htaccess со следующим кодом:
Order Allow,Deny
Deny from All
Этот код запрещает что бы то ни было загружать из этой папки из Интернета, в то же время оставляя возможность работать с ее содержимым из сценариев. Поэто-му при использовании такого способа в папку с "защищенным" файлом не следует помещать web-страницы, которые должны быть доступны посетителям напрямую, через браузер.
3. Установить атрибуты файла так, чтобы они запрещали работу с ним группе "Public", например, поставив их в 770 (в CuteFTP это делается пунктом CHMOD из контекстного меню файла).
4. Указать в файле настроек web-сервера Apache (именующемся httpd.conf) в разделе описания соответствующего виртуального сервера параметр Location:
<Location /…путь к папке из корневого каталога вирту-ального сервера…>
deny from all
</Location>
но сделать это вы сможете, только если являетесь администратором web-сервера.
Рис.8.4. Средства закрытия папки от чтения ее файлов че-рез Сеть.
Итак, начнем сценарий. Для удобства используем в нем в качестве пометки о прохождении авторизации отдельную переменную — назо-вем ее $rez. Запросив в начале сценария у посетителя логин с паро-лем и проверив их, присвоим переменной $rez значение 1, если они правильные. А впоследствии при необходимости вывести на web-страницу что-то, что должно быть видно лишь авторизованным поль-зователям, будем проверять значение этой переменной: если оно рав-но 1, то авторизация прошла успешно, если нет — то посетитель не знает правильных логина и пароля. Так проще, чем каждый раз про-водить просмотр файла паролей.
Для начала присвоим переменной $rez значение 0 — ведь иначе любой, кто, зайдя на эту web-страницу, укажет в адресной строке браузера после имени страницы параметр rez=1 (это, например, мо-жет выглядеть как "http://www.******.ru/auth.php? rez=1") , тем самым "обманет" сценарий и заставит его считать, что авторизация уже выполнена правильно. Ведь, как вы помните, пара-метры, указываемые в адресной строке браузера после имени web-страницы со сценарием на PHP, становятся переменными в этом са-мом сценарии (Если в файле настроек PHP php.ini включена в On опция register_globals). Бесспорно, для такого обмана потребуется знать имя и нужное значение этой самой переменной-"пометки", но мало ли, вдруг это откуда-либо будет известно злоумышленнику…
<?php
$rez=0;
Командой file считаем файл построчно в массив…
Примечание:
Команда file помещает в массив указанный в ее параметре файл, помещая ка-ждую строку файла в отдельный элемент массива.
…и начнем сравнивать пару "логин-пароль" каждой строчки файла (т.е. каждый элемент массива) с той парой, что мы получили от поль-зователя. Массив даже нет нужды именовать — достаточно просто ука-зать команду file в цикле foreach (как упоминалось в предыду-щей главе, этот оператор считывает каждый элемент указанного в его параметрах массива в переменную с именем, указанным после ключе-вого слова as, и выполняет для каждого элемента массива код, ука-занный в фигурных скобках).
foreach (file("passw/passwr") as $k)
{
Комментарий:
Оператор foreach будет работать только в PHP 4.0 и выше. Если вы можете ис-пользовать лишь PHP3, то вместо этого оператора можно использовать цикл for, указав в его параметрах величину массива:
$b=file("passw/passwr");
for ($i = 1; $i < $sizeof($b); $i++)
{
Для удобства можно записать значение очередного элемента массива в пере-менную:
$value=$k[$i];
Поскольку каждая строчка файла завершалась символом перевода строки (вернее, двумя символами — с ASCII-кодами 10 и 13), то его необходимо удалять перед сравнением (в введенных пользователем значениях символа перевода строки-то нет!) — это делает функция substr.
if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")
{
Примечание:
Команда substr предназначена для выделения из строки ее части. Строка (или переменная, ее содержащая) должна быть указана в первом параметре команды. Второй параметр — позиция, с которой начинается выделяемая часть (вернее, число символов, которые необходимо пропустить до начала выделения части строки), а третий — количество выделяемых символов.
Второй параметр может быть и отрицательным. В этом случае отсчет позиции начала выделяемой части будет идти не с начала, а с конца строки. Иными словами, в результате выполнения команды substr ("qwertyuiop", -3, 2) из строки "qwertyuiop" будет выделена строка io — она начинается за 3 символа от конца ис-ходной строки и продолжается 2 символа.
Третий параметр тоже может быть отрицательным. В этом случае будет выделе-на строка, начинающаяся с указанной во втором параметре позиции и оканчиваю-щаяся за столько символов до конца строки, сколько указано в третьем параметре. Иными словами, в результате выполнения команды substr ("qwertyuiop", 3, -2) из строки "qwertyuiop" будет выделена строка rtyui — она начинается после 3 символа исходной строки и заканчивается за 2 символа до ее окончания.
В том случае, если параметры установлены так, что выделить согласно им сим-волы из строки оказывается невозможно (например, второй параметр больше, чем число ее символов), то результатом работы команды substr будет пустая строка — "".
Если в файле с паролями была найдена пара "логин-пароль", сов-падающая с данными, введенными пользователем, то присвоим пере-менной $rez значение 1. Впоследствии ниже, когда нам надо будет проверить, совершилась ли авторизация, просто будем проверять зна-чение этой переменной — так проще, чем вновь проводить просмотр файла паролей.
$rez=1;
}
}
Собственно, и все — проверка завершена. Теперь в том случае, если переменная $rez не равна 1, следует выдать окно авторизации и полу-чить от посетителя логин и пароль, а если равна — то выводить стра-ницу дальше. Как помните, возможности установить значение этой переменной в 1 через указание ее в адресной строке мы воспрепятст-вовали, установив $rez в 0 в начале сценария.
if ($rez!=1)
{
Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\"");
Header("HTTP/1.0 401 Unauthorized");
…команды вывода текста страницы, выдающейся посетителю в случае нажатия им кнопки "Отмена"…
exit;
}
?>
Как уже говорилось, переменные $PHP_AUTH_USER и $PHP_AUTH_PW передаются всем страницам, которые загружаются в то же самое окно браузера — то есть на которые посетитель перехо-дит. Поэтому их можно использовать для проверки его прав на вы-полнение того или иного действия без новых запросов. Скажем, если на какой-нибудь странице, на которую посетитель должен перейти лишь после авторизации, должна производиться загрузка файла, то перед загрузкой (в сценарии-обработчике загруженного файла, под-робнее — см.главу 7) следует вновь проверить соответствие передан-ных этой странице переменных $PHP_AUTH_USER и $PHP_AUTH_PW какой-нибудь паре логина и пароля в файле паро-лей:
<?php
foreach (file("passw/passwr") as $k)
{
if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")
{
…команды загрузки файла…
}
}
?>
Данный код просматривает файл с паролями (да, опять тот же файл…) и определяет, есть ли там такая же пара "логин-пароль", как и та, что записана в переданных странице переменных. Если есть — файл загружается, если нет (т.е. посетитель зашел на страницу с формой для загрузки файла, скажем, по сделанной в "Избранном" закладке или введя ее URL в адресную строку браузера, миновав страницу ав-торизации) — то загрузки не происходит.
Иными словами — один раз введенные посетителем правильные ло-гин с паролем записываются в переменные $PHP_AUTH_USER и $PHP_AUTH_PW до тех пор, пока посетитель не закроет окно браузе-ра (и все окна, открытые по ссылкам командой "Открыть в новом ок-не" из окна, где произошла авторизация). На тех страницах, куда по-сетитель может попасть после авторизации, значения этих перемен-ных можно проверять, сравнивая с каким-либо эталоном, например, записанными в скрытом файле логинами и паролями, и выдавать по-сетителю в зависимости от соответствия эталону его авторизацион-ных данных разную информацию. Это предотвратит возможность по-пасть в "закрытую зону" помимо окна авторизации, через набор адре-са в адресной строке или по закладке.
Например, для отправки на страницу авторизации всех, кто ее не прошел, можно воспользоваться кодом
<?php
$rez=0;
foreach (file("passw/passwr") as $k)
{
if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")
{ $rez=1; }
}
if ($rez!=1)
{
Header ("Location: auth.php");
}
?>
где auth.php — страница с кодом выдачи окна авторизации. Заголо-вок Location, переданный браузеру, вызывает его переход на указан-ную в нем страницу. Так как в данном коде используется команда Header, то она сработает без ошибок лишь в том случае, если до нее в браузер посетителя ничего не выдавалось (кроме разве что других за-головков и cookies).
Особенности описанного способа авторизации довольно очевидны. Например, данные авторизации сохраняются в переменных лишь в течение одного сеанса работы посетителя; достаточно ему закрыть окно браузера, чтобы необходимость ввода логина и пароля возникла снова. Для заполнения полей окна авторизации нельзя использовать имеющуюся во многих браузерах функцию автозаполнения форм (со-временные браузеры могут запоминать соответствие определенному URL лишь одной пары "логин-пароль" и подставлять именно ее в по-ля окна), да и в интерфейс страницы это окно вписать никак нельзя (оно ведь отображается браузером).
Однако есть еще один прием регламентации доступа к страницам сайта — с использованием файлов cookies. Им мы сейчас и займемся.
Авторизация с помощью cookies
Cookie — это файл в специальном формате, который присылается сервером браузеру посетителя сайта, расположенном на этом сервере (рис.8.5). Браузер, если он поддерживает cookie (и эта поддержка в нем не отключена), помещает его в особое место и впоследствии от-правляет назад на сервер при поступлении от него запроса. Иными словами, cookie позволяет серверу хранить свою информацию на компьютерах посетителей и считывать ее оттуда при необходимости. (Современные стандарты безопасности предусматривают, что каждый сервер (Вернее, узел с собственным доменным именем (любого уровня)) может получить назад только те cookie, которые были уста-новлены лично им, так что даже о том, какие сайты еще посещал по-сетитель, с помощью cookie узнать нельзя.)
Рис.8.5. Cookie изнутри.
Врезка
Cookie
Cookie можно установить (т.е. прислать на компьютер посетителя) и средствами PHP. Для этого используется команда SetCookie, имеющая параметры: имя cookie, информация, записанная в cookie, время жизни cookie — указывается количе-ство секунд, после истечения которых с 1 января 1970 года cookie не должен считы-ваться с компьютера посетителя (так уж измеряется время в операционных систе-мах типа Unix — с начала "эпохи Unix" 01.01.1970), адреса сайта и каталога на нем, где cookie должен быть действителен, и указание на протокол передачи cookie (под-робнее смотрите в Описании PHP). Считать cookie можно простой командой echo ("имя cookie"). Можно сказать, что, как только cookie установлен, сценариям на всех страницах того сайта, на котором он был поставлен, становится доступна переменная с тем же именем, что и у cookie, и тем содержимым, которое было запи-сано в нем (если в файле настройки PHP установлен в on параметр register_globals).
Кроме того, значения cookie помещаются в массив $HTTP_COOKIE_VARS и доступны в его элементах, одноименных с именами cookie — $HTTP_COOKIE_VARS['имя cookie'] (если в файле настройки PHP установлен в on параметр track_vars), а в PHP версии 4.1 и выше — еще и в массив $_COOKIE.
Для удаления cookie достаточно записать в него пустое значение (это сделает команда SetCookie с единственным параметром — именем cookie).
Для установки времени жизни cookie можно сначала узнать текущее "время Unix" командой time(), а потом просто прибавить к нему то количество секунд, которое cookie должен просуществовать после его установки на компьютер посети-теля. Если время жизни для cookie не установлено, то он проживет до закрытия всех окон браузера посетителя.
Как и отправка заголовков командой Header, установка cookie должна пред-шествовать любому выводу в выдаваемый документ: как результатов выполне-ния команд PHP, так и простого содержимого страницы. Иначе возникнет ошибка.
Как cookie можно использовать для решения обсуждаемой в этой главе задачи — авторизации доступа? Да очень просто — запросив от посетителя логин и пароль, записать их в cookie, а потом на каждой странице "защищенной зоны" считывать их оттуда и проверять, име-ются ли такие данные в файле паролей. Ну и поступать в соответст-вии с результатом такого сравнения — например, отправлять те брау-зеры, которые не смогли представить cookie с правильными логином и паролем, прямиком на страницу авторизации, посылая им с помо-щью PHP-функции Header заголовок Location с соответствующим па-раметром, как было показано выше для предыдущего варианта авто-ризации.
Вот фрагменты сценария, в которых видна технология использова-ния cookies. На той странице, откуда должен осуществляться вход в "защищенную зону", следует поставить простую форму для ввода ло-гина и пароля (см.рис.8.6). Например, такую:
<FORM ACTION="up.php" METHOD=POST>
Логин: <INPUT NAME="login" TYPE="text"><br>
Пароль: <INPUT NAME="pass" TYPE="password"><br>
<INPUT TYPE="submit" VALUE="Войти"></FORM>
Рис.8.6. Авторизация на основе cookies. Просто форма…
На той странице, имя которой указано в параметре ACTION заго-ловка формы, введенные данные проверяются, и в том случае, если такие логин и пароль имеются в файле паролей, браузеру посетителя отсылается cookie, куда эти логин с паролем записываются.
<?php
$rez=0;
foreach (file("passw/passwr") as $k)
{
if (substr($k, 0, -2)=="$login $pass")
{
$rez=1;
И вот — сама установка cookie под именем auth. Ему устанавливает-ся "время жизни" — 3600 секунд, то есть час.
SetCookie("auth","$login $pass",time()+3600);
}
}
?>
Дальше должен находиться сценарий, который в зависимости от исхода авторизации — т.е. значения переменной $rez — выводит раз-личную информацию. Например, можно отослать посетителя назад на исходную страницу командой Header ("Location…"), как в примере предыдущего раздела этой главы.
На всех остальных странице "защищенной зоны" должен находить-ся сценарий проверки содержимого переменной auth (то есть той, чье имя совпадает с именем поставленного cookie) — то есть точно такой же скрипт, как и на той, где cookie ставился, только строка проверки должна выглядеть как
if (substr($k, 0, -2)=="$auth"),
ну и, естественно, самой команды установки cookie быть не долж-но. Дальнейший текст страницы — на усмотрение web-мастера: если логин с паролем, полученные из cookie, есть в файле паролей, то, скажем, вывести форму для закачки файлов, если нет — то вежливо распрощаться…
Желательно также сделать страницу "выхода", включив в нее ко-манду установку cookie без параметров — SetCookie ("имя cookie").
В результате в том случае, если посетитель единожды прошел ав-торизацию, то он может спокойно посещать страницы "защищенной зоны" до тех пор, пока cookie "жив" на его компьютере. Он может за-крыть окно браузера и даже выключить компьютер, а потом, включив его, вновь зайти на тот же адрес, используя "Историю" браузера или "Закладки"- и он будет авторизован, если время жизни cookie не ис-текло. Зато те, кто "подглядит" этот адрес или украдет закладку, но не будет иметь соответствующего cookie, авторизованы не будут
В отличие от предыдущего способа, данный вариант не является абсолютно надежным в плане сохранности в тайне логина с паролем. Во-первых, cookie с этими данными сохраняется на компьютере посе-тителя, а значит, теоретически может быть с него похищен (тем, кто просто сядет за этот компьютер и скопирует нужный cookie из той папки, где они хранятся). Во-вторых, возможность захода на web-страницу "защищенной зоны" в течение некоторого времени без не-обходимости ввода логина и пароля может привести и к нежелатель-ным последствиям — если посетитель забудет зайти на страницу выхо-да, то любой, кто воспользуется его компьютером до истечения срока действия cookie, с точки зрения сервера будет вполне легальным пользователем и сможет устроить истинному владельцу логина нема-ло проблем.
Стоит сказать, что использовать имя cookie как переменную можно только в том случае, если в файле настройки PHP — php.ini — есть па-раметр register_globals. Там, где этого параметра нет (например, в PHP версии 4.2 и выше он по умолчанию неактивен), работать с cookies как с обычными переменными не получится. Информацию, помещен-ную в них, придется получать из массива с названием $HTTP_COOKIE_VARS (он создается автоматически при обнаруже-нии cookies от данного сайта), используя имя нужного cookie в каче-стве индекса:
<?php
$rez=0;
foreach (file("passw/passwr") as $k)
{
if (substr($k, 0, -2)==$HTTP_COOKIE_VARS['auth'])
{
$rez=1;
}
}
…
В PHP версии 4.1 и выше вместо $HTTP_COOKIE_VARS можно использовать массив $_COOKIE.
Надеюсь, вы поняли, что все эти проверки логинов и паролей в cookies и передающихся между окнами переменных, о которых так подробно рассказывается в этом и предыдущем пунктах, предназна-чены для одной цели — чтобы тот, кто каким бы то ни было образом узнал бы адрес страницы из "защищенной зоны" и зашел бы на эту страницу путем набора ее адреса в адресной строке браузера (или с помощью ярлыка в "Избранном"), не мог бы увидеть на ней то же са-мое, что и добросовестно прошедшие авторизацию посетители. Чтобы не приходилось запрашивать от посетителей пароль и логин на каж-дой странице, где есть возможность совершить те действия, которые крайне нежелательно позволять делать всем подряд — как, например, загрузка файлов или просмотр личной почты — а дать посетителям возможность, единожды введя авторизационные данные, свободно перемещаться по "защищенной зоне". Именно это и является основ-ной задачей описанных технологий.
Авторизация с помощью сессий
Вы, наверное, уже заметили особенность обоих вышеописанных способов авторизации — проверка правильности логина и пароля осу-ществляется на каждой странице, где требуется авторизованный дос-туп. Если посетителей на сайте не очень много, то это вполне допус-тимо, однако при большом числе авторизованных посетителей на-грузка на web-сервер может оказаться немалой.
В связи с этим возникает вопрос: а нельзя ли как-нибудь избежать необходимости каждый раз осуществлять проверку логина и пароля посетителя? Чтобы, единожды авторизовав посетителя, впоследствии предоставлять ему доступ на страницы защищенной зоны без каких-либо проверок? Именно так, кстати, действует защита на основе средств web-сервера — файлов .htaccess, описанная в первом разделе главы. Но можно ли сделать то же самое средствами PHP? Да и вооб-ще использовать cookie для хранения паролей не очень желательно: его содержимое может узнать любой, кто воспользуется компьюте-ром, на котором этот cookie сохранен (многие браузеры хранят cookie в предназначенной для них папке, даже если "время жизни" cookie истекло и он больше не принимается сервером).
Напрашивается первое предложение: а почему бы, например, после успешной авторизации не отправить посетителю cookie с какой-либо пометкой (например, устанавливать в 1 значение переменной в этом cookie), а впоследствии проверять не наличие записанных в cookie ло-гина и пароля в файле паролей или базе данных, а присутствие в cookie этой самой пометки, одинаковой для всех, прошедших автори-зацию? Или даже сделать разные типы пометок и в зависимости от типа предоставлять посетителю разные возможности на сайте?
Сделать-то так можно, да вот устойчивость такой системы автори-зации к взлому будет не особо великой. Злоумышленнику будет дос-таточно узнать, что за пометку помещает сценарий авторизации в cookie, чтобы получить к защищенной зоне полный доступ — просто вручную создав такой cookie. (А если при проверке "пометки" ис-пользовался не элемент массива $HTTP_COOKIE_VARS, а одно-именная переменная, то и просто подставив ее значение в адресной строке при заходе на страницу с такой проверкой: например, page.php?auth=1.) Кроме того, просмотреть значение cookie на компьютере посетителя и узнать, какие его имя и значение являются "пометкой", тоже не так трудно.
Но самое главное — посетители нередко отключают использование cookie при своих путешествиях по Интернету. При отключенных cookie описанная выше система авторизации на их основе работать не будет.
Как же быть?
Следует использовать весьма интересный механизм сессий, поя-вившийся в 4-й версии PHP.
Сессии
"Сессия" — несколько абстрактное понятие, означающее нечто вро-де "законченного периода работы с сайтом". Например, в сессию могут входить такие действия, как "приход на сайт — загрузка данных — уход с сайта". Иногда определения сессии разнятся в своей форму-лировке, но суть примерно такая.
Так вот — с помощью команд "поддержки сессий" PHP можно при заходе посетителя на сайт запоминать какие-либо переменные и по-том эти переменные считывать или изменять на других страницах этого сайта. При этом — обратите внимание — в браузер посетителя пе-редаются отнюдь не сами эти переменные, а некий пароль, по кото-рому сервер впоследствии этот браузер узнает и восстановит именно те значения переменных, которые были установлены для данного по-сетителя.
Иными словами — работа механизма сессий в PHP происходит так. Когда посетитель заходит на сайт и для него устанавливаются какие-либо переменные (сам ли он их вводит или, скажем, они берутся из базы данных), то команды начала и регистрации сессии сохраняют эти переменные в определенном месте на самом сервере (в специаль-ном файле в папке временных файлов сервера, рис.8.7, 10.8).
Рис.8.7. Файлы с данными сессий
в папке временных файлов сервера.
Имена файлов соответствуют идентификаторам сессий.
Рис.8.8. Содержимое одного из таких файлов.
В сессии сохранены переменные: legus, wq1, wq2, wq3.
Если у посетителя браузер принимает cookie, то ему высылается cookie (с определенным именем — по умолчанию "PHPSESSID"), со-держащий так называемый "идентификатор сессии" (рис.8.9), а если нет, то web-сервер автоматически помещает данный идентификатор в переменную PHPSESSID в каждую ссылку (рис.8.10) на выдаваемых посетителю страницах сайта (естественно, "внутреннюю" — то есть ве-дущую на другие страницы того же самого сайта, с тем же самым до-менным именем). Таким образом, идентификатор передается на сер-вер при каждом заходе посетителя на какую-либо из страниц сайта, будучи либо взятым из соответствующего cookie, установленного по-сетителю при открытии сессии, либо из адресной строки ссылки, куда этот идентификатор автоматически помещается web-сервером.
Рис.8.9. Содержимое сookie с идентификатором сессии.
Рис.8.10. Ссылка с идентификатором сессии.
Как только сервер получает от посетителя определенный иденти-фикатор сессии, то он передает сценарию на той странице, на кото-рую тот зашел, все сохраненные переменные, установленные для это-го посетителя, и сценарий может их свободно использовать: читать, изменять, уничтожать. При переходе на следующую страницу сайта все изменения будут сохранены.
Идентификатор сессии каждый раз создается новый, и алгоритм генерации довольно хороший — вероятность того, что для какой-либо последовательности символов на определенном сервере будет суще-ствовать набор сохраненных переменных, пренебрежимо мала. Еще меньше вероятность совпадения двух идентификаторов сессий, так что разные посетители сайта ну никак не смогут получить значения переменных друг друга.
Бесспорно, набор сохраненных переменных, относящихся к одной сессии, будет существовать на сервере не вечно. В параметрах файла конфигурации PHP — php.ini — указывается, какое время жизни уста-навливается для cookie с идентификатором сессии (по умолчанию 0 — то есть до закрытия окна браузера и всех открытых из него окон), а также через какое время данные сессий из папки временных файлов удаляются физически (рис.8.11). Кроме того, существует специальная команда "разрушения сессии", которая при своем выполнении унич-тожает сохраненные в папке временных файлов данные сессии и тем самым делает недействительным идентификатор сессии. Параметры устанавливаемых cookie, в частности, их "время жизни" также можно задать специальной командой в сценарии на PHP, однако время хра-нения данных сессии в папке временных файлов определяется пара-метром в php.ini, так что при использовании виртуального хостинга вам не всегда удастся настроить работу с сессиями полностью так, как вам бы хотелось.
Рис.8.11. Файл php.ini, раздел настроек параметров сессий.
Чтобы использовать в сценарии на странице возможности работы с сессиями, необходимо включить в него команду session_start() (Так как при работе с сессиями используются cookie, то данная команда должна находиться в начале страницы, перед какими-либо выводимыми в браузер данными), — как при первоначальной установке перемен-ных, так и при последующей работе с ними (Если в файле php.ini установлен в 1 параметр session.auto_start, то это делать не обязательно). Чтобы указать, какие переменные следует сохранять в качестве данных сессии, следует ис-пользовать команду session_register("имя первой пере-менной", "имя второй переменной",… и т.д.), а чтобы закрыть сессию — команду session_destroy(). При закрытии сес-сии переменные, переданные сценарию с ее помощью, не обнуляются (последнее делает команда session_unset();), так что их можно использовать и в остальной части сценария.
Переменные сессии доступны на сценариях сайта по своим изна-чальным именам — скажем, если командой session_register пе-ременная $a была зарегистрирована в качестве сессионной, то ее зна-чение будет доступно под тем же самым именем — $a — на всех страни-цах сайта, где используются сессии (т.е. в их начале размещена ко-манда session_start()).
Однако в целях большей безопасности лучше работать в сценарии с переменными сессии через автоматически создаваемые массивы $HTTP_SESSION_VARS и (в PHP версий 4.1 и старше) — $_SESSION, используя одноименные этим переменным элементы этих массивов. Дело в том, что тогда сценарий будет огражден от возможных попы-ток злоумышленников передать ему значения этих переменных через указание их в адресной строке в том случае, если сессия не была от-крыта (в указанные массивы попадают те и только те данные, что бы-ли получены с сессией). Такая передача может привести, скажем, к тому, что переменная — пометка об успешном прохождении авториза-ции будет получена сценарием не из данных сессии (в которых она может появиться только после успешного ввода посетителем пра-вильных логина и пароля), а от злоумышленника.
Сценарий авторизации
Алгоритм сценария прост. После определения допустимости по-лученных от посетителя каким бы то ни было образом (вводом в фор-му или в диалоговое окно авторизации) логина и пароля открывается сессия и в ней регистрируется переменная — указатель на успешную авторизацию, которой присваивается определенное значение. На каж-дой странице "защищенной зоны" проверяется значение полученной с данными сессии этой переменной (а, как вы помните, берется оно не из отправляемых браузером посетителя данных, а из созданного во временной директории сервера файла с данными сессии — браузер по-сетителя сообщает лишь идентификатор этого файла), и если оно сов-падает с обозначающим успешную авторизацию, то посетитель до-пускается к работе со страницей, если же нет — то доступ к странице не разрешается. На странице "выхода" из защищенной зоны распола-гается команда session_destroy();, после выполнения которой идентификатор сессии "забывается" сервером и передача сценарию переменной — указателя на успешную авторизацию происходить пере-стает — до нового прохождения авторизации.
Начало сценария на странице проверки логина с паролем может быть таким:
<?php
$rez=0;
foreach (file("passw/passwr") as $k)
{if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")
{$rez=1;}}
if ($rez!=1) {Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\""); Header("HTTP/1.0 401 Unauthorized");
…текст страницы, выдающейся посетителю в слу-чае нажатия им кнопки "Отмена"…
exit;}
или таким (если логин и пароль передаются из формы в перемен-ных $login и $pass):
<?php
$rez=0;
foreach (file("passw/passwr") as $k)
{if (substr($k, 0, -2)=="$login $pass"){$rez=1; }}
if ($rez!=1) {…команды вывода текста страницы, выдающейся посетителю в случае ввода неправильных логина и пароля…
exit;}
Оба варианта были подробно рассмотрены в двух предыдущих разделах этой главы. В результате их выполнения нижеследующий текст сценария будет выполняться только в том случае, если введен-ные посетителем логин или пароль есть в файле логинов и паролей (имеющем в данном случае имя "passwr").
Продолжение же сценария довольно простое. Создаем сессию…
session_start();
…регистрируем переменную:
session_register("auth");
…и устанавливаем ей определенное значение — дабы потом его и проверять.
$auth=1;
Собственно, и все.
?>
Дальше следует текст страницы, которую посетитель должен уви-деть сразу же после успешной авторизации.
Так как идентификатор сессии обычно сохраняется в cookie, то вышеприведенный код должен стоять в самом начале страницы — что-бы сервер имел возможность работать с cookies: отправить cookie с идентификатором сессии браузеру посетителя. (Если браузер посети-теля не принимает cookie, то идентификатор сессии будет автомати-чески присоединяться ко всем найденным на данной странице ссыл-кам на другие ресурсы сайта.)
На каждой странице "защищенной зоны", в самом ее начале нужно поставить код
<?php
$auth=0;
session_start();
if ($auth!=1)
{…команды вывода текста страницы, выдающейся посетителю в случае попытки несанкционированного доступа…
exit;}
…и все, что после этого кода, будет выполнено и/или выдано посе-тителю только в том случае, если он успешно прошел авторизацию на первой странице. При заходе на страницу "защищенной зоны" брау-зер посетителя перешлет серверу cookie с идентификатором сессии, а сервер возьмет из своего временного хранилища значение всех пере-менных сессии и передаст их сценарию.
Обратите внимание на необходимость присвоения переменной-"пометки о санкционированности доступа" нулевого значения в нача-ле сценария — о причине этого было сказано выше.
Страница выхода из "защищенной зоны" должна содержать код (Если в файле php.ini установлен в 1 параметр session.auto_start, то ука-зывать команду session_start() на каждой странице, где используются пере-менные сессии или производятся действия с самой сессией, не обязательно)
<?php
session_start();
session_destroy();
?>
После его выполнения для посещения страниц "защищенной зоны" вновь потребуется авторизация.
Если посетитель не воспользовался страницей выхода из защищен-ной зоны, то время, в течение которого из его браузера можно по-пасть на другие ее страницы, определяется настройками в файле php.ini. По умолчанию cookie с идентификатором сессии, устанавли-ваемый посетителю, существует до закрытия всех окон браузера, а сами данные сессии хранятся несколько часов. Существует команда session_set_cookie_params(), с помощью которой можно ус-тановить другое "время жизни" cookie, однако для изменения настро-ек в файле php.ini необходимо иметь права администратора для web-сервера.
Посредством сессий можно передавать между страницами сайта и другие данные. Например, при создании сценария Интернет-магазина, витрина которого занимает больше чем одну страницу, данные о зака-зываемых посетителем товарах имеет смысл передавать по страницам сайта в переменных сессии для последующего их оформления на спе-циальной странице как заказа, как, собственно, и делается на боль-шинстве подобных ресурсов Сети.
Так что, как видите, никаких особых секретов в технологии огра-ничения доступа нет. Авторизация пользователей на многих службах хостинга, web-интерфейсов почтовых систем, Интернет-форумах обычно строятся на тех же принципах, что и приведенные выше сце-нарии.
Реализовав данные приемы на своем сайте, вы можете, например, вполне спокойно приглашать к себе на работу ведущих отдельных разделов вашего сайта. Все компоненты для их удобной и безопасной работы у вас уже есть: и "папкопотрошилка", и "закачиватель фай-лов", теперь вот еще и "защищенная зона"… Разве что стоит добавить еще и нечто вроде "файлового менеджера", чтобы посетители могли и удалять, и переименовывать загруженные ими файлы. Но об этом — еще через пару глав.
Пример сценария
Вот пример сценария, в котором используется авторизация на основе заголовка WWW-Authenticate. Он состоит из двух страниц — на первой логин с паролем прове-ряются и в том случае, если они есть в файле паролей, то посетителю выводится форма для загрузки файла. На второй странице осуществляется загрузка файла.
Файл 1.
<?php
$rez=0;
foreach (file("passw/passwr") as $k)
{
if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")
{$rez=1;}
}
if ($rez!=1)
{
Header("WWW-Authenticate: Basic realm=\"Защищенная зона"\"");
Header("HTTP/1.0 401 Unauthorized");
echo ("<p>Доступ закрыт!</p>");
exit;
}
?>
<FORM ENCTYPE="multipart/form-data" ACTION="Файл 2" METHOD=POST>Закачать файл:<INPUT NAME="zak" TYPE="file">
<INPUT TYPE="submit" VALUE="Закачать"></FORM>
Файл 2.
<?php
$rez=0;
foreach (file("passw/passwr") as $k)
{
if (substr($k, 0, -2)=="$PHP_AUTH_USER $PHP_AUTH_PW")
{ $rez=1; }
}
if ($rez!=1)
{echo ("<p>Доступ закрыт!</p>");exit;}
if ($zak=="none")
{echo ("Вы забыли указать файл…");}
elseif (copy($zak, "папка для файлов/$zak_name"))
{echo("Файл $zak_name загружен");}
else
{ echo("Не удалось скопировать $zak_name");}
?>