понедельник, 10 августа 2009
Так вот, я вновь вернулся к этому вопросу =)Порылся по интернету, в итоге ориентируясь на советы собрал вот такой вот код:
читать дальше<?php
define(TIME_COOKIE, 604800); // воемя жизни кук (неделя)
define(DOMEN, 'domen.com'); // домен
// проверяет, существует ли пользователь в БД
function authLogin($login, $pass)
{
$login = mysql_real_escape_string($login);
$pass = md5($pass);
// делаем запрос к БД
$query = "SELECT `id` FROM `users` WHERE (`login` = '{$login}' AND `pass` = '{$pass}') LIMIT 1";
$sql = mysql_query($query);
// проверяем, найден ли пользователь с таким паролем
if(mysql_num_rows($sql) == 0)
{
$rows = mysql_fetch_assoc($sql);
return intval($rows['id']);
}
else
return false;
}
// проверяет ip
function authIP()
{
// вообще весь IP проверять нежелательно - достаточно только первые цифры
// вынесенно в функцию для возможности последующей модификации
if($_SESSION['ip'] == $_SERVER['REMOTE_ADDR'])
return true;
else
return false;
}
//----------------------------------------------------------------------------------------------
// если ранее сессия запущена не была
// тут два варианта проверки:
// либо (session_id() == '') либо (isset($_COOKIE['PHPSESSID']))
// какой лучше?
if(session_id() == '')
{
// если в куках хранятся логин-пароль
if(isset($_COOKIE['login']) && isset($_COOKIE['pass']))
{
// если логин-пароль правильные
if($id = authLogin($_COOKIE['login'], $_COOKIE['pass']))
{
// здесь можно добавить дополнительную проверку браузера пользователя
// if(isset($_COOKIE['myKey']) && $_COOKIE['myKey'] == md5($_SERVER['HTTP_USER_AGENT'] . $_SERVER['HTTP_ACCEPT_CHARSET']))
session_start();
$_SESSION['id'] = $id;
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
// продляем жизнь кукам
setcookie('login', $_COOKIE['login'], time() + TIME_COOKIE, '/', '.'.DOMEN);
setcookie('pass', $_COOKIE['pass'], time() + TIME_COOKIE, '/', '.'.DOMEN);
}
// если логин-пароль неправильные
else
{
// удаляем куки
setcookie('login', '', time() - TIME_COOKIE, '/', '.'.DOMEN);
setcookie('pass', '', time() - TIME_COOKIE, '/', '.'.DOMEN);
// надо ли делать переадресацию?
// header("Location: http://".DOMEN.$_SERVER['REQUEST_URI']);
}
}
// если в куках ничего нет
else
{
// если пользователь пытается авторизироваться
if(isset($_POST['login']) && isset($_POST['pass']))
{
// если такой пользователь найден
if($id = authLogin($_POST['login'],$_POST['pass']))
{
session_start();
$_SESSION['id'] = $id;
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
// устанавливаем куки
// надо ли тут делать проверку post-данных?
setcookie('login', $_POST['login'], time() + TIME_COOKIE, '/', '.'.DOMEN);
setcookie('pass', $_POST['pass'], time() + TIME_COOKIE, '/', '.'.DOMEN);
}
}
else
{
// надо как-то уведомить пользователя
// что введенные данные не верны
}
}
}
// если сессия уже была запущена
else
{
session_start(); // запускаем сессию
if(!authIP()) // если IP не совпадает
{
session_unset(); // уничтожаем переменные
session_destroy(); // уничтожаем сессию
// надо ли здесь чистить куки?
}
}
// -----------------------------------------------------------------------------------------
if(isset($_SESSION['id']))
{
// пользователь авторизировался
}
else
{
// форма авторизации
}
?>
Собственно, какие ошибки я тут допустил и как их избежать?
Что еще можно дополнить?
+ по ходу кода оставил еще несколько вопросов.
P.S. Хотел изначально весь алгоритм выложить ввиде блок-схемы, но понял что это займет черезчур много времени.
P.P.S. Касательно передачи идентификатора сессии через get-запросы, то думаю что лучше вообще этого не делать, так как черезчур легко украсть.
@темы:
Безопасность,
PHP
Ну и какого ? Если найдена запись в БД, ты возвращаешь FALSE. А потом внизу проверка, где для авторизации нужен TRUE. Зачем тут ? Вроде нужно
Или я что-то недосмотрел ?
Кстати насчет запросов, наверное лучше уж использовать mysql_result. )
Рекомендую сделать здержку если авторизация не удалась.
В каком плане задержку? Чтобы с одного ip можно было посылать запросы с интервалом не менее числа n?
Рекомендую сделать здержку если авторизация не удалась.
В случае кражи сессии если попытаться зайти с другово ip - сессия уничтожится.. Думаю будет очень полезно.
лучше уж использовать mysql_result
Ну в данном случае - да, но помимо id вероятно потребуются и другии поля - тут я просто упростил до мксимума.
SpiritEagle
Ну и какого ?
Это опечатка) Я сначала написал mysql_num_rows($sql) > 0, потом подумал, что число строк по идеи должно равняться 1, т.к. LIMIT 1, а вот нолик исправить забыл).
Я имел ввиду не синтаксические ошибки - их я и сам найду. А именно ошибки в логике алгоритма.
Ну и:
1. нужно ли проверять браузер при старте сессии? по идеи если хранить мд5-хэш в куках, то при краже кук он не поможет..
2. Случаи с ошибками - как их обрабатывать? И как уведомлять пользователя об ошибке?
Самый наверное банальный вариант - это создать отдельную переменную, инициалировав ее нулем, и в случае ошибки присвоить ей номер. А по номеру ниже уже обрабатывать..
3. И, собственно, самое главное - а как именно вообще можно украсть сессию?
Я лишь смутно знаю, что какой-то там троян может скопировать инфу из кук и отослать куда-либо - от этого я не спасусь. А как еще можно?
а) При авторизации происходит задержка - исключает возможность брутфорса, пользователь не заметит 500 мс, а бот поляжет.
б) В случае успешной авторизации в куки закидывается логин и специальный ключ (длинная строка никак не зависящая от пароля), далее пара ключ ип заносится в базу.
в) При получении куков проверяется массив ключей пользователя, за счет того что ключей может быть несолько пользователь может быть как дома так и на работе с одного логина. Чистка устаревших ключей производится автоматически, и не зависит от времени жизни куков. Если куки кривые - задержка.
г) Дополнительно есь возможность авторизации через post запрос, иначе не работют некоторые флеш загрузчики.
И напоследок. Хранить логин и пароль в куках - плохо. Украдут будет плохо. У меня при наличии связки ключ-ип каждый ключ действует для своего ип (нажимая "запомнить" дествует две недели).
Плюс пользователь может видеть все свои сессии. Т.е. когда и с какого ип в его аккаунт входили и грохнуть сессии (как в гугле).
Вот так.
sleep(5); чтоль?
б) В случае успешной авторизации в куки закидывается логин и специальный ключ (длинная строка никак не зависящая от пароля), далее пара ключ ип заносится в базу.
По сути ключ - это тот же session_id привязанный к ip, только существующий после завершения работы браузера.
+ Пароль не хранится в куках
+ Доступ в этом случае с нового IP можно будет получить только вводом логина-пароля
Но:
1 А если IP у пользователя динамический? То каждый раз ему придется авторизироваться заного?
2 Как хранить массив данных - сериализация, explode () или отдельные поля для ограниченного количества IP?
пользователь может видеть все свои сессии
Хм.. А это интересно)
sleep(5); чтоль?
Ну вот одна буковка и куча нервов: usleep(500);
2 Как хранить массив данных - сериализация, explode () или отдельные поля для ограниченного количества IP?
Как душа ляжет, в любом случае их обработка уже после выборки. Мой выбор serialize как наиболее быстрый и удобный.
1 А если IP у пользователя динамический? То каждый раз ему придется авторизироваться заного?
Вы переоцениваете динамические ип. ) Я делал так что можно и отрубить слежку по ип, но в любом случае смена ип = выброс из интернета.
Тогда проблема динамического ип для меня была весьма актуальна)
Ладно, мысль понял - завтра переработаю скрипт
Ах, да, кстати, насколько я понял сессии в этом случае и вовсе не понядобятся?