В данном фрагменте текста страницы моего сайта я хочу продавать контекстные ссылки

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

13 сентября 2003 - Администратор

 Если вы хотя бы иногда посещаете сайты, на которых есть "защищенная зона", то есть часть, доступ на которую возможен лишь по определенным логину и паролю (например, почтовые службы с web-интерфейсом или сервисы хостинга), то вас наверняка интересовало, как эта авторизация происходит. И наверняка вам хотелось устроить то же самое и на вашем сайте, ведь необходимость в этом иногда возникает. Ниже будет рассказано о технологиях авторизации доступа, основанных на средствах web-сервера и технологии PHP. Думается, значение слова "авторизация" вам понятно - это не что иное, как обеспечение возможности доступа к чему-либо тем и только тем пользователям, которые знают определенные кодовые слова - логин и пароль.

Антон Орлов

 

Авторизация средствами web-сервера

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

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

Авторизация с помощью сессий

 

Данный текст - глава из книги Антона Орлова "PHP: полезные приемы", вышедшей в издательстве "Горячая линия - Телеком".
Знаете, как старые "компьютерщики" учатся новому языку программирования? Они не изучают толстенные учебники, не штудируют конспекты и руководства. Им не нужны премудрые преподаватели, они не корпят над тщательно выполняемыми уроками. Им достаточно всего лишь краткого описания языка и нескольких уже готовых программ, написанных на нем. На основании анализа этого они язык и осваивают.

Если вы хотя бы иногда посещаете сайты, на которых есть "защищенная зона", то есть часть, доступ на которую возможен лишь по определенным логину и паролю (например, почтовые службы с web-интерфейсом или сервисы хостинга), то вас наверняка интересовало, как эта авторизация происходит. И наверняка вам хотелось устроить то же самое и на вашем сайте, ведь необходимость в этом иногда возникает. Ниже будет рассказано о технологиях авторизации доступа, основанных на средствах web-сервера и технологии PHP. Думается, значение слова "авторизация" вам понятно - это не что иное, как обеспечение возможности доступа к чему-либо тем и только тем пользователям, которые знают определенные кодовые слова - логин и пароль.

Авторизация средствами web-сервера

Для того, чтобы к файлам, находящимся в какой-либо директории, могли иметь доступ лишь определенные посетители, знающие два кодовых слова (логин и пароль), можно использовать встроенные в Web-сервер Apache средства ограничения доступа.

В конфигурационных файлах Apache есть специальная строчка - 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).

Вот и все! Теперь при попытке запроса любого ресурса из защищенной папки (в том числе и картинок, включенных в другие страницы тэгом

Запрос на вход в папку
Рис.8.1. Запрос на вход в папку.

 

Пароль на папку средствами web-сервера? Достаточно двух файлов - .htaccess и .htpasswd...
Рис.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, размещающийся на странице, посетители увидеть не смогут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. Им мы сейчас и займемся.

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

Cookie - это файл в специальном формате, который присылается сервером браузеру посетителя сайта, расположенном на этом сервере (рис.8.5). Браузер, если он поддерживает cookie (и эта поддержка в нем не отключена), помещает его в особое место и впоследствии отправляет назад на сервер при поступлении от него запроса. Иными словами, cookie позволяет серверу хранить свою информацию на компьютерах посетителей и считывать ее оттуда при необходимости. (Современные стандарты безопасности предусматривают, что каждый сервер2 может получить назад только те cookie, которые были установлены лично им, так что даже о том, какие сайты еще посещал посетитель, с помощью cookie узнать нельзя.)

Cookie изнутри
Рис.8.5. 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>
Авторизация на основе cookies. Просто форма...
Рис.8.6. Авторизация на основе cookies. Просто форма...

На той странице, имя которой указано в параметре ACTION заголовка формы, введенные данные проверяются, и в том случае, если такие логин и пароль имеются в файле паролей, браузеру посетителя отсылается cookie, куда эти логин с паролем записываются.

 <?php
        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
        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 и передающихся между окнами переменных, о которых так подробно рассказывается в этом и предыдущем разделах, предназначены для одной цели - чтобы тот, кто каким бы то ни было образом узнал бы адрес страницы из "защищенной зоны" и зашел бы на эту страницу путем набора ее адреса в адресной строке браузера (или с помощью ярлыка в "Избранном"), не мог бы увидеть на ней то же самое, что и добросовестно прошедшие авторизацию посетители. Чтобы не приходилось запрашивать от посетителей пароль и логин на каждой странице, где есть возможность совершить те действия, которые крайне нежелательно позволять делать всем подряд - как, например, загрузка файлов или просмотр личной почты - а дать посетителям возможность, единожды введя авторизационные данные, свободно перемещаться по "защищенной зоне". Именно это и является основной задачей описанных технологий.

[1] - Вернее, смогут лишь в том случае, если данный код располагается в файле не с тем расширением, которое указано в настройках web-сервера как признак страниц с программами на PHP.
[2] - Вернее, узел с собственным именем (любого уровня).

 

 

Авторизация с помощью сессий

Вы, наверное, уже заметили особенность обоих вышеописанных способов авторизации - проверка правильности логина и пароля осуществляется на каждой странице, где требуется авторизованный доступ. Если посетителей на сайте не очень много, то это вполне допустимо, однако при большом числе авторизованных посетителей нагрузка на 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, 8,8).

