Безопасный метод авторизации на PHP

Written by

in

\"\"Давайте посмотрим вокруг: форумы, интернет магазины, гостевые книги и т.д. используют регистрацию и последующую авторизацию пользователей. Можно даже сказать, что это почти необходимая функция каждого сайта (только если это не домашняя страничка Васи Пупкина или не визитная карточка, какой-нибудь небольшой компании). Сегодня я хочу поделиться со всеми новичками информацией, о том, как лучше это все реализовать.

1. Модель (клиент) 
Регистрация
– логин (a-z0-9)
– пароль
Вход
– логин
– пароль
Cookie
– уникальный идентификатор юзера
– хэш
Модель (сервер) MySQL
Таблица users
user_id (int(11))
user_login (Varchar(30))
user_password (varchar(32))
user_hash (varchar(32))
user_ip (int(10)) по умолчанию 0При регистрации в базу данных записываеться логин пользователя и пароль(в двойном md5 шифровании)

При авторизация, сравниваеться логин и пароль, если они верны, то генерируеться случайная строка, которая хешируеться и добавляеться в БД в строку user_hash. Также записываеться IP адрес пользователя(но это у нас будет опциональным, так как кто-то сидит через Proxy, а у кого-то IP динамический… тут уже пользователь сам будет выбирать безопасность или удобство). В куки пользователя мы записываем его уникальный индетификатор и сгенерированный hash.

Почему надо хранить в куках хеш случайно сгенерированной строки, а не хеш пароля?
1. Из-за невнимательности программиста, во всей системе могут быть дырки, воспользовавшийсь этими дырками, злоумышленик может вытащить хеш пароля из БД и подставить его в свои куки, тем самым получить доступ к закрытым данным. В нашем же случае, двойной хеш пароля не чем не сможет помочь хакеру, так как расшифровать он его не сможет(теоретически это возможно, но на это он потратит не один месяц, а может быть и год) а воспользоваться этим хешем ему негде, ведь у нас при авторизации свой уникальный хеш прикрепленный к IP пользователя.
2. Если злоумышленик вытащит трояном у пользователя уникальный хеш, воспользовать им он также не сможет(разве если только, пользователь решил принебречь своей безопастностью и выключил привязку к IP при авторизации).

2. Практика
--
— Структура таблицы `users`
—
CREATE TABLE `users` (
`user_id` int(11) unsigned NOT NULL auto_increment,
`user_login` varchar(30) NOT NULL,
`user_password` varchar(32) NOT NULL,
`user_hash` varchar(32) NOT NULL,
`user_ip` int(10) unsigned NOT NULL default \'0\',
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251 AUTO_INCREMENT=1 ;

register.php 
<?
// Страница регситрации нового пользователя

# Соединямся с БД
mysql_connect(\”localhost\”, \”myhost\”, \”myhost\”);
mysql_select_db(\”testtable\”);

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(user_id) FROM users WHERE user_login=\’\”.mysql_real_escape_string($_POST[\’login\’]).\”\’\”);
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 user_login=\’\”.$login.\”\’, user_password=\’\”.$password.\”\’\”);
header(\”Location: login.php\”); exit();
}
else
{
print 
\”<b>При регистрации произошли следующие ошибки:</b><br>\”;
foreach(
$err AS $error)
{
print 
$error.\”<br>\”;
}
}
}
?>

<form method=\”POST\”>
Логин <input name=\”login\” type=\”text\”><br>
Пароль <input name=\”password\” type=\”password\”><br>
<input name=\”submit\” type=\”submit\” value=\”Зарегистрироваться\”>
</form>

login.php

<?
// Страница авторизации

# Функция для генерации случайной строки
function generateCode($length=6) {
$chars = \”abcdefghijklmnopqrstuvwxyzABCDEFGHI JKLMNOPRQSTUVWXYZ0123456789\”;
$code = \”\”;
$clen = strlen($chars) – 1;  
while (strlen($code) < $length) {
$code .= $chars[mt_rand(0,$clen)];  
}
return 
$code;
}

# Соединямся с БД
mysql_connect(\”localhost\”, \”myhost\”, \”myhost\”);
mysql_select_db(\”testtable\”);

if(isset($_POST[\’submit\’]))
{
# Вытаскиваем из БД запись, у которой логин равняеться введенному
$query = mysql_query(\”SELECT user_id, user_password FROM users WHERE user_login=\’\”.mysql_real_escape_string($_POST[\’login\’]).\”\’ LIMIT 1\”);
$data = mysql_fetch_assoc($query);

# Соавниваем пароли
if($data[\’user_password\’] === md5(md5($_POST[\’password\’])))
{
# Генерируем случайное число и шифруем его
$hash = md5(generateCode(10));

if(!@$_POST[\’not_attach_ip\’])
{
# Если пользователя выбрал привязку к IP
# Переводим IP в строку
$insip = \”, user_ip=INET_ATON(\’\”.$_SERVER[\’REMOTE_ADDR\’].\”\’)\”;
}

# Записываем в БД новый хеш авторизации и IP
mysql_query(\”UPDATE users SET user_hash=\’\”.$hash.\”\’ \”.$insip.\” WHERE user_id=\’\”.$data[\’user_id\’].\”\’\”);

# Ставим куки
setcookie(\”id\”, $data[\’user_id\’], time()+60*60*24*30);
setcookie(\”hash\”, $hash, time()+60*60*24*30);

# Переадресовываем браузер на страницу проверки нашего скрипта
header(\”Location: check.php\”); exit();
}
else
{
print 
\”Вы ввели неправильный логин/пароль\”;
}
}
?>
<form method=\”POST\”>
Логин <input name=\”login\” type=\”text\”><br>
Пароль <input name=\”password\” type=\”password\”><br>
Не прикреплять к IP(не безопасно) <input type=\”checkbox\” name=\”not_attach_ip\”><br>
<input name=\”submit\” type=\”submit\” value=\”Войти\”>
</form>

check.php

<?
// Скрипт проверки

# Соединямся с БД
mysql_connect(\”localhost\”, \”myhost\”, \”myhost\”);
mysql_select_db(\”testtable\”);

if (isset($_COOKIE[\’id\’]) and isset($_COOKIE[\’hash\’]))
{
$query = mysql_query(\”SELECT *,INET_NTOA(user_ip) FROM users WHERE user_id = \’\”.intval($_COOKIE[\’id\’]).\”\’ LIMIT 1\”);
$userdata = mysql_fetch_assoc($query);

if(($userdata[\’user_hash\’] !== $_COOKIE[\’hash\’]) or ($userdata[\’user_id\’] !== $_COOKIE[\’id\’])
or ((
$userdata[\’user_ip\’] !== $_SERVER[\’REMOTE_ADDR\’])  and ($userdata[\’user_ip\’] !== \”0\”)))
{
setcookie(\”id\”, \”\”, time() – 3600*24*30*12, \”/\”);
setcookie(\”hash\”, \”\”, time() – 3600*24*30*12, \”/\”);
print 
\”Хм, что-то не получилось\”;
}
else
{
print 
\”Привет, \”.$userdata[\’user_login\’].\”. Всё работает!\”;
}
}
else
{
print 
\”Включите куки\”;
}
?>

Для защиты формы логина от перебора, можно использовать капчу.

Хочу отметить, что здесь я рассматривал авторизацию основоную на cookies, не стоит в комментариях кричать, что сессии лучше/удобнее и т.д. Спасибо.

Примечания:
1. mysql_select_db(\”testtable\”); // нелогичное название базы, testdb лучше
2. Нужно использовать mysql_real_escape_string()
3. Убирать лишние пробелы нет смысла, так как их не пропустит вот эта строка:
if(!preg_match(\”/^[a-zA-Z0-9]+$/\”,$_POST[\’login\’]))

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *