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

Ограничение доступа к какой-либо области сайта обычно выглядит
однообразно: каждому пользователю выдается логин и пароль или он сам
их выбирает, и для входа в защищенную часть сайта их нужно ввести. С технической же точки зрения для проверки пароля используются
разные методы. Для ввода логина и пароля может использоваться 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". Получаем результат:

Эта статья устарела.

Эта статья написана для услуги Виртуальный хостинг, которая считается устаревшей с 1 июня 2019 года.

Актуальную услугу хостинга можно заказать на нашем сайте

HTTP-авторизация средствами PHP

В данной статье рассмотрено создание HTTP-авторизации с помощью PHP, что позволит закрыть какой-либо раздел сайта, например административную часть.

Для установления подлинности HTTP обеспечивает простой механизм "вызов-ответ" (challenge-response), который может использоваться сервером для вызова (challenge) клиентского запроса, а клиентом для предоставления опознавательной информации (authentication information). Наиболее распространенной схемой авторизации является "Базовая схема установления подлинности" (Basic Authentication Scheme).

"Базовая" схема установления подлинности основана на том, что агент пользователя (браузер) должен подтвердить свою подлинность при помощи идентификатора пользователя (имени пользователя) и пароля для каждой защищенной области (realm). Сервер обслужит запрос, в случае если он может проверить правильность идентификатора пользователя и его пароля для данной защищенной области. Никаких дополнительных опознавательных параметров в данной схеме не предусмотрено.

После получения запроса на идентификацию, сервер отвечает вызовом (challenge), подобным следующему:

WWW-Authenticate: Basic realm="Restricted Area" HTTP/1.1 401 Unauthorized

Здесь "Restricted Area" -- строка, назначенная сервером, которая идентифицирует защищенную область запрашиваемого URI (Request-URI). Говоря простым языком -- имя защищенной области.

Далее, чтобы получить права доступа, агент пользователя (браузер) отправляет на сервер идентификатор пользователя (имя пользователя) и пароль, разделенные одним символом двоеточия (":"), внутри base64-кодированной строки рекомендаций (credentials):

Basic-credentials = "Basic" basic-cookie

Здесь

  • basic-cookie -- base64-кодированая строка, содержащая user-pass
  • user-pass -- строка вида "userid:password"
  • userid -- текст, не содержащий символов ":"
  • password -- текст

Обратите внимание, что и имя пользователя и пароль являются чувствительными к регистру. То есть, например, User и user -- два различных имени пользователя.

HTTP-авторизация и PHP

Возможно вам уже приходилось использовать Basic-схему авторизации совместно с PHP и вам известно, что суть метода сводится к тому, чтобы получить от веб-сервера переменные PHP_AUTH_USER и PHP_AUTH_PW , определяющие соответственно имя пользователя и пароль, и каким-либо образом обработать их внутри PHP-скрипта. Но заметим, что данный способ эффективен только тогда, когда PHP функционирует в качестве модуля веб-сервера Apache. На нашем хостинге PHP работает в режиме CGI/FastCGI и описанный выше метод работать не будет, поскольку переменные PHP_AUTH_USER и PHP_AUTH_PW не будут передаваться внутрь скрипта.

Однако существует способ, который позволяет обойти это ограничение и передать значения имени пользователя и пароля, которые вводит пользователь, внутрь PHP-скрипта. Для этих целей используются средства mod_rewrite -- модуля веб-сервера Apache. Правила, которые мы будем использовать, имеют следующий вид:

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

При запросе через HTTP к файлу sitename.ru/www/index.php, данное правило будет передавать GET-запросом содержимое непустого поля Authorization в параметр authorization. Если мы средствами PHP посмотрим на содержимое переменной $_GET["authorization"], то увидем как раз описываемый выше basic-credentials -- строку вида:

Preg_match("/^Basic\s+(.*)$/i", $_GET["authorization"], $user_pass); list($user,$pass)=explode(":",base64_decode($user_pass));

Таким образом мы получим две переменные -- $user и $pass, содержащие соответствено имя пользователя и пароль. Теперь, как упоминалось выше, их нужно лишь каким-либо образом обработать -- например, сравнить с аналогичными переменными из базы данных или из файла с учетными записями пользователей.

Заключение

Рассмотренный в данной статье метод будет успешно работать не только на нашем хостинге, но и везде, где PHP работает в режиме CGI/FastCGI и в качестве веб-сервера используется Apache + mod_rewrite.

Исходные тексты рабочих примеров вы можете найти в Приложении к данной статье.

Приложение. Исходные тексты скрипта

Исходный текст файла.htaccess

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

Исходный текст PHP-скрипта

