Бессильный auth php. HTTP Установка защиты на страницу используя MySQL и PHP

Возможно использовать функцию header() для отправки сообщения "Authentication Required" браузеру, заставив его показать окошко для ввода логина и пароля. Как только пользователь заполнит логин и пароль, ссылка, содержащая PHP-скрипт будет вызвана еще раз с предопределенными переменными PHP_AUTH_USER , PHP_AUTH_PW , и AUTH_TYPE , установленными в логин, пароль и тип аутентификации соответственно. Эти предопределенные переменные хранятся в массивах $_SERVER и $HTTP_SERVER_VARS . Поддерживаются оба типа: "Basic" и "Digest" (начиная с версии PHP 5.1.0). Подробнее смотрите функцию header() .

Пример фрагмента скрипта, который вынуждает клиента авторизоваться для просмотра страницы:

Пример #1 Пример Basic HTTP-аутентификации

if (!isset($_SERVER [ "PHP_AUTH_USER" ])) {
header ("WWW-Authenticate: Basic realm="My Realm"" );

echo "Текст, отправляемый в том случае,
если пользователь нажал кнопку Cancel"
;
exit;
} else {
echo
"

Hello { $_SERVER [ "PHP_AUTH_USER" ]} .

" ;
echo "

Вы ввели пароль { $_SERVER [ "PHP_AUTH_PW" ]} .

" ;
}
?>

Пример #2 Пример Digest HTTP-аутентификации

Это пример реализации простого скрипта Digest HTTP-аутентификации. За подробностями обращайтесь к » RFC 2617 .

$realm = "Запретная зона" ;

//user => password
$users = array("admin" => "mypass" , "guest" => "guest" );