Файлы с данными сессий в папке временных файлов сервера. Имена файлов соответствуют идентификаторам сессий.
Рис.8.7. Файлы с данными сессий в папке временных файлов сервера. Имена файлов соответствуют идентификаторам сессий.

 

Cодержимое одного из таких файлов. В сессии сохранены переменные: legus, wq1, wq2, wq3.
Рис.8.8. Содержимое одного из таких файлов. В сессии сохранены переменные: legus, wq1, wq2, wq3.

Если у посетителя браузер принимает cookie, то ему высылается cookie (с определенным именем - по умолчанию "PHPSESSID"), содержащий так называемый "идентификатор сессии" (рис.8.9), а если нет, то web-сервер автоматически помещает данный идентификатор в переменную PHPSESSID в каждую ссылку (рис.8.10) на выдаваемых посетителю страницах сайта (естественно, "внутреннюю" - то есть ведущую на другие страницы того же самого сайта, с тем же самым доменным именем). Таким образом, идентификатор передается на сервер при каждом заходе посетителя на какую-либо из страниц сайта. При этом идентификатор выбирается либо из соответствующего cookie, установленного посетителю при открытии сессии, либо из адресной строки ссылки, куда этот идентификатор автоматически помещается web-сервером.

Содержимое сookie с идентификатором сессии.
Рис.8.9. Содержимое сookie с идентификатором сессии.

 

Ссылка с идентификатором сессии.
Рис.8.10. Ссылка с идентификатором сессии.

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

Каждый раз создаётся новый идентификтор сессии. Благодаря хорошему алгоритму генерации вероятность того, что для какой-либо последовательности символов на определенном сервере будет существовать набор сохраненных переменных, пренебрежимо мала. Еще меньше вероятность совпадения двух идентификаторов сессий, так что разные посетители сайта ну никак не смогут получить значения переменных друг друга.

Бесспорно, набор сохраненных переменных, относящихся к одной сессии, будет существовать на сервере не вечно. В параметрах файла конфигурации PHP - php.ini - указывается, какое время жизни устанавливается для cookie с идентификатором сессии (по умолчанию 0 - то есть до закрытия окна браузера и всех открытых из него окон), а также через какое время данные сессий из папки временных файлов удаляются физически (рис.8.11). Кроме того, существует специальная команда "разрушения сессии", которая при своем выполнении уничтожает сохраненные в папке временных файлов данные сессии и тем самым делает недействительным идентификатор сессии. Параметры устанавливаемых cookie, в частности, их "время жизни" также можно задать специальной командой в сценарии на PHP, однако время хранения данных сессии в папке временных файлов определяется параметром в php.ini, так что при использовании виртуального хостинга вам не всегда удастся настроить работу с сессиями полностью так, как вам бы хотелось.

Файл php.ini, раздел настроек параметров сессий.
Рис.8.11. Файл php.ini, раздел настроек параметров сессий.

Чтобы использовать в сценарии на странице возможности работы с сессиями, необходимо включить в него команду session_start()3 , - как при первоначальной установке переменных, так и при последующей работе с ними4. Чтобы указать, какие переменные следует сохранять в качестве данных сессии, следует использовать команду session_register("имя первой переменной", "имя второй переменной",... и т.д.), а чтобы закрыть сессию - команду session_destroy(). При закрытии сессии переменные, переданные сценарию с ее помощью, не обнуляются (последнее делает команда session_unset();), так что их можно использовать и в остальной части сценария.

Переменные сессии доступны на сценариях сайта по своим изначальным именам - скажем, если командой session_register переменная $a была зарегистрирована в качестве сессионной, то ее значение будет доступно под тем же самым именем - $a - на всех страницах сайта, где используются сессии (т.е. в их начале размещена команда session_start()).

