Аутентификация с помощью PHP и MySQL.
К счастью для пользователей Web, информация, которую выдает браузер, не позволяет идентифицировать пользователя. Если хотите знать имя посетителя и другие детали, следует спросить его об этом. Запросив и получив в ответ информацию о посетителе, необходимо каким-то образом ассоциировать ее с посетителем при его следующих входах на сайт. Если предположить, что с определенного компьютера, используя определенное имя пользователя на этом компьютере, на сайт заходит только один пользователь, а каждый пользователь работает только на одном компьютере, то для идентификации пользователя можно создать cookie-набор на компьютере пользователя. Подобное предположение неверно для большинства пользователей. Часто многие люди поочередно пользуются одним и тем же компьютером, либо же кто-то использует несколько компьютеров. По крайней мере, иногда приходится повторно спрашивать пользователя о том, кто он, и попросить посетителя предоставить какие-то доказательства того, что он тот, за кого себя выдает.
Просьба к пользователю доказать свою личность называется аутентификацией. Обычный метод аутентификации в Web — это требование к посетителям предоставить уникальное имя пользователя и пароль. Аутентификация обычно используется для разрешения или запрещения доступа к определенным страницам или ресурсам. Аутентификация может быть необязательной либо использоваться для других целей, например, для персонализации.
Реализация контроля доступа.Простой контроль доступа реализовать несложно. Код, показанный на листинге 7.10, выводит одну из трех возможных страниц. Если этот файл загружен без параметров, будет отображаться HTML-форма с приглашением вести имя пользователя и пароль. Если при загрузке параметры присутствуют, но они неправильные, отображается сообщение об ошибке. Если при загрузке параметры присутствуют и они правильные, посетителю отображается секретное содержимое.
Листинг 6.11. secret.php — РНР-код для реализации простого механизма аутентификации
<?
if(!isset($name)&&!isset($password))
{// Посетитель должен ввести имя и пароль
?>
<h1>Please Log In</h1>This page is secret.
<form method = post action = "secret.php">
<table border = 1>
<tr><th>Username</th><td><input type = text name = name></td></tr>
<tr><th>Password</th><td><input type = password name = password>
</td></tr><tr><td colspan =2 align = center>
<input type = submit value = "Log In"></td></tr>
</table></form>
<? }
else if($name=="user"&&$password=="pass")
<? // Комбинация имени и пароля посетителя правильная
echo "<h1>Here it is!</h1>";
echo "I bet you are glad you can see this secret page."; }
else
{ // Комбинация имени и пароля посетителя неправильная
echo "<hl>Go Away!</hl>";
echo "You are not authorized to view this resource."; }
?>
Код, показанный в листинге 6.11, реализует простой механизм, позволяющий санкционированным посетителям видеть защищенную страницу, однако код содержит несколько значительных проблем. Этот сценарий:
· поддерживает только одно жестко закодированное имя пользователя и пароль
· хранит пароль в виде простого текста
· защищает только одну страницу
· передает пароль в виде простого текста
Упомянутые проблемы можно разрешить с различной степенью усилий и успеха.
Хранение паролей.Для хранения паролей существует много более подходящих, нежели код сценария, мест. Внутри сценария очень трудно изменять данные. Можно написать сценарий, который будет изменять себя, но это плохая идея. Это будет означать существование выполняющегося на сервере сценария, доступного для записи и изменений со стороны других пользователей. Хранение паролей в отдельном файле на сервере позволит без труда написать программу для добавления и удаления пользователей, а также для изменения паролей.
Внутри сценария или другого файла данных существует ограничение на количество пользователей, которых можно обслуживать, серьезно не навредив общей производительности сценария. Если планируется сохранять большое количество элементов в файле или производить поиск в рамках большого числа элементов, то, как обсуждалось ранее, следует рассмотреть возможность использования базы данных вместо двумерного файла. Практический метод выбора гласит: если вы собираетесь хранить и производить поиск в более чем 100 элементах, следует отдать предпочтение базе данных.
Использование базы данных для хранения имен и паролей посетителей не сильно усложнит сценарий, но позволит быстро проводить аутентификацию множества пользователей. Это также упростит создание сценария для добавления и удаления пользователей, а также даст возможность пользователям изменять свои пароли.
Сценарий для аутентификации посетителей страницы с использованием базы данных приведен в листинге 6.12.
Листинг 6.12. secretdb.php — применение MySQL для простого механизма аутентификации.
<?
if(!isset($name)&&!isset($password))
{ // Посетитель должен ввести имя и пароль
?>
<hl>Please Log In</hl>This page is secret.
<form method = post action = "secretdb.php"><table border = l>
<tr><th>Username</th><td><input type = text name = name></td></tr>
<tr><th>Password</th><td><input type = password name = password>
</td></tr><tr><td colspan =2 align = center>
<input type = submit value = "Log In"></td> </tr>
</table></form>
<? }
else
{ // Подключиться к MySQL
$mysql = mysql_connect( 'localhost', 'webauth', 'webauth' );
if(!$mysql)
{echo 'Cannot connect to database.'; exit;}
// Выбрать соответствующую базу данных
$mysql = mysql_select_db('auth');
if(!$mysql)
{ echo 'Cannot elect database.'; exit; }
// Запрос к базе данных, чтобы проверить,
// существует ли соответствующая запись
$query = "select count (*) from auth where name = '$name' and
pass = '$password'";
$result = mysql_query($query);
if (!$result)
{echo 'Cannot run query.';exit; }
$count = mysql_result($result, 0, 0 );
if ( $count > 0 )
{// Комбинация имени и пароля посетителя правильная
echo "<hl>Here it is!</hl>";
echo "I bet you are glad you can see this secret page."; }
else
<?
// Комбинация имени и пароля посетителя не правильная
echo "<hl>Go Away!</hl>";
echo "You are not authorized to view this resource."; }
}
?>
Используемую в примере базу данных можно создать, подключившись к MySQL как пользователь root и запустив показанный в листинге 6.13 сценарий.
Листинг 6.13. createauthdb.sql —создание базы данных, таблицы и двоих пользователей.
create database auth;
use auth;
create table auth (
name varchar(10) not null,
pass varchar(30) not null,
primary key (name)
);
insert into auth values ('user', 'pass');
insert into auth values
( 'testuser', password('test123') );
grant select, insert, update, delete
on auth.*
to webauth@localhost
identified by 'webauth';
Шифрование паролей.Независимо от того, где хранятся пароли — в базе данных или в файле — хранение паролей в виде простого текста сопряжено с неоправданным риском. Однонаправленный алгоритм хэширования обеспечит дополнительную защиту при незначительных дополнительных затратах.
РНР-функция crypt() представляет собой однонаправленную криптографическую хэш-функцию. Прототип этой функции таков:
string crypt (string str[, string salt])
Получив на входе строку str, эта функция возвращает псевдослучайную строку. Например, если передать в функцию строку "pass" и аргумент salt равный "хх", то crypt() вернет строку "xxkTlmYjlikoII". Эта строка не может быть дешифрована и превращена обратно в "pass" даже ее создателем, поэтому на первый взгляд строка может и не показаться столь уж полезной. Что делает функцию crypt() полезной, так это то, что результат этой функции детерминирован. При каждом вызове с одними и теми же параметрами str и salt эта функция будет возвращать один и тот же результат.
Вместо РНР-кода, такого как
if( $username == "user" && password == "pass" )
{ // Пароль совпадает
}
можно воспользоваться таким кодом
if($username= 'user' && crypt($password,'хх') == 'xxkTlmYjlikoII')
{ // Пароль совпадает
}
Нам не требуется знать, как выглядела строка "xxkTlmYjlikoH" перед использованием функции crypt(). Необходимо только знать, совпадает ли введенный пароль тем паролем, для которого применялась функция crypt().
Как уже упоминалось, жесткое кодирование правильных имен и паролей посетителей - плохая идея. Для этого следует организовать отдельный файл или базу данных. Если для хранения данных аутентификации используется база данных MySQL, можно воспользоваться РНР-функцией crypt() или MySQL-функцией PASSWORD(). результат этих функций не совпадает, но они имеют одно предназначение. Обе функции — crypt() и PASSWORD() — получают строку как аргумент и применяют к поденной строке необращаемый алгоритм хэширования.
Чтобы задействовать функцию PASSWORD() в листинге 14.2 запрос SQL следует переписать так:
select count (*) from auth where
name = '$name' and
pass = password('$password')
Этот запрос посчитает количество строк в таблице auth, в которых значение поля name совпадает с содержимым переменной $name, а поля pass - с результатом функции PASSWORD(), примененной к значению переменной $password. Если мы заставляем посетителей выбирать уникальные имена, результатом запроса может быть 0 или 1.
Защита множества страниц.Защита более чем одной страницы с помощью подобных сценариев немного сложнее. Поскольку в HTTP-протоколе нет механизма состояний, то не существует автоматической связи или ассоциации между последовательными запросами от одного и того же посетителя. Это усложняет перенос между страницами введенных пользователем данных, таких как данные аутентификации.
Чтобы самостоятельно создать такую функциональность, потребуется включить части листинга 6.11 в каждую страницу, которую необходимо защитить. При помощи директив auto_prepend_file и auto_append_file требуемый файл можно автоматически вставить в начало (prepend) или в конец (append) каждого файла в указанных каталогах.
Если воспользоваться таким подходом, то что произойдет, когда пользователь откроет несколько страниц на сайте? Недопустимо запрашивать пароль отдельно для каждой страницы, которую желает просмотреть пользователь.
Можно включить введенную пользователем информацию в каждую гиперссылку на странице. Так как пользователи могут применять пробелы или другие символы, запрещенные в URL, следует обратиться к функции urlencode(), чтобы безопасно упаковать подобные символы.
С этим подходом связаны еще некоторые проблемы. Поскольку данные аутентификации будут присутствовать в отправляемой посетителю Web-странице, защищенные страницы, которые посетил пользователь, могут быть просмотрены любым человеком, работающим за тем же компьютером. Для этого достаточно щелкнуть на кнопке "Назад" в окне браузера и просмотреть кэшированные копии страниц или заглянуть в историю посещения страниц. Пароль пересылается в браузер и обратно с каждой запрошенной или предоставленной страницей, что происходит чаще, чем это необходимо.
Решить проблему можно с помощью двух механизмов — базовой HTTP-аутентификации и поддержки сеансов. Базовая аутентификация позволяет решить проблему кэширования, но сервер все равно отправляет пароль Web-браузеру в каждом запросе. Управление сеансами позволяет решить обе проблемы. Сначала рассмотрим базовую HTTP-аутентификацию, а управление сеансами освещается далее.
Базовая аутентификация.К счастью, аутентификация пользователей — это достаточно распространенная задача и существуют возможности аутентификации, встроенные в HTTP-протокол. Сценарии и Web-серверы могут запрашивать аутентификацию у Web-браузера. После этого Web-браузер должен вывести на экран диалоговое окно или что-то подобное и запросить у пользователя необходимую информацию.
Хотя Web-сервер запрашивает новые детали аутентификации в каждом запросе пользователя, Web-браузеру нет необходимости запрашивать эту информацию для каждой страницы. В общем случае браузер хранит детали аутентификации, пока открыто окно браузера, и автоматически отправляет их без вмешательства со стороны пользователя.
Описанная возможность HTTP-протокола называется базовой аутентификацией. Баовую аутентификацию можно включить средствами РНР или с помощью Web-сервера, Apache и IIS. Далее рассматриваются методы, предполагающие использование РНР.
Базовая аутентификация передает имя пользователя и пароль в виде простого текста и поэтому не особо безопасна. Протокол HTTP 1.1 обладает более безопасным методом, называемым дайджест-аутентификацией (digest authentication). Этот метод использует алгоритм хэширования (как правило, MD5) для маскировки деталей транзакции. Дайджест-аутентификация поддерживается во многих Web-серверах, но не поддерживается в значительном числе браузеров. Дайджест-аутентификация поддерживается в браузере Microsoft Internet Explorer начиная с версии 5.0. Поддержка дайджест-аутентификации включена в Netscape Navigator версию 6.0.
В дополнение к очень слабой поддержке в наборе доступных браузеров, дайджест-аутентификация к тому же и не очень безопасна. И базовая, и дайджест-аутентификация предоставляют низкий уровень защищенности. Ни один из этих методов не дает пользователю гарантий, что он работает именно с тем компьютером, доступ к которому он планировал получить. Оба метода позволяют взломщику повторить тот же запрос серверу. Поскольку базовая аутентификация передает пароль пользователя в открытом виде, любой взломщик, способный перехватывать пакеты, может сымитировать запрос пользователя.
Базовая аутентификация предоставляет низкий уровень защиты, подобный тому, который обеспечивается при подключении по протоколу Telnet или FTP. Эти методы также передают пароли в виде простого текста. Дайджест-аутентификация несколько более безопасна и шифрует пароли перед передачей. Использование протокола SSL и цифровых сертификатов позволяет надежно защитить все части транзакций в Web.
Однако во многих ситуациях наиболее подходящим будет быстрый и относительно незащищенный метод, такой как базовая аутентификация.
Базовая аутентификация позволяет защитить именованные области и требует от пользователей ввода правильного имени и пароля. Области именованные, поэтому на одном сервере может быть существовать множество областей. Различные файлы и каталоги на одном сервере могут принадлежать разным областям, каждая из которых защищена своими наборами имен пользователей и паролей. Именованные области позволяют также сгруппировать в одну область несколько каталогов на одном физическом или виртуальном узле и защитить всю область одним паролем.
Использование базовой аутентификации в РНР.РНР-сценарии, в основном, можно назвать кросс-платформенными, но использование базовой аутентификации базируется на переменных среды, устанавливаемых сервером. Сценарий HTTP-аутентификации должен определять тип сервера и вести себя соответствующим образом в зависимости от того, выполняется ли он как модуль Apache на сервере Apache или как ISAPI-модуль на сервере IIS. Показанный в листинге 6.14 сценарий будет выполняться на обоих серверах.
Листинг 6.14. http.php — включение базовой HTTP-аутентификации средствами РНР.
<?
// Если используется сервер IIS, потребуется установить переменные
// среды $PHP_AUTH_USER и $PHP_AUTH_PW
if (substr($SERVER_SOFTWARE, 0, 9) == "Microsoft" &&
!isset($PHP_AUTH_USER) && !isset($PHP_AUTH_PW) &&
substr($HTTP_AUTHORIZATION, 0, 6) == "Basic" )
{ list($PHP_AUTH_USER, $PHP_AUTH_PW) =
explode(":", base64_decode(substr($HTTP_AUTHORIZATION, 6))); }
// Замените этот оператор if запросом к базе данных
if ($PHP_AUTH_USER != "user" || $PHP_AUTH_PW != "pass")
{
// Посетитель еще не передал деталей или имя и пароль неправильные
header('WWW-Authenticate: Basic realm="Realm-Name"');
if (substr($SERVER_SOFTWARE, 0, 9) == "Microsoft")
header("Status: 401 Unauthorized");
else header("HTTP/1.0 401 Unauthorized");
echo "<hl>Go Away!</hl>";
echo "You are not authorized to view this resource."; }
else {
// посетитель предоставил правильную информацию
echo "<h1>Here it is!</hl>";
echo "<p>I bet you are glad you can see this secret page.";
}
?>
Этот код работает так же, как и код из предыдущего листинга. Если пользователь не передал данных аутентификации, ему выдается запрос на аутентификацию. Если пользователь предоставил неправильную информацию, для него отображается сообщение об отказе в доступе. Если же информация правильная, пользователь увидит содержимое страницы.
Интерфейс данного примера отличается от интерфейса предыдущих примеров. Мы не создаем HTML-форму для ввода имени и пароля. Диалоговое окно для аутентификации выведет браузер пользователя. Некоторые рассматривают это как улучшение, другие предпочитают иметь полный контроль над визуальными аспектами интерфейса.
Поскольку для аутентификации применяются встроенные возможности браузеров, последние демонстрируют некоторую осторожность в обработке неудачных попыток аутентификации. Internet Explorer дает пользователю три попытки аутентификации, и если все они проходят неудачно, выводится сообщение об отказе в доступе. Netscape Navigator предоставляет неограниченное число попыток, но между попытками выводит диалоговое окно с запросом о повторе "Authentication Failed. Retry?". Netscape отображает сообщение об отказе в доступе, только когда пользователь щелкает на кнопке Cancel.
Как и код из листингов 6.11 и 6.12, код данного примера можно вставить в начало каждого файла, который требуется защитить. Это можно сделать вручную или автоматически для каждого файла в каталоге.
Дата добавления: 2015-11-04; просмотров: 1566;