$authenticated=0; if(isset($_GET["authorization"])) { if(preg_match("/^Basic\s+(.*)$/i", $_GET["authorization"], $user_pass)) { list($user,$pass)=explode(":",base64_decode($user_pass)); // Проверка корректности введенных реквизитов доступа if($user=="user" && $pass=="password") { $authenticated=1; } } } if($authenticated) { // Авторизация успешно пройдена echo("user: ".$user."
pass: ".$pass); } else { header("WWW-Authenticate: Basic realm="Restricted Area""); header("HTTP/1.1 401 Unauthorized"); echo("Access denied."); }

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

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

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

Доброго времени суток друзья! Давай с Вами рассмотрим регистрацию пользователей на PHP. Для начала давайте определим условия для нашей регистрации пользователей:

  • Пароль шифруем при помощи алгоритма MD5
  • Пароль будем "солить"
  • Проверка на занятость Логина
  • Активация пользователя письмом.
  • Запись и хранение данных в СУБД MySQL

Для написание данного скрипта нам нужно понять, что такое регистрация пользователя. Регистрация пользователя - это получения данных реального пользователя, обработка и хранение данных.

Если объяснять простыми словами то регистрация это всего лишь запись и хранение определенных данных по которым мы можем авторизировать пользователя в нашем случае - это Логин и Пароль.

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

Рассмотрим структуру каталогов скриптов для реализации нашей регистрации с авторизацией. Нам нужно разбить скрипты на логические составляющие. Модули регистрации и авторизации мы поместив в отдельный каталог. Так же в отдельные каталоги мы поместим подключение к базе данных MySQL , файл с пользовательскими функциями, файл стилей CSS и наш шаблон HTML . Данная структура позволяет быстро ориентироваться в скриптах. Представьте себе, что у Вас большой сайт с кучей модулями и т.д. и если не будет порядка, то будет очень сложно что-то отыскать в таком бардаке.

Так как мы будем хранить все данные в СУБД MySQL , то давайте создадим не большую таблицу в которой будем хранить данные о регистрации.

Для начала нужно создать таблицу в базе данных. Таблицу назовем bez_reg где bez - это префикс таблицы, а reg название таблицы.

Структура таблицы: bez_reg

-- -- Структура таблицы `bez_reg` -- CREATE TABLE IF NOT EXISTS `bez_reg` (`id` int(11) NOT NULL AUTO_INCREMENT, `login` varchar(200) NOT NULL, `pass` varchar(32) NOT NULL, `salt` varchar(32) NOT NULL, `active_hex` varchar(32) NOT NULL, `status` int(1) NOT NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

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

Файл INDEX.PHP

Файл CONFIG.PHP

"); ?>

Файл 404.HTML

Ошибка 404

Ошибка 404

На странице произошла ошибка 404

Вернуться

Файл BD.PHP

Файл INDEX.HTML

Регистрация пользователей PHP MySQL с активацией письмом

Файл FUNCT.PHP

"."\n"; if(is_array($data)) { foreach($data as $val) $err .= "

  • ". $val ."
  • "."\n"; } else $err .= "
  • ". $data ."
  • "."\n"; $err .= ""."\n"; return $err; } /**Простая обертка для запросов к MySQL * @param string $sql */ function mysqlQuery($sql) { $res = mysql_query($sql); /* Проверяем результат Это показывает реальный запрос, посланный к MySQL, а также ошибку. Удобно при отладке.*/ if(!$res) { $message = "Неверный запрос: " . mysql_error() . "\n"; $message .= "Запрос целиком: " . $sql; die($message); } return $res; } /**Простой генератор соли * @param string $sql */ function salt() { $salt = substr(md5(uniqid()), -8); return $salt; }

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

    Файл REG.PHP

    Вы успешно зарегистрировались! Пожалуйста активируйте свой аккаунт!!"; //Производим активацию аккаунта if(isset($_GET["key"])) { //Проверяем ключ $sql = "SELECT * FROM `". BEZ_DBPREFIX ."reg` WHERE `active_hex` = "". escape_str($_GET["key"]) ."""; $res = mysqlQuery($sql); if(mysql_num_rows($res) == 0) $err = "Ключ активации не верен!"; //Проверяем наличие ошибок и выводим пользователю if(count($err) > 0) echo showErrorMessage($err); else { //Получаем адрес пользователя $row = mysql_fetch_assoc($res); $email = $row["login"]; //Активируем аккаунт пользователя $sql = "UPDATE `". BEZ_DBPREFIX ."reg` SET `status` = 1 WHERE `login` = "". $email ."""; $res = mysqlQuery($sql); //Отправляем письмо для активации $title = "Ваш аккаунт на http://сайт успешно активирован"; $message = "Поздравляю Вас, Ваш аккаунт на http://сайт успешно активирован"; sendMessageMail($email, BEZ_MAIL_AUTOR, $title, $message); /*Перенаправляем пользователя на нужную нам страницу*/ header("Location:". BEZ_HOST ."less/reg/?mode=reg&active=ok"); exit; } } /*Если нажата кнопка на регистрацию, начинаем проверку*/ if(isset($_POST["submit"])) { //Утюжим пришедшие данные if(empty($_POST["email"])) $err = "Поле Email не может быть пустым!"; else { if(!preg_match("/^[email protected](+\.)+{2,6}$/i", $_POST["email"])) $err = "Не правильно введен E-mail"."\n"; } if(empty($_POST["pass"])) $err = "Поле Пароль не может быть пустым"; if(empty($_POST["pass2"])) $err = "Поле Подтверждения пароля не может быть пустым"; //Проверяем наличие ошибок и выводим пользователю if(count($err) > 0) echo showErrorMessage($err); else { /*Продолжаем проверять введеные данные Проверяем на совподение пароли*/ if($_POST["pass"] != $_POST["pass2"]) $err = "Пароли не совподают"; //Проверяем наличие ошибок и выводим пользователю if(count($err) > 0) echo showErrorMessage($err); else { /*Проверяем существует ли у нас такой пользователь в БД*/ $sql = "SELECT `login` FROM `". BEZ_DBPREFIX ."reg` WHERE `login` = "". escape_str($_POST["email"]) ."""; $res = mysqlQuery($sql); if(mysql_num_rows($res) > 0) $err = "К сожалению Логин: ". $_POST["email"] ." занят!"; //Проверяем наличие ошибок и выводим пользователю if(count($err) > 0) echo showErrorMessage($err); else { //Получаем ХЕШ соли $salt = salt(); //Солим пароль $pass = md5(md5($_POST["pass"]).$salt); /*Если все хорошо, пишем данные в базу*/ $sql = "INSERT INTO `". BEZ_DBPREFIX ."reg` VALUES("", "". escape_str($_POST["email"]) ."", "". $pass ."", "". $salt ."", "". md5($salt) ."", 0)"; $res = mysqlQuery($sql); //Отправляем письмо для активации $url = BEZ_HOST ."less/reg/?mode=reg&key=". md5($salt); $title = "Регистрация на http://сайт"; $message = "Для активации Вашего акаунта пройдите по ссылке ". $url .""; sendMessageMail($_POST["email"], BEZ_MAIL_AUTOR, $title, $message); //Сбрасываем параметры header("Location:". BEZ_HOST ."less/reg/?mode=reg&status=ok"); exit; } } } } ?>

    Файл REG_FORM.HTML

    Регистрация пользователей PHP MySQL с активацией письмом

    E-mail*:
    Пароль*:
    Подтверждения пароля*:

    Поля со значком * обязательны для заполнения

    Так как регистрация пользователей у нас готова, самое время написать авторизацию. Создадим форму для авторизации пользователей, далее напишем обработчик формы авторизации и на последок сделаем скрипт show.php который будет показывать нам авторизированны мы в системе или нет.

    Файл AUTH.PHP

    0) echo showErrorMessage($err); else { /*Создаем запрос на выборку из базы данных для проверки подлиности пользователя*/ $sql = "SELECT * FROM `". BEZ_DBPREFIX ."reg` WHERE `login` = "". escape_str($_POST["email"]) ."" AND `status` = 1"; $res = mysqlQuery($sql); //Если логин совподает, проверяем пароль if(mysql_num_rows($res) > 0) { //Получаем данные из таблицы $row = mysql_fetch_assoc($res); if(md5(md5($_POST["pass"]).$row["salt"]) == $row["pass"]) { $_SESSION["user"] = true; //Сбрасываем параметры header("Location:". BEZ_HOST ."less/reg/?mode=auth"); exit; } else echo showErrorMessage("Неверный пароль!"); } else echo showErrorMessage("Логин ". $_POST["email"] ." не найден!"); } } ?>

    Для тех у кого последняя версия PHP выкладываю данный скрипт с использованием PDO т.к. расширение MySQL устарело и было удалено из новой версии PHP. Скачать регистрация и авторизация php mysql pdo

    Архив обновлен 24.02.2015г.

    Внимание: Если вы используете данный скрипт на локальном сервере типа DENWER , XAMPP , то не стоит ждать писем на свой почтовый ящик. Письма лежат в заглушке sendmail . В Denwer вы их можете найти по пути Z:\tmp\!sendmail\ открыть данные файлы вы сможете в любом почтовом клиенте.

    Иногда бывает нужно закрыть от постороннего доступа 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 адрес).