Однако в целях безопасности лучше работать в сценарии с переменными сессии через автоматически создаваемые массивы $HTTP_SESSION_VARS и (в PHP версий 4.1 и старше) $_SESSION, используя одноименные с переменными элементы этих массивов. Дело в том, что в этом случае сценарий будет огражден от возможных попыток злоумышленников передать ему значения этих переменных через указание их в адресной строке, если сессия не была открыта (в указанные массивы попадают те и только те данные, что были получены с сессией). Такая передача может привести, скажем, к тому, что переменная - пометка об успешном прохождении авторизации будет получена сценарием не из данных сессии (в которых она может появиться только после успешного ввода посетителем правильных логина и пароля), а от злоумышленника.

Сценарий авторизации

Алгоритм сценария прост. После определения допустимости полученных от посетителя каким бы то ни было образом (вводом в форму или в диалоговое окно авторизации) логина и пароля открывается сессия и в ней регистрируется переменная - указатель на успешную авторизацию, которой присваивается определенное значение. На каждой странице "защищенной зоны" проверяется значение полученной с данными сессии этой переменной (а, как вы помните, берется оно не из отправляемых браузером посетителя данных, а из созданного во временной директории сервера файла с данными сессии - браузер посетителя сообщает лишь идентификатор этого файла), и если оно совпадает с обозначающим успешную авторизацию, то посетитель допускается к работе со страницей, если же нет - то доступ к странице не разрешается. На странице "выхода" из защищенной зоны располагается команда session_destroy();, после выполнения которой идентификатор сессии "забывается" сервером и передача сценарию переменной - указателя на успешную авторизацию более не происходит - до нового прохождения авторизации.

Начало сценария на странице проверки логина с паролем может быть таким:

       <?php
        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
        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
        session_start();
        if ($auth!=1)
        {...текст страницы, выдающейся посетителю в случае попытки несанкционированного досту-па...
        exit;}

...и все, что после этого кода, будет выполнено и/или выдано посетителю только в том случае, если он успешно прошел авторизацию на первой странице. При заходе на страницу "защищенной зоны" браузер посетителя перешлет серверу cookie с идентификатором сессии, а сервер возьмет из своего временного хранилища значение всех переменных сессии и передаст их сценарию.

Страница выхода из "защищенной зоны" должна содержать код5

        <?php
        session_start();
        session_destroy();
        ?>

После его выполнения для посещения страниц "защищенной зоны" вновь потребуется авторизация.

Если посетитель не воспользовался страницей выхода из защищенной зоны, то время, в течение которого из его браузера можно попасть на другие ее страницы, определяется настройками в файле php.ini. По умолчанию cookie с идентификатором сессии, устанавливаемым данному посетителю, существует до закрытия всех окон браузера, а сами данные сессии хранятся несколько часов. Существует команда session_set_cookie_params() (подробную информацию о ней смотрите в Описании PHP), с помощью которой можно установить другое "время жизни" cookie, однако для изменения настроек в файле php.ini необходимо иметь права администратора для web-сервера.

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

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

Реализовав данные приемы на своем сайте, вы можете, например, вполне спокойно приглашать к себе на работу ведущих отдельных разделов вашего сайта. Все компоненты для их удобной и безопасной работы у вас уже есть: и "папкопотрошилка", и "закачиватель файлов", теперь вот еще и "защищенная зона"... Разве что стоит добавить еще и нечто вроде "файлового менеджера", чтобы посетители могли и удалять, и переименовывать загруженные ими файлы. Но об этом - еще через пару глав.

[3] - Так как при работе с сессиями используются cookie, то данная команда должна находиться в начале страницы, перед какими-либо выводимыми в браузер данными.
[4] - Если в файле php.ini установлен в 1 параметр session.auto_start, то это делать не обязательно.
[5] - Если в файле php.ini установлен в 1 параметр session.auto_start, то указывать команду session_start() на каждой странице, где исполь-зуются переменные сессии или производятся действия с самой сессией, не обязательно.

 

 

Пример сценария
Вот пример сценария, в котором используется авторизация на основе заголовка WWW-Authenticate. Он состоит из двух страниц - на первой логин с паролем проверяются и в том случае, если они есть в файле паролей, то посетителю выводится форма для загрузки файла. На второй странице осуществляется загрузка файла.

Файл 1
  <?php
        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
        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");}
        ?>

Пояснения к сценарию загрузки файлов смотрите в предыдущей главе.

Комментарии (0)

Нет комментариев. Ваш будет первым!

Яндекс цитирования
В случае перепечатки материалов активная ссылка на cattus.ru обязательна
© 2004-2013 cattus.ru