if (empty($_SERVER [ "PHP_AUTH_DIGEST" ])) {
header ("HTTP/1.1 401 Unauthorized" );
header ("WWW-Authenticate: Digest realm="" . $realm .
"",qop="auth",nonce="" . uniqid (). "",opaque="" . md5 ($realm ). """ );

Die("Текст, отправляемый в том случае, если пользователь нажал кнопку Cancel" );
}

// анализируем переменную PHP_AUTH_DIGEST
if (!($data = http_digest_parse ($_SERVER [ "PHP_AUTH_DIGEST" ])) ||
!isset($users [ $data [ "username" ]]))
die("Неправильные данные!" );

// генерируем корректный ответ
$A1 = md5 ($data [ "username" ] . ":" . $realm . ":" . $users [ $data [ "username" ]]);
$A2 = md5 ($_SERVER [ "REQUEST_METHOD" ]. ":" . $data [ "uri" ]);
$valid_response = md5 ($A1 . ":" . $data [ "nonce" ]. ":" . $data [ "nc" ]. ":" . $data [ "cnonce" ]. ":" . $data [ "qop" ]. ":" . $A2 );

if ($data [ "response" ] != $valid_response )
die("Неправильные данные!" );

// ok, логин и пароль верны
echo "Вы вошли как: " . $data [ "username" ];

// функция разбора заголовка http auth
function http_digest_parse ($txt )
{
// защита от отсутствующих данных
$needed_parts = array("nonce" => 1 , "nc" => 1 , "cnonce" => 1 , "qop" => 1 , "username" => 1 , "uri" => 1 , "response" => 1 );
$data = array();
$keys = implode ("|" , array_keys ($needed_parts ));

Preg_match_all ("@(" . $keys . ")=(?:([\""])([^\2]+?)\2|([^\s,]+))@" , $txt , $matches , PREG_SET_ORDER );

Foreach ($matches as $m ) {
$data [ $m [ 1 ]] = $m [ 3 ] ? $m [ 3 ] : $m [ 4 ];
unset($needed_parts [ $m [ 1 ]]);
}

Return $needed_parts ? false : $data ;
}
?>

Замечание : Замечание касательно совместимости

Будьте особенно внимательны при указании HTTP-заголовков. Для того, чтобы гарантировать максимальную совместимость с наибольшим количеством различных клиентов, слово "Basic" должно быть написано с большой буквы "B", регион (realm) должен быть взят в двойные (не одинарные!) кавычки, и ровно один пробел должен предшествовать коду 401 в заголовке HTTP/1.0 401 . Параметры аутентификации должны разделяться запятыми, как это было показано в примере Digest аутентификации выше.

Вместо простого отображения на экране переменных PHP_AUTH_USER и PHP_AUTH_PW , вам, возможно, понадобится проверить их корректность. Используйте для этого запрос к базе данных или поиск пользователя в dbm-файле.

Вы можете пронаблюдать особенности работы браузера Internet Explorer. Он очень требователен к параметру передаваемых заголовков. Трюк с указанием заголовка WWW-Authenticate перед отправкой статуса HTTP/1.0 401 пока что работает для него.

Для того, чтобы предотвратить написание кем-либо скрипта, раскрывающего пароль к странице, которая использует внешнюю аутентификацию, переменные PHP_AUTH не устанавливаются в случае, если данная страница использует внешнюю аутентификацию и установлен безопасный режим . Несмотря на это, переменная REMOTE_USER может использоваться для аутентификации пользователя, прошедшего внешнюю аутентификацию. Таким образом, вы всегда можете воспользоваться переменной $_SERVER["REMOTE_USER"] .

Замечание : Замечание касательно конфигурации

PHP использует указание директивы AuthType для указания того, используется внешняя аутентификация или нет.

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

И Netscape Navigator и Internet Explorer очищают кэш аутентификации текущего окна для заданного региона (realm) при получении от сервера статуса 401. Это может использоваться для реализации принудительного выхода пользователя и повторного отображения диалогового окна для ввода имени пользователя и пароля. Некоторые разработчики используют это для ограничения авторизации по времени или для предоставления кнопки "Выход".

Пример #3 Пример HTTP-аутентификации с принудительным вводом новой пары логин/пароль

function authenticate () {
header ("WWW-Authenticate: Basic realm="Test Authentication System"" );
header ("HTTP/1.0 401 Unauthorized" );
echo "Вы должны ввести корректный логин и пароль для получения доступа к ресурсу \n" ;
exit;
}

if (!isset($_SERVER [ "PHP_AUTH_USER" ]) ||
($_POST [ "SeenBefore" ] == 1 && $_POST [ "OldAuth" ] == $_SERVER [ "PHP_AUTH_USER" ])) {
authenticate ();
} else {
echo "

Добро пожаловать: " . htmlspecialchars ($_SERVER [ "PHP_AUTH_USER" ]) . "
" ;
echo "Предыдущий логин: " . htmlspecialchars ($_REQUEST [ "OldAuth" ]);
echo "

\n" ;
echo "\n" ;
echo ". htmlspecialchars ($_SERVER [ "PHP_AUTH_USER" ]) . "\" />\n" ;
echo "\n" ;
echo "

\n" ;
}
?>

Это поведение не регламентируется стандартами HTTP Basic -аутентификации, следовательно, вы не должны зависеть от этого. Тестирование браузера Lynx показало, что Lynx не очищает кэш авторизации при получении от сервера статуса 401, и, нажав последовательно "Back", а затем "Forward" возможно открыть такую страницу, при условии, что требуемые атрибуты авторизации не изменились. Однако, пользователь может нажать клавишу "_" для очистки кеша аутентификации.

Для того, чтобы добиться корректной работы HTTP-аутентификации в IIS сервере с CGI версией PHP, вы должны отредактировать конфигурационную настройку IIS под названием "Directory Security ". Щелкните на надписи "Edit " и установите опцию "Anonymous Access ", все остальные поля должны остаться неотмеченными.

Замечание : Замечание касательно IIS:
Для того, чтобы HTTP-аутентификация корректно работала в IIS, в конфигурации PHP опция cgi.rfc2616_headers должна быть установлена значением 0 (значение по умолчанию).

Замечание :

В случае, если используется безопасный режим , UID текущего скрипта будет добавлен в realm -часть заголовка WWW-Authenticate .

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

Закрыть такую страницу можно несколькими взаимодополняющими друг друга способами:

  1. Защита паролем (логин/пароль) с помощью переменных $_SERVER["PHP_AUTH_USER"] и $_SERVER["PHP_AUTH_PW"] .
  2. Защита по IP адресу клиента с помощью переменной $_SERVER["REMOTE_ADDR"] .
  3. Защита по MAC адресу в локальных сетях (дополнительно к защите по IP ).

Разберем сначала первый способ, который является основным. Он позволяет закрыть доступ к странице по логину и паролю, таким образом доступ могут получить только люди знающие логин и пароль. К тому же их можно разделять по этому признаку и выдавать соответственно разную информацию для каждого. Реализуется с помощью выдачи специальных полей в заголовке протокола HTTP . Создадим функцию auth_send() :

" ,"

Ошибка аутентификации

" ,"

Обратитесь к администратору для получения логина и пароля.

" ,""; exit; }; ?>

Эту функция сообщает браузеру о том, что для доступа нужна авторизация по логину и паролю. И выдает также страницу в HTML для пользователя.

" ,"

Добро пожаловать!

" ,"

Вы зашли по логину ",$auth_user," и паролю ",$auth_pass,".

" ,""; ?>

Код проверки логина и пароля не слишком сложный в данном случае, так как реализован для одного человека. Логика работы проста, если нет переменной $_SERVER["PHP_AUTH_USER"] и $_SERVER["PHP_AUTH_PW"] или их значения не совпадают с нужными, то вызываете функцию auth_send() . Не забывайте, что в ней в конце вызывается exit , поэтому выполнение программы прекращается.

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

Ваш IP не найден!!!"; exit; }; ?>

Тут в строке $allowed_ips через пробел указаны IP адреса, которым разрешен доступ. Далее получаем массив с помощью explode() и делаем поиск адреса клиента из $_SERVER["REMOTE_ADDR"] . Я для поиска применил функцию array_search() , так как неверняка ее код реализованный на Си будет работать несколько быстрее, чем то, что мы можем написать на PHP с помощью циклов for или foreach . Но скорость тут не главное:)

И последняя ступень защиты это проверка MAC адреса. Она относится к разряду параноидальных и ее стоит использовать, если вы получаете доступ из локальной сети и данные, которые вы защищаете действительно очень важные. Я пока реализовал эту проверку только на системе Linux , в силу относительной простоты реализации. Но Вы можете ее попробовать реализовать под любую другую платформу. Пишем функцию:

Как линуксоиды уже поняли она основана на ARP таблице системы, доступ к которой можно получить с помощью файла /proc/net/arp . Функция ищет по строкам требуемый IP адрес и возвращает его MAC адрес:

Ваш IP=192.168.10.15 и MAC=00:04:31:E4:F8:37

В системе Windows возможно тоже есть какие-то способы получить MAC попроще, но из тех, которые реально работают, это вывод ARP таблицы системы командой:

C:\WINDOWS\>arp -a Интерфейс: 192.168.10.15 on Interface 0x1000003 Адрес IP Физический адрес Тип 192.168.10.1 00-50-22-b0-6a-aa динамический 192.168.10.2 00-0f-38-68-e9-e8 динамический 192.168.10.3 00-04-61-9e-26-09 динамический 192.168.10.5 00-0f-38-6a-b1-18 динамический

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

Ограничение доступа к какой-либо области сайта обычно выглядит
однообразно: каждому пользователю выдается логин и пароль или он сам
их выбирает, и для входа в защищенную часть сайта их нужно ввести. С технической же точки зрения для проверки пароля используются
разные методы. Для ввода логина и пароля может использоваться HTML-форма.
В этом случае пароль передается на сервер открытым текстом в POST-запросе.
Это неприемлемо, если пользователь сидит в локалке, где возможно
использование снифера. Для решения этой проблемы придуман метод
аутентификации с помощью хешей, при котором пароль не передается, а
передается хеш строка, зависящая от пароля, некоего одноразового
параметра и, возможно, еще от каких-либо параметров. Этот метод еще
называют challenge/response, поскольку при его использовании клиент
получает запрос с одноразовым параметром и посылает ответ, содержащий хеш. На уровне протокола HTTP 1.1 возможна аутентификация методом
Basic, что ни чем не лучше использования HTML-формы, и Digest, который
мы и рассмотрим подробно.

При использовании метода Digest, как уже было сказано, пароль
не передается, и его невозможно отснифить, однако есть и другая сторона
проблемы. Для того, чтобы проверить пароль, сервер должен вычислить
ответ и сравнить его с ответом клиента, следовательно, на сервере должен
храниться пароль или зависящие от него данные, необходимые для
прохождения аутентификации. Отсюда следует, что человек, получивший права
на чтение аккаунтов (например, с помощью SQL-injection), сможет получить
доступ к страницам, защищенным методом Digest. При использовании метода
Basic возможно хранение хешей вместо паролей, что не дает поднять права,
прочитав эти хеши (ниже мы увидим, что в Digest тоже могут храниться хеши,
но такие, что их знания достаточно для вычисления ответа). Таким образом, перед нами дилемма: либо наш пароль отснифят,
либо получат через web-уязвимость, которую кто-нибудь обязательно отыщет,
потому что кто ищет, тот всегда найдет. Есть метод аутентификации без
обоих этих недостатков - метод аутентификации на основе открытого ключа:
для проверки нужен открытый ключ, а для прохождения проверки - секретный,
однако в HTTP 1.1 такой метод не предусмотрен. RFC 2069
рекомендует использовать SSL, если защита так важна. Защищается только передача пароля, а контент не шифруется, так
что нет смысла защищать этим методом ресурсы, откуда пользователь
получает секретную информацию. Для них необходим SSL. А имеет смысл
защищать, например, форум или заливку контента на сайт. Итак, если хостинг не поддерживает SSL, а аутентификация должна
быть безопасной, то будем использовать Digest. В Apache предусмотрен модуль mod_digest. Для его использования
в конфиге (или в.htaccess) пишем:

AuthType Digest
AuthUserFile <файл>
AuthName <название защищаемой области>
Require valid_user

Файлы пользователей создаются утилитой
htdigest. Про mod_digest одно время появлялись сообщения, что он уязвим, так что,
возможно, там еще какие-нибудь проблемы обнаружатся. Кроме того, когда
я попытался его использовать у себя дома, получил ошибку
500 Server Internal Error. Кроме того, если добавление аккаунтов должно происходить
автоматически, и их должно быть много, они должны
храниться не в конфиге Апача, а в MySQL. Решение -
использовать PHP. В PHP нет встроенной поддержки этого
метода, поэтому его придется реализовать. Для этого необходимо изучить
этот метод подробно. Сразу замечу, что приведенная в этой статье
реализация работает только на Apache, так как полный доступ к заголовкам
запроса (функция apache_request_headers) работает только в Apache, а на
других серверах может отсутствовать. Нам же просто необходимо прочитать
заголовок Authorization.

Описание метода

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

WWW-Authenticate: Digest realm="secure area", nonce="123456123456"

realm - это название защищенной области, а nonce - одноразовое
значение. Есть еще необязательные параметры, которые мы обсуждать
не будем. Клиент повторяет запрос, добавив к нему заголовок такого вида:

Authorization: Digest realm="secure area", username="123", uri="/index.php", nonce="123456123456", response="1234567890abcdef1234567890abcdef"

Параметр uri должен совпадать с URI в запросе, а response - это
ответ, который вычисляется так:

response = H(H(A1) + ":" + nonce + ":" + H(A2))
H - хеш-функция, по умолчанию MD5
A1 = логин + ":" + realm + ":" + пароль
A2 = метод запроса + ":" + URI
метод запроса - это GET, POST и тд.

Как видим, A1 не зависит ни от запроса, ни от одноразового
значения, поэтому на сервере может храниться не пароль, а
H(A1). Именно так это реализовано в mod_digest в Apache.
Однако этих же данных достаточно и клиенту. Злоумышленник, получив
этот хеш, может вычислить ответ по приведенным выше формулам и
сформировать HTTP-запрос, например, с помощью программы
AccessDriver и ее инструмента HTTP
Debugger. Подробнее этот процесс будет показан ниже. Сервер должен проверить, является ли одноразовое значение
тем, которое было ранее выдано клиенту и не устарело ли оно.
Если ответ соответствует параметру nonce, но значение этого параметра
не актуально, выдается описанный выше ответ с кодом 401 с той лишь
разницей, что в заголовок WWW-Authenticate добавляется параметр
stale=true, указывающий, что в доступе отказано лишь по этой причине,
и следует повторить попытку, не запрашивая у пользователя новый пароль. Это, имхо, неудобно, поскольку если такая ситуация возникнет
при запросе POST или PUT с большим блоком данных, то клиенту придется
передать все данные дважды. Во избежание этого стандартом предусмотрен
заголовок Authentication-Info, в котором сервер может при ответе на
успешный запрос сообщить клиенту следующее одноразовое значение.
Синтаксис такой же, как у WWW-Authenticate, кроме того что nonce
заменяется на nextnonce. Однако, судя по результатам моих
экспериментов, Opera игнорирует этот заголовок. Другое решение: в соответствии с
RFC 2068 (HTTP/1.1), сервер может ответить раньше, чем завершится запрос,
чтобы клиент прервал ненужную передачу данных, но на Apache+PHP это
не реализуется, поскольку скрипт начинает выполняться только после того,
как Apache полностью получит и пропарсит запрос.

Хранение данных между запросами

В реализации метода challenge/response на PHP есть тонкий момент.
Одноразовый параметр формируется и выдается клиенту в одном ответе, а
проверяется уже в другом сеансе работы скрипта.
То есть его необходимо сохранить от одного вызова скрипта до другого, и для этого придется
использовать файлы или БД. В моем примере используются файлы с именами,
соответствующими одноразовым значениям, а в самих файлах записаны
IP-адреса клиентов, которым они выданы. В примере не реализован сбор
мусора: надо периодически удалять старые файлы.

Разбор кода

Этот скрипт проверяет только пароль, и работает независимо от
логина. В зависимости от успешности проверки выдаются простые ответы.

$realm = "secure area"; // Название защищаемой области
$pass = "pass"; // Пароль
$fileprefix = "./"; // Путь для файлов-меток, обозначающих валидность nonce

/* Сконструируем одноразовый параметр так, как рекомендуется в RFC2069, хотя можно и по-другому. Параметр, по рекомендации, должен зависеть от адреса клиента, текущего времени и секретной строки. */
$nonce = md5($_SERVER["REMOTE_ADDR"] . ":" . time() . ":MyCooolPrivateKey");

// Получаем заголовки
$headers = apache_request_headers();

// Флаг, который мы установим в TRUE при успешной проверке
$auth_success = FALSE;
$stale = "";

// Если нет заголовка Authorization, то нечего и проверять
if (isset($headers["Authorization"]))
{
$authorization = $headers["Authorization"];

/* Пропарсим заголовок с помощью регулярного выражения. Заголовок содержит слово "Digest" и список
пареметров вида param="value" или param=value через запятую. Это регулярное выражение соответствует одному такому параметру.
*/
preg_match_all("/(,|\s|^)(\w+)=("([^"]*)"|([\w\d]*))(,|$)/",
$authorization, $matches, PREG_SET_ORDER);

/* Теперь сформируем для удобства дальнейшей обработки массив, где ключи - названия параметров, а значения элементов массива -
значения параметров.
*/
$auth_params = Array();
for ($i = 0; $i < count($matches); $i++)
{
$match = $matches[$i];

/* Название всегда во второй группе скобок, в значениев зависимости от того, в кавычках оно или нет, может
быть в 4-й или 5-й группе. Для групп скобок, попавших
в нереализованную ветвь, в массиве пустая строка,
поэтому можно просто сложить значения.
*/
$auth_params[$match] = $match . $match;
}

/* Вычислим ответ, который соответствует
логину, введенному пользователем, нашему паролю и одноразовому параметру, переданному пользователем.
*/
$a1 = $auth_params["username"] . ":" . $auth_params["realm"] . ":" . $pass;
$a2 = $_SERVER["REQUEST_METHOD"] . ":" . $_SERVER["REQUEST_URI"];
$resp = md5(md5($a1) . ":" . $auth_params["nonce"] . ":" . md5($a2));

// Проверяем ответ.
if ($resp == $auth_params["response"])
{
//
Проверяем актуальность одноразового параметра
$fn = $fileprefix . $auth_params["nonce"];
if (@file_get_contents($fn) == $_SERVER["REMOTE_ADDR"])
{
unlink($fn); //
Больше этот параметр неактуален
$auth_success = TRUE; //
Аутентификация пройдена
} else
{
// Одноразовый параметр неактуален
$stale = ", stale=true";
}
}
}

if ($auth_success)
{
print("Digest auth test

print("Successfully authenticated\n");
var_dump($auth_params);

print("");

} else
{
file_put_contents($fileprefix . $nonce, $_SERVER["REMOTE_ADDR"]);

$proto = $_SERVER["SERVER_PROTOCOL"];
Header("$proto 401 Not Authorized");
Header("WWW-Authenticate: Digest realm=\"$realm\", nonce=\"$nonce\"$stale");

print("Digest auth test

");
print("You must authenticate with Digest method");
print("
");
}

Прохождение Digest Auth при известном H(A1)

Покажу на примере, как проходить проверку, если пароль неизвестен,
но известен H(A1). Для этого, как уже было сказано, понадобится
AccessDriver. Расчеты хешей я буду делать вызывая из командной строки
PHP CLI. Защищенная страница пусть находится по адресу
http://mrblack.local/auth1.php, а хеш H(A1) равен "a8fb5b2d780a7bf0782207a51a013f04".

Открываем AccessDriver->Tools->HTTP Debugger и вбиваем адрес
"http://mrblack.local/auth1.php". Жмем "Connect". Получаем:

HTTP Header = HTTP/1.1 401 Authorization Required
HTTP Header = Date: Mon, 04 Jul 2005 08:09:17 GMT
HTTP Header = Server: Apache/1.3.31 (Win32) PHP/5.0.2
HTTP Header = X-Powered-By: PHP/5.0.2
HTTP Header = WWW-Authenticate: Digest realm="secure area", nonce="5925bea78552224abda11bfe318a8a03"
HTTP Header = Connection: close
HTTP Header = Content-Type: text/html

Открываем консоль, переходим в папку с PHP и вбиваем такую команду:

php -r "print md5("a8fb5b2d780a7bf0782207a51a013f04:
: ".md5("GET:http://mrblack.local/auth1.php"));"

Получаем искомый Digest-ответ: c6d0af0db239d75c
3f59640a4896d096
Теперь в AccessDriver ставим галочку "Header Data", копируем в появившееся
поле заголовки, которые были посланы в прошлом запросе, и дописываем к ним
Authorization. Вот что получается:

GET http://mrblack.local/auth1.php HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*
Accept-Language: en-us,en;q=0.5
User-Agent: Mozilla compatible
Host: mrblack.local
Pragma: no-cache
Authorization: Digest username="mrblack", realm="secure area", nonce="5925bea78552224ab
da11bfe318a8a03", uri="http://mrblack.local/auth1.php", response="c6d0af0db239d75c3f59
640a4896d096"

Жмем "Connect". Получаем результат:

Буквально вчера я решил приступить к разработке личного кабинете одного безымянного сайта. Передо мной встал вопрос по поводу внешнего вида формы авторизации, мне, откровенно, не хотелось заниматься дизайном формы, делать всплывающие подсказки, да и в целом уделять огромное внимание форме. Я сдуру ринулся искать готовые решения, увидел достаточно безвкусных форм. Думал подыскать какой-нибудь готовый компонент, готовое расширение, но в большинстве своём они разочаровали меня. Блуждая по форумам, мне показалось интересным разыграть карту с HTTP Authentication: Basic. Я отправился читать мануал, прежде был недостаточно осведомлён об данном способе. Далее и начались проблемы.

Деваться было некуда, пошастал по сети в поисках сайтов, что возьмутся генерировать формы, код, стили. Тщетны оказались мои попытки облегчить себе задачу. Выход невелик, я приступил к разрешению проблемы. Одно время я старался сварганить решение с помощью.htaccess, нашел такое.

RewriteCond %{HTTP:Authorization} ^Basic.* RewriteRule (.*) index.php?authorization=%{HTTP:Authorization}

Увы, у меня возникли проблемы с Joomla, у неё и так всё неплохо в.htaccess, а тут я еще со своим кодом. Уделив время на перебор различных комбинаций, я согласился с тем, что переписывать.htaccess я не буду. С начала код был очень сырым, настолько сырым, что я только-только его скопипастил.

Hello {$_SERVER["PHP_AUTH_USER"]}.

"; echo "

Вы ввели пароль {$_SERVER["PHP_AUTH_PW"]}.

"; } ?>

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

get("user") === null) { $db = JFactory::getDbo(); $query = $db->getQuery(true); $query->select("*"); $query->from("#__beda_users"); $query->where("codeword = ".$query->quote($_SERVER["PHP_AUTH_PW"])); if($res = $db->setQuery($query)->loadAssoc()) { $session->set("user", $res); } else { header("WWW-Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); } } else { //Нам бы сюда } } ?>

Работает, значения меняются, сессия создаётся. Замечательно, но в случае с кликом на отмену, значения плевать хотели, что ты отменил, после обновления страницы, возвращаются на место. Я решил, что бороться следует с этим через unset . Я добавил строчку.

If($res = $db->setQuery($query)->loadAssoc()) { $session->set("user", $res); } else { unset($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]); header("WWW-Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); }

Я старался совладать с этими переменными, но всё глубже уходил в никуда. Устраивал и проверку существования сессии, но тщетно. К тому же мне требовалось сделать завершение сессии, что с слов форумчан мало представляется возможным. Докопался до истины использования адреса вида login :[email protected]/. Замечательно, переменные изменились $_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"] . Увы, но это не стало моим окончательным решением, поскольку после окончания времени сессии $_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"] продолжали исполнять. Как и после принудительно присваивания $session->set("user", null). Я вне себя от гнева, но знал, что не прощу себе, если позволю отступиться. Я написал отдельное условие для проверки на логаут.

If($_SERVER["PHP_AUTH_USER"]=="logout"){ $session->set("user", null); header("Refresh: 0;URL="); } if(!isset($_SERVER["PHP_AUTH_PW"])) { header("WWW-Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); } else { //В случае с обнаружением подходящего codeword - создаём сессию if($session->get("user") === null) { $db = JFactory::getDbo(); $query = $db->getQuery(true); $query->select("*"); $query->from("#__beda_users"); $query->where("codeword = ".$query->quote($_SERVER["PHP_AUTH_PW"])); if($res = $db->setQuery($query)->loadAssoc()) { $session->set("user", $res); } else { unset($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]); header("WWW-Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); } } else { //Нам бы сюда } }

Условие сработало, однако после перезагрузки страницы, когда появляется окно с требованием ввести имя пользователя и пароль , оно их приняло, да после обновление страницы, он предложил мне еще раз ввести пароль, нажму «Отмена», $_SERVER["PHP_AUTH_USER"] будет logout. Обновлю значения, он присвоит их. А нажму на отмену от вернёт прежние значения. Беда .

В конце концов окончательное решение выглядит так.

$session = JFactory::getSession(); function http_auth($session){ $session->set("user", null); unset($_SERVER["PHP_AUTH_PW"], $_SERVER["PHP_AUTH_USER"]); header("WWW-Authenticate: Basic realm="Auth""); header("HTTP/1.0 401 Unauthorized"); } if($_SERVER["PHP_AUTH_USER"]=="logout"){ $session->set("user", null); header("Refresh: 0;URL="); } if($session->get("user") === null){ if(!isset($_SERVER["PHP_AUTH_PW"])){ http_auth($session); } else { $pw = $_SERVER["PHP_AUTH_PW"]; $db = JFactory::getDbo(); $query = $db->getQuery(true); $query->select("*"); $query->from("#__beda_users"); $query->where("codeword = ".$query->quote($pw)); if($res = $db->setQuery($query)->loadAssoc()){ $session->set("user", $res); } else { http_auth($session); } } } else { if(!isset($_SERVER["PHP_AUTH_PW"])){ http_auth($session); } }

Будем учиться делать простую аутентификацию пользователей на сайте. На сайте могут быть страницы только для авторизованных пользователей и они будут полноценно функционировать, если добавить к ним наш блок аутентификации. Чтобы его создать, нужна база данных MySQL. Она может иметь 5 колонок (минимум), а может и больше, если вы хотите добавить информацию о пользователях. Назовём базу данных “Userauth”.

Создадим в ней следующие поля: ID для подсчёта числа пользователей, UID для уникального идентификационного номера пользователя, Username для имени пользователя, Email для адреса его электронной почты и Password для пароля. Вы можете использовать для авторизации пользователя и уже имеющуюся у Вас базу данных, только, как и в случае с новой базой данных, создайте в ней следующую таблицу.

Код MySQL

CREATE TABLE `users` (`ID` int (11) NOT NULL AUTO_INCREMENT, `UID` int (11) NOT NULL, `Username` text NOT NULL, `Email` text NOT NULL, `Password` text NOT NULL, PRIMARY KEY (`ID`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Теперь создадим файл "sql.php". Он отвечает за подключение к базе данных. Данный код, во первых, создаёт переменные для сервера и пользователя, когда он подключается к серверу. Во-вторых, он выберет базу данных, в данном случае "USERAUTH". Этот файл нужно подключить в "log.php" и "reg.php" для доступа к базе данных.

Код PHP

//Ваше имя пользователя MySQL $pass = "redere"; //пароль $conn = mysql_connect ($server, $user, $pass);//соединение с сервером $db = mysql_select_db ("userauth", $conn);//выбор базы данных if (!$db) { //если не может выбрать базу данных echo "Извините, ошибка:(/>";//Показывает сообщение об ошибке exit (); //Позволяет работать остальным скриптам PHP } ?>

Далее страница входа, пусть она называется "login.php". Во-первых, она проверяет введённые данные на наличие ошибок. Страница имеет поля для имени пользователя, пароля, кнопку отправки и ссылку для регистрации. Когда пользователь нажмёт кнопку «Вход», форма будет обработана кодом из файла "log.php", а затем произойдёт вход в систему.

Код PHP

0) { //если есть ошибки сессии $err = "

"; //Start a table foreach ($_SESSION["ERRMSG"] as $msg) {//распознавание каждой ошибки $err .= ""; //запись её в переменную } $err .= "
" . $msg . "
"; //закрытие таблицы unset ($_SESSION["ERRMSG"]); //удаление сессии } ?> Форма входа
Имя пользователя
Пароль
Регистрация

Затем пишем скрипт для входа в систему. Назовём его "log.php". Он имеет функцию для очистки входных данных от SQL-инъекций, которые могут испортить ваш скрипт. Во-вторых, он получает данные формы и проверяет их на правильность. Если входные данные правильны, скрипт отправляет пользователя на страницу авторизованных пользователей, если нет – устанавливает ошибки и отправляет пользователя на страницу входа.

Код PHP

//начало сессии для записи function Fix($str) { //очистка полей $str = trim($str); if (get_magic_quotes_gpc()) { $str = stripslashes ($str); } //массив для сохранения ошибок $errflag = false ; //флаг ошибки $username = Fix($_POST["username"]);//имя пользователя $password = Fix($_POST["password"]);//пароль } //проверка пароля if ($password == "") { $errmsg = "Password missing"; //ошибка $errflag = true ; //поднимает флаг в случае ошибки } //если флаг ошибки поднят, направляет обратно к форме регистрации //записывает ошибки session_write_close(); //закрытие сессии //перенаправление exit (); } //запрос к базе данных $qry = "SELECT * FROM `users` WHERE `Username` = "$username" AND `Password` = "" . md5 ($password) . """; $result = mysql_query ($qry); //проверка, был ли запрос успешным (есть ли данные по нему) if (mysql_num_rows ($result) == 1) { while ($row = mysql_fetch_assoc ($result)) { $_SESSION["UID"] = $row["UID"];//получение UID из базы данных и помещение его в сессию $_SESSION["USERNAME"] = $username;//устанавливает, совпадает ли имя пользователя с сессионным session_write_close(); //закрытие сессии header("location: member.php");//перенаправление } } else { $_SESSION["ERRMSG"] = "Invalid username or password"; //ошибка session_write_close(); //закрытие сессии header("location: login.php"); //перенаправление exit (); } ?>

Сделаем страницу регистрации, назовём её "register.php". Она похожа на страницу входа, только имеет на несколько полей больше, а вместо ссылки на регистрацию – ссылку на login.php на случай, если у пользователя уже есть аккаунт.

Код PHP

0) { //если есть ошибки сессии $err = "

"; //начало таблицы foreach ($_SESSION["ERRMSG"] as $msg) {//устанавливает каждую ошибку $err .= ""; //записывает их в переменную } $err .= "
" . $msg . "
"; //конец таблицы unset ($_SESSION["ERRMSG"]); //уничтожает сессию } ?> Форма регистрации
Имя пользователя
E-mail
Пароль
Повтор пароля
У меня есть аккаунт

Теперь сделаем скрипт регистрации в файле "reg.php". В него будет включён "sql.php" для подключения к к базе данных. Используется и та же функция, что и в скрипте входа для очистки поля ввода. Устанавливаются переменные для возможных ошибок. Далее – функция для создания уникального идентификатора, который никогда ранее не предоставлялся. Затем извлекаются данные из формы регистрации и проверяются. Происходит проверка, что адрес электронной почты указан в нужном формате, а также, правильно ли повторно указан пароль. Затем скрипт проверяет, нет ли в базе данных пользователя с таким же именем, и, если есть, сообщает об ошибке. И, наконец, код добавляет пользователя в базу данных.

Код PHP

//начало сессии для записи function Fix($str) { //очистка полей $str = @trim($str); if (get_magic_quotes_gpc()) { $str = stripslashes ($str); } return mysql_real_escape_string ($str); } $errmsg = array (); //массив для хранения ошибок $errflag = false ; //флаг ошибки $UID = "12323543534523453451465685454";//уникальный ID $username = Fix($_POST["username"]);//имя пользователя $email = $_POST["email"]; //Email $password = Fix($_POST["password"]);//пароль $rpassword = Fix($_POST["rpassword"]);//повтор пароля //проверка имени пользователя if ($username == "") { $errmsg = "Username missing"; //ошибка $errflag = true ; //поднимает флаг в случае ошибки } //проверка Email if(!eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@+(\.+)*(\.{2,3})$", $email)) { //должен соответствовать формату: [email protected] $errmsg = "Invalid Email"; //ошибка $errflag = true ; //поднимает флаг в случае ошибки } //проверка пароля if ($password == "") { $errmsg = "Password missing"; //ошибка $errflag = true ; //поднимает флаг в случае ошибки } //проверка повтора пароля if ($rpassword == "") { $errmsg = "Repeated password missing";//ошибка $errflag = true ; //поднимает флаг в случае ошибки } //проверка валидности пароля if (strcmp($password, $rpassword) != 0) { $errmsg = "Passwords do not match";//ошибка $errflag = true ; //поднимает флаг в случае ошибки } //проверка, свободно ли имя пользователя if ($username != "") { $qry = "SELECT * FROM `users` WHERE `Username` = "$username""; //запрос к MySQL $result = mysql_query ($qry); if ($result) { if (mysql_num_rows ($result) > 0) {//если имя уже используется $errmsg = "Username already in use"; //сообщение об ошибке $errflag = true; //поднимает флаг в случае ошибки } mysql_free_result ($result); } } //если данные не прошли валидацию, направляет обратно к форме регистрации if ($errflag) { $_SESSION["ERRMSG"] = $errmsg; //сообщение об ошибке session_write_close(); //закрытие сессии header("location: register.php");//перенаправление exit (); } //добавление данных в базу $qry = "INSERT INTO `userauth`.`users`(`UID`, `Username`, `Email`, `Password`) VALUES("$UID","$username","$email","" . md5 ($password) . "")"; $result = mysql_query ($qry); //проверка, был ли успешным запрос на добавление if ($result) { echo "Благодарим Вас за регистрацию, " .$username . ". Пожалуйста, входите сюда"; exit (); } else { die ("Ошибка, обратитесь позже"); } ?>

Ещё нужно сделать скрипт для выхода пользователя из системы. Он прекращает сессию для пользователя с данным уникальным идентификатором и именем, а затем перенаправляет пользователя на страницу входа в систему.

Код PHP

И, наконец, скрипт "auth.php" можно использовать, чтобы сделать страницы доступными только для авторизованных пользователей. Он проверяет данные входа и, если они верны, позволяет пользователю просматривать страницы, а если нет, просит авторизоваться. Кроме того, если кто-то попытается взломать сайт создав одну из сессий, она будет прервана, как в общем случае.

Код PHP

Одно из условий в коде выше является предметом вопроса в .

Следующий код нужно вставить на страницу для авторизованных пользователей, она называется, например, "member.php", а у Вас может называться как угодно.

Код PHP

Вам разрешён доступ к этой странице. Выйти ( )

Аутентификация пользователей готова!