Пишем свою авторизацию на PHP и MySQL
Ну что же, сегодня я вам расскажу о безопасной авторизации на PHP и Cookie. Ну о том что она абсолютна безопасна я не говорю, ибо взломать можно все, но для маленького сайта она вполне подходит. Так же я предвижу комментарии о том что сессии безопаснее. Не спорю куки уступают сессиям в безопасности но для реализации простенькой авторизации вполне подходят. Подробности ниже.
И так, в базе у нас будет 1 база из 4 полей: users_id, users_login, users_password и users_hash. SQL запрос:
1 2 3 4 5 6 7 | CREATE TABLE IF NOT EXISTS `users` ( `users_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `users_login` VARCHAR(30) NOT NULL, `users_password` VARCHAR(32) NOT NULL, `users_hash` VARCHAR(32) NOT NULL, PRIMARY KEY (`users_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; |
-
Сам скрипт авторизации будет тоже на 4 файлах. А именно:
- conf.php — Файл конфигурации, в котором так же содержится подключение к бд;
- register.php — Регистрация нового пользователя;
- login.php — Авторизация пользователя;
- check.php — Скрипт проверки авторизации;
Давайте разберем каждый файл.
conf.php
В константах содержится данные SQL подключения, а именно хост, логин, пароль и имя вашей базы. Тут же мы подключаемся к базе, устанавливаем кодировку в которой будут передаваться данные в бд и выбираем бд с которой будем работать. Так же тут содержится массив ошибок которые могут возникнуть при авторизации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php # настройки define ('DB_HOST', 'localhost'); define ('DB_LOGIN', 'example_user'); define ('DB_PASSWORD', 'example_password'); define ('DB_NAME', 'example_base'); mysql_connect(DB_HOST, DB_LOGIN, DB_PASSWORD) or die ("MySQL Error: " . mysql_error()); mysql_query("set names utf8") or die ("<br>Invalid query: " . mysql_error()); mysql_select_db(DB_NAME) or die ("<br>Invalid query: " . mysql_error()); # массив ошибок $error[0] = 'Я вас не знаю'; $error[1] = 'Включи куки'; $error[2] = 'Тебе сюда нельзя'; ?> |
register.php
Файл регистрации, тут содержится простейшая форма и ее обработчик. Исходный код прокомментирован, но общий процесс я коротко опишу. Вначале проверяем наш логин, он может содержать только английские буквы и цифры. Далее мы проверяем длину логина, от 3 до 30 символов. Проверяем свободен ли логин. При успешных проверках добавляем нового пользователя в базу. Из введенного пароля мы вырезаем пробелы на случай если пользователь хранит свои пароли в каком ни будь текстовом файле (в windows текстовые редакторы любят «хватать» пробелы в начале или конце выделяемого текста). Шифруем пароль в двойном MD5 и добавляем в базу данные о новом пользователе. Перебрасываем пользователя на login.php.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <?php # Подключаем конфиг include 'conf.php'; if(isset($_POST['submit'])) { $err = array(); # проверям логин if(!preg_match("/^[a-zA-Z0-9]+$/",$_POST['login'])) { $err[] = "Логин может состоять только из букв английского алфавита и цифр"; } if(strlen($_POST['login']) < 3 or strlen($_POST['login']) > 30) { $err[] = "Логин должен быть не меньше 3-х символов и не больше 30"; } # проверяем, не сущестует ли пользователя с таким именем $query = mysql_query("SELECT COUNT(users_id) FROM users WHERE users_login='".mysql_real_escape_string($_POST['login'])."'")or die ("<br>Invalid query: " . mysql_error()); if(mysql_result($query, 0) > 0) { $err[] = "Пользователь с таким логином уже существует в базе данных"; } # Если нет ошибок, то добавляем в БД нового пользователя if(count($err) == 0) { $login = $_POST['login']; # Убераем лишние пробелы и делаем двойное шифрование $password = md5(md5(trim($_POST['password']))); mysql_query("INSERT INTO users SET users_login='".$login."', users_password='".$password."'"); header("Location: login.php"); exit(); } } ?> <form method="POST" action=""> Логин <input type="text" name="login" id="reg_inp" /><br /> Пароль <input type="password" name="password" id="reg_inp" /><br /> <input name="submit" type="submit" value="Зарегистрироваться"> </form> <?php if (isset($err)) { print "<b>При регистрации произошли следующие ошибки:</b><br>"; foreach($err AS $error) { print $error."<br>"; } } ?> |
login.php
Опять кратко расскажу о действиях совершаемых в данном скрипте. В самом начале у нас висит функция для генерации случайной строки, она служит для хеша пользователя (чуть позже более подробно). Далее мы проверяем наличие куков с ошибками (они ставятся в check.php). Подключаем файл конфигурации и проверяем пользователя. Вытаскиваем из бд логин и пароль, сравниваем с введенными и генерируем хеш. Записываем в бд новый хеш пользователя и ставим куки. В куках находится id и хеш пользователя. Пересылаем пользователя на check.php.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | <?php # Функция для генерации случайной строки function generateCode($length=6) { $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRQSTUVWXYZ0123456789"; $code = ""; $clen = strlen($chars) - 1; while (strlen($code) < $length) { $code .= $chars[mt_rand(0,$clen)]; } return $code; } # Если есть куки с ошибкой то выводим их в переменную и удаляем куки if (isset($_COOKIE['errors'])){ $errors = $_COOKIE['errors']; setcookie('errors', '', time() - 60*24*30*12, '/'); } # Подключаем конфиг include 'conf.php'; if(isset($_POST['submit'])) { # Вытаскиваем из БД запись, у которой логин равняеться введенному $data = mysql_fetch_assoc(mysql_query("SELECT users_id, users_password FROM `users` WHERE `users_login`='".mysql_real_escape_string($_POST['login'])."' LIMIT 1")); # Соавниваем пароли if($data['users_password'] === md5(md5($_POST['password']))) { # Генерируем случайное число и шифруем его $hash = md5(generateCode(10)); # Записываем в БД новый хеш авторизации и IP mysql_query("UPDATE users SET users_hash='".$hash."' WHERE users_id='".$data['users_id']."'") or die("MySQL Error: " . mysql_error()); # Ставим куки setcookie("id", $data['users_id'], time()+60*60*24*30); setcookie("hash", $hash, time()+60*60*24*30); # Переадресовываем браузер на страницу проверки нашего скрипта header("Location: check.php"); exit(); } else { print "Вы ввели неправильный логин/пароль<br>"; } } ?> <form method="POST"> Логин <input name="login" type="text"><br> Пароль <input name="password" type="password"><br> <input name="submit" type="submit" value="Войти"> </form> <?php # Проверяем наличие в куках номера ошибки if (isset($errors)) {print '<h4>'.$error[$errors].'</h4>';} ?> |
check.php
И последний файл, который содержит проверку авторизации пользователя. В начале подключаем конфиг и если существуют куки начинаем проверку, если их нет, то ставим куки с номером ошибки и отсылаем на login.php. И так проверка. Вытаскиваем из бд id и хеш. Если они не проходят проверку на соответствие с теми куками которые стоят у посетителя, то удаляем существующие куки посетителя и ставим куки с номером ошибки, пересылаем на login.php. Если же все нормально то пользователь увидит страницу.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?php # подключаем конфиг include 'conf.php'; # проверка авторизации if (isset($_COOKIE['id']) and isset($_COOKIE['hash'])) { $userdata = mysql_fetch_assoc(mysql_query("SELECT * FROM users WHERE users_id = '".intval($_COOKIE['id'])."' LIMIT 1")); if(($userdata['users_hash'] !== $_COOKIE['hash']) or ($userdata['users_id'] !== $_COOKIE['id'])) { setcookie('id', '', time() - 60*24*30*12, '/'); setcookie('hash', '', time() - 60*24*30*12, '/'); setcookie('errors', '1', time() + 60*24*30*12, '/'); header('Location: login.php'); exit(); } } else { setcookie('errors', '2', time() + 60*24*30*12, '/'); header('Location: login.php'); exit(); } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <title></title> </head> <body> hello! </body> </html> |
Выход
Выход можно осуществить любым удобным для вас способом просто удалив куки, допустим так:
1 2 3 4 5 6 7 8 9 | <form action="" method="post"><input type='submit' name='exit' value='Выйти'/></form> <?php if($_REQUEST['exit']) { setcookie('id', '', time() - 60*60*24*30, '/'); setcookie('hash', '', time() - 60*60*24*30, '/'); header('Location: login.php'); exit(); } ?> |
Сразу хочу предупредить о том что ваши файлы должны быть в кодировке UTF8 и БЕЗ BOM! Приятного пользования!
Подскажите, как сделать выход?
[Ответить]
ZekMan Reply:
Октябрь 11th, 2010 at 16:28
Добавил в статью…
[Ответить]
а зачем используется
# массив ошибок
???
[Ответить]
ZekMan Reply:
Январь 6th, 2011 at 17:23
Если ты написал неверный логин/пароль, или зашел на защищенную страницу не залогинившись то тебя перебрасывает на страницу авторизации с и в куке передается номер ошибки, выдаем ошибку и просим авторизоваться…
А массив использую потому что это проще чем несколько переменных…
[Ответить]
Если собрать файлы, и попробовать зарегистрироваться, выдает
Invalid query: Unknown column ‘users_id’ in ‘field list’
[Ответить]
ZekMan Reply:
Январь 12th, 2011 at 19:07
В примере примерные название таблиц и баз, вы можете менять их по своему желанию. Была небольшая ошибочка в названия таблиц в дампе бд. Поправил.
[Ответить]
По моему с DOCTYPE можно обойтись проще. Достаточно . Но это уже мелочи.
[Ответить]
ZekMan Reply:
Март 15th, 2011 at 05:44
Это валидный DOCTYPE — по стандарту XHTML 1.0 =)
[Ответить]
Ну а DOCTYPE html это валидный для всех документов 😉 Просто писать меньше и удобней.
[Ответить]
ZekMan Reply:
Март 15th, 2011 at 13:52
Писать надо по стандарту на котором пишешь, для проверки валидности есть validator.w3.org а для «писать меньше и удобней» есть ZenCoding про который я уже писал 😉
[Ответить]
Спасибо за статью. для своего самописного проекта приходилось писать и регистрацию, и авторизацию. Также на эту тему писал серию статей, во многом, кстати, схожих с этой)
Если интересует, можем обменяться постовыми. У тебя на этой странице, а у меня — на такой же по тематике статье.
Заинтересует — пишите)
[Ответить]
ZekMan Reply:
Июнь 5th, 2011 at 15:48
Интересует — отписал на почту
[Ответить]
«В самом начале у нас висит функция для генерации случайной строки, она служит для хеша пользователя (чуть позже более подробно).»
А зачем нужна эта функция и зачем записывать в БД полученный хэш?
[Ответить]
ZekMan Reply:
Июль 5th, 2011 at 15:25
Сравнивая ID и хэш пользователя мы допускаем пользователя к странице или наоборот.
ID и хэш в куках должен совпадать с id и хэшем пользователя в бд. Таким образом если у пользователя есть правильных хэш и куки ему не надо авторизовываться снова. Подбор хэша исключаем генерируя его каждый раз при авторизации.
[Ответить]
Нашел много разных статей, где описывается авторизация на сессиях или куки отдельно. Но вот никак не могу воедино собрать — как сделать скрипт как в примере у вас (по-моему мнению у вас один из лучших примеров в сети), но с возможностью выбора пользователя — сохранять пароль в куки или же пользоваться только сессиями.
Вы не планируете написать статью, в котором будет пример разработки скрипта авторизации именно с возможностью выбора (типа галочки «запомнить меня»)? Я был бы просто счастлив почитать такое.
Но и за эту статью огромное спасибо!
[Ответить]
ZekMan Reply:
Июль 5th, 2011 at 15:31
Куки или сессии? Пользователю не надо выбирать алгоритм работы — это ни к чему.
По поводу галочки «Запомнить меня»: Реализуется очень просто. В данном примере нужно добавить еще одно условие на проверку checkbox и если он активен увеличиваем время на которое будет поставлена кука с хешем и id.
Чуть позже я добавлю пару статей в блог, очень давно хочу их дописать но все руки не доходят, среди них будет авторизация на сессиях ( учту и галочку как пожелание 🙂 ) и украшение checkbox и еще много чего. Если хотите — подпишитесь на RSS что бы следить за обновлениями.
[Ответить]
Один вопрос возник) Как сделать так:
index.php
проверяет есть ли куки если нет то login.php подгружается на страницу, после введения логина и пароля перенаправляет отбратно.
а если куки есть то открывает инфу с сайта.
Эээ… Постарался понятно обьяснить))
[Ответить]
ZekMan Reply:
Июль 7th, 2011 at 21:49
check.php переименуйте в index.php — дальше смотрим, первой идет проверка на авторизацию если не авторизован идет перенаправление на форму логина и ставится кука с ошибкой. Вместо перенаправления нужно выдать форму а если человек авторизован то выдать контент — данный пример не очень удобен для разбора такого подхода, нужно переделывать всю логику и переписывать на функции.
Как я говорил выше чуть позже будет статья о авторизации на сессиях. Она будет на функциях сделана примерно так как вы хотите. Потерпите выхода новой статьи или переписывайте логику этой авторизации =)
[Ответить]
Perfecto!
[Ответить]
У меня проблема, почему то не авторизовывается на login.php Запись в базе данных есть с зашифрованным паролем, но при логине он мне говорит что неверно введен пароль или логин… Не могу понять где я накосячил. Спасибо.
[Ответить]
Сделал ) Спасибо
[Ответить]
ZekMan Reply:
Март 25th, 2012 at 21:15
Ну вот и чудно, а я уже хотел что ни будь да ответить))
[Ответить]
Коли ваша статья сподвигла меня изучать PHP ) Нет ли у вас какого нибудь совета как можно интегрировать пользователей из Active Directory ?
[Ответить]
ZekMan Reply:
Март 26th, 2012 at 21:19
Ну судя по поверхностному поиску следует копать в PHP:LDAP или adLDAP. Точнее можно пробежаться по гуглу уже.
Вообще я предпочитаю не держать web сервер на windows — не подходящая платформа.
[Ответить]
что означает атрибут id=»reg_inp» в форме?
[Ответить]
ZekMan Reply:
Май 19th, 2012 at 09:02
Он предназначен для CSS стилизации, но тут скорее всего закралась ошибка, т.к. использование нескольких одинаковых id запрещено.
[Ответить]
В файле check.php у Вас ставятся куки в errors со значением 1, что говорит «включи куки». Получается если человек логинится из другого браузера либо когда время у куков кончилось, либо первый раз, то он видит эту ошибку. Если же куки на самом деле выключены, то пользователь вообще не увидит это сообщение, так как ошибка записывается тоже через куки (которые отключены 🙂 ). Так что куку errors = 1 можно просто не ставить и убрать $error[1] из массива ошибок.
[Ответить]
ZekMan Reply:
Август 3rd, 2012 at 06:35
Мда, надо переписывать))) Этот исходник уже очень стар, в блоге есть гораздо новее на сессиях
[Ответить]
А что если, злоумышленник сворует куки и поставит их себе? ведь у него и hash и id будет тот же самый, и он без проблем зайдет туда куда ему не положено
[Ответить]
Форма выхода, setcookie не переписывает куки…. в чем может быть проблема?
[Ответить]
ZekMan Reply:
Август 27th, 2012 at 14:45
Файл выхода лежит в той же директории что и файл входа? Попробуйте вывести все куки которые есть. т.е. в файле выхода напишите что то типа < ?php print_r($_COOKIE[]); ?> или так же но var_dump посмотрите доступны ли этому файлу куки вообще.
[Ответить]
Доброго времени суток!
Я не совсем понял зачем мучатся с хешем, лишний код, нагрузка на сервер БД при постоянных авторизациях и тд.
Не могли бы Вы кратенько пояснить что это нам даёт ?
или чем опасно просто в куках хранить пароль и логин в md5 ?
Заранее благодарен, Игорь!
[Ответить]
ZekMan Reply:
Октябрь 4th, 2012 at 12:13
Злоумышленник может своровать куки и получить пароль в открытом виде, двойное хеширование так же не безопасно, вообще это авторизация на куках уже довольно старая, как простой пример подойдет. В рабочие сайты с большой посещаемостью я бы не советовал ее ставить. Среди последних постов у меня есть авторизация на сессиях — она предпочтительнее. Хранить в открытом виде можно только логин, пароли надо хранить в хеше с солью, так что бы если злоумышленник и получил хеш пароля — он не мог им воспользоваться ( по крайней мере не так просто). В куки мы ставим id пользователя и личный хеш, куки своруют получат временный кеш но не пароль, и при следующей авторизации личный хеш поменяется.
[Ответить]
Качественно скрипт написан. Отлично
[Ответить]
А можно обьяснить что дальше. Авторизировался. А как сделать дальше? Чтоб можнобыло работать на сайте пока ты авторизирован и запретить при разрыве сесий ? Что-то того?
[Ответить]
ZekMan Reply:
Март 24th, 2013 at 16:14
Посмотрите на пример файла check.php
Там осуществляется проверка авторизации, если пользователь не авторизован его с этой страницы перебросит на страницу авторизации login.php.
Возьмите это за пример
[Ответить]
Доброго времени суток.
У меня вопрос — мы создаем таблицу с пользователями, но мы ведь это делаем от имени супер пользователя root (или другое установленное имя при установке mysql).
При этом для того чтобы пользователь из созданной нами таблицы смог войти ему нужно для начала соедениться с бд.
А для доступа к таблицам из бд у него прав нет… так как формально это просто таблица лежащая в бд.
Таблица содержащая пользователей с доступом к бд лежит в MySql / user — правильно ли я понимаю, чтоб сделать полноценный доступ нужно дописывать пользователей в базу MySql / user с назначением прав, а в нашей таблице просто дублировать эти данные + дополнительные параметры (которые мы хотим присвоить) .
Спасибо за ваш ответ.
[Ответить]
ZekMan Reply:
Май 13th, 2013 at 07:07
возможно что бы избежать вопросов таких мне как ни будь стоит подробно описать процесс создания баз и работы с бд в целом.
Обычно я поступаю просто, создаю нового пользователя (для каждого проекта/субдомена) и создаю базу с именем пользователя, предоставляя на нее все права. В том же самом phpmyadmin это выглядит так:
http://storage6.static.itmages.ru/i/13/0513/h_1368414371_7347828_edadb61379.png
Ну а от рута в бд я сижу просто потому что приходится работать с кучей баз, и для каждой из них свои пользователи.
[Ответить]
Спасибо за материал!
В контрольной использую вашу авторизацию. В форму регистрации добавлено несколько полей. При регистрации в БД не записывает кириллицу.
Подскажите как исправить.
[Ответить]
ZekMan Reply:
Май 13th, 2013 at 06:59
на сколько я помню кирилица спокойно проходила, проверьте кодировки. Ну и если с кодировками все нормально то опишите ситуацию более подробно
[Ответить]
Ввожу вот это условие из Вашего примера:
# проверям логин
if(!preg_match(«/^[a-zA-Z0-9]+$/»,$_POST[‘login’]))
{
$err[] = «Логин может состоять только из букв английского алфавита и цифр»;
}
Вставляю себе в код:
$login = $_POST[‘login’];
if ($login != «»)
{
if(!preg_match(«/^[a-zA-Z0-9]+$/»,$_POST[‘login’]))
{
if ((strlen($login) >= 2) and (strlen($login) <= 25))
{
echo $login;
}
else
{
echo "Вы ввели не корректные данные в поле \"Имя\"";
echo "»;
}
}
else
{
echo «буквы не английские»;
}
}
else
{
echo «Вы не ввели данные в поле login»;
}
Когда заполняю поле с логином английскими буквами, и нажимаю кнопку для отправки в файл обработки, мне выдает, что буквы не английские, русские буквы почему-то выводяться нормально?
[Ответить]
if (preg_match(‘#^[A-Za-z0-9]+$#’,$login)), а вот так ввел, работает!
[Ответить]
ZekMan Reply:
Май 29th, 2013 at 21:08
Я рад что Вы самостоятельно разобрались с проблемой 🙂
[Ответить]
Warning: Cannot modify header information — headers already sent by (output started at Z:\home\localhost\www\hyip\account\login.php:8) in Z:\home\localhost\www\hyip\account\login.php on line 45
И так со всеми заголовками… Делаю на Денвере. Что делать? 🙁
[Ответить]
ZekMan Reply:
Июнь 3rd, 2013 at 21:18
у вас что то выводиться до старта сессии.
session_start должно быть до вывода чего либо на экран. Так же проверьте что бы файл был сохранен как utf-8 без BOM.
[Ответить]
ZekMan Reply:
Июнь 3rd, 2013 at 21:18
у вас что то выводиться до старта сессии.
session_start должно быть до вывода чего либо на экран. Так же проверьте что бы файл был сохранен как utf-8 без BOM.
[Ответить]
Warning: Cannot modify header information
что делать?
[Ответить]
Код не понравился. Особенно не понравилась подключение к БД. Автор, вы, я надеюсь, знакомы с таким понятием, как — класс? Видимо нет. Я понимаю, если пишешь одну страничку с регистрацией, то такой код приемлем. В остальных случаях так можно просто базу «убить».
[Ответить]
ZekMan Reply:
Сентябрь 7th, 2013 at 18:34
Код приведен в ознакомительных целях поэтому не нагружен ООП и прочими радостями жизни. Цель — показать логику максимально доходчиво. Отнюдь не каждый может сразу понять как функционируют функции и классы, их области видимости и т.д. когда начинает учиться программировать.
Кто работает в веб разработке — даже не будет читать подобные статьи, он уже знаком с тем как правильно организовывать код, как построить свое приложение. Еще раз повторюсь что данная статья ориентирована на новичков.
Если
[Ответить]
Доброго времени суток. Куда обработку выхода писать (в какой файл)? Можно подробнее про него?
[Ответить]