PHP - авторизация доступа

Авторизация с помощью заголовка


В PHP есть команда Header - она позволяет отправить браузеру посетителя, запросившему страницу с содержащим эту команду сценарием, определенную служебную информацию - так называемый "заголовок". Существует довольно много вариантов заголовков (например, заголовок "Location: http://адрес" приведет к перенаправлению на указанный URL; то же самое, что и при использовании мета-тэга http-equiv с параметром "Refresh"), однако для авторизации нам потребуется заголовок "WWW-Authenticate".

Заголовок - это данные, передаваемые браузеру до передачи самой web-страницы, сообщающие ему некоторые параметры передаваемого файла или определенные команды. Список всех возможных заголовков, которые обязаны поддерживать современные браузеры, можно найти в спецификациях протокола 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, размещающийся на странице, посетители увидеть не смогут1. В этом случае команда проверки содержимого переменных $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). Для того, чтобы этот файл нельзя было загрузить через web-интерфейс, просто набрав его имя (и тем самым получив на экран все его содержимое), можете сделать это имя как можно более длинным и заковыристым (все равно оно фигурирует только в программном коде, то есть из Сети его узнать будет никак нельзя), а можете просто запретить чтение данного файла из Web, соответственно установив его атрибуты, например, в 770 (в CuteFTP это делается пунктом CHMOD из контекстного меню файла, рис.8.4).

Еще запретить чтение содержимого директорий из Web можно, указав в файле настроек web-сервера Apache (именующемся httpd.conf) в разделе описания соответствующего виртуального сервера параметр Location (например, deny from all - в три строки), однако доступ к файлу настроек web-сервера есть не всегда.

Рис.8.3. Простейший файл паролей.


Рис.8.4. Чтобы файл не "достали" из Интернета, установите его параметры вот так...

Итак, начнем сценарий. Командой file считаем файл построчно в массив...

Команда file помещает в массив указанный в ее параметре файл, помещая каждую строку файла в отдельный элемент массива.
...и начнем сравнивать пару "логин-пароль" каждой строчки файла (т.е. каждый элемент массива) с той парой, что мы получили от пользователя. Массив даже нет нужды именовать - достаточно просто указать команду file в цикле обработки всех элементов массива foreach (как упоминалось в Главе 3, этот оператор считывает каждый элемент указанного в его параметрах массива в переменную с именем, указанным после ключевого слова as, и выполняет для каждого элемента массива код, указанный в фигурных скобках).

<?php 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, следует выдать окно авторизации и получить от посетителя логин и пароль, а если равна - то выводить страницу дальше.

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 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. Им мы сейчас и займемся.


Содержание раздела