Поругайте авторизацию и межстраничную валидацию пользователя.

Статус
В этой теме нельзя размещать новые ответы.

igortik

Новичок
Поругайте авторизацию и межстраничную валидацию пользователя.

Зада типичная - создать систему авторизации для сайта с возможностью дальнейшей поддержки активности "соединения" клиента и сервера.
Работает на COOKIE.

Server:

Register_globals = off.

Реализация:

- Таблица users для хранения id, name, status, blocked, ip, x_ip (другие параметры я опускаю, используются в личных целях)
-- status и blocked - активность аккаунта и статус блокировки соответственно

- Таблица users_login_session для хранения user_id, hash, ip, x_ip, last_login
-- hash - уникальный MD5 хеш, полученный конкатенацией текущего времени, ID пользователя и имени

Скрипты.

1. Авторизация

PHP:
if($_POST['doLogin'])
		{
		//Входящие переменные
		$ip = $_SERVER['REMOTE_ADDR'];
		$x_ip = $_SERVER['HTTP_X_FORWARDED_FOR']; 
		$remindme = 1; // Необходимо включить в настройки
		$login = mysql_real_escape_string($_POST['login']);
		$password = $_POST['password']; //Иправлено. Благодарности *****'у. Экранировать нельзя, т.к. получим иной хеш в случае с паролем типа: er'drgk09.99
		//-------------------------------------------------------
		$search_user = mysql_query("SELECT `id`,`name`,`status`,`blocked` FROM `users` WHERE `login`='$login' AND `pass`='".md5($password)."' LIMIT 1") or die(mysql_error());
			if(mysql_num_rows($search_user) == 1)
				{
				//echo 'Пользователь существует';
				$user_id = mysql_result($search_user,0,'id');
				$user_name = mysql_result($search_user,0,'name');
				$status = mysql_result($search_user,0,'status');
				$blocked = mysql_result($search_user,0,'blocked');
				if($blocked == 1) {header("Location: ".$_SERVER['PHP_SELF']."?mod=say&message=11"); exit;} //Заблокирован
				if($status == 0) {header("Location: ".$_SERVER['PHP_SELF']."?mod=say&message=12"); exit;} //Не активирован
				//Создаем сессию пользователя в базе
				$current_time = time();
				/* Генерируем уникальный hash
				Данный хеш запишется в куку для дальнейшей проверки!
				Из кук он будет сопоставляться с текущим значением в таблице `users_login_sessions` для конкретного ID пользователя.
				В случае подмены хеша валидация не пройдет. 
				*/
				$user_hash = md5($current_time.$user_id.$user_name);
				mysql_query("UPDATE `users_login_session` SET `hash`='$user_hash',`ip`='$ip',`x_ip`='$x_ip' WHERE `user_id`='$user_id'") or die(mysql_error());
				//----------------------------------
				//Запомнить на компьютере (в COOKIE)
				if($remindme == 1) //переименовать переменную для эстетики
					{
					setcookie('user_id',$user_id,time() + (3600 * 24 * 365),"/");
					setcookie('user_hash',$user_hash,time() + (3600 * 24 * 365),"/");
					}
					else
						{
						setcookie('user_id',$user_id,time() + 3600,"/");
						setcookie('user_hash',$user_hash,time() + 3600,"/");
						}
				//-----------------------------------
				}
		}
2. Скрипт проверки валидности соединения

PHP:
if((!isset($_COOKIE['user_id'])) && (!isset($_COOKIE['user_hash']))) 
	{
	$session_valid = 0;
	} 
	else {
		 //Обрабатываем данные из куки
		 $user_id = intval($_COOKIE['user_id']);
		 $user_hash = mysql_real_escape_string($_COOKIE['user_hash']);
		 }	
	if($user_id != 0) 
		{
		//echo 'user_hash: '.$_COOKIE['user_hash']."<br>";
		//При успешной подмене значений кук пользователь прийдет к этой стадии!
		//Сравниваем полученный из кук хеш с тем, который существует в данный момент в таблице сессий для конкретного ID пользователя.
		//Так как хеш уникален в базе и генерируется только п офакту ввода верного пароля, то можно предположить, что на лицо подмена куки в случае неудачи.	
		$find_user = mysql_query("SELECT users.id,users.name,users_login_session.hash FROM `users` 
INNER JOIN `users_login_session` ON users_login_session.user_id = users.id WHERE `user_id`='$user_id' LIMIT 1") or die(mysql_error());	
		if(mysql_num_rows($find_user) == 1) 
			{
			$db_user_hash = mysql_result($find_user,0,'hash');
			//echo 'DB_hash: '.$db_user_hash."<br>";
			/* Проверяем hash
			Хеш может отличаться только в том случае, если намеренно были изменены куки
			*/
			if($_COOKIE['user_hash'] == $db_user_hash)	
				{
				//Сессия активна
				echo 'Сессия активна';
				$session_valid = 1;
				}
				else
					{
					//Закрываем сессию (подмена куки)
					echo 'Сессия закрыта';
					$session_valid = 0;
					}		
			}	
		}
3. Выход из системы (придумал на ходу пока писал контент этой темы:))
PHP:
//Я использую эту конструкцию всегда на своих проектах для удобства управления действиями
switch($_GET['do'])
 {
 case logout:
 //Только в случае валидности текущего соединения мы можем его разорвать
 if($session_valid == 1)
  {
  setcookie('user_id','',time() - 3600,"/");
  setcookie('user_hash','',time() - 3600,"/");
  mysql_query("UPDATE `users_login_session` SET `hash`='' WHERE `user_id`='".intval($_COOKIE['user_id'])."'") or die(mysql_error());
  //В данном случае соединение разрывается полностью. Даже для тех, кто украл куку, т.к. хеш обнуляется и скрипт проверки подлинности вернет FALSE
  }
  break;
 }
Видимые мною недостатки:

1. Постоянно общается одним запросом с базой для проверки валидности соединения
2. Куки могут быть украдены и защита не сработает (по вине пользователя)

Преимущества:

1. Адекватная реакция на блокировку аккаунта
2. Нет постоянного открытия сессий
3. Hash всегда уникален в базе и принадлежит тому, кто правильно ввел комбинацию логин-пароль
4. Повторный логин в системе обнуляет hash в `users_login_session`, что припятствует укравшему куку, залогиниться.
(new) 5. При выходе пользователем из аккаунта шансов войти у злоумышленника, укравшего куки - нет

Сразу же вопрос:

Валидация активного соединения определяется по конечному значению переменной $session_valid и в зависимости от ее значения пользователю открываются функции!
Допустим ли такой метод? Если нет, - прошу обосновать. Может вместо переменной использовать константу для гибкости работы функций?

В общем.. Ваши замечания ...

-~{}~ 23.06.09 13:10:

P.S. Уже вижу недочет с вылогиниванием, т.к. нам надо как-то передавать переменную $session_valid.

Sessions maybe ?
 

zerkms

TDD infected
Команда форума
зачем таскать в куках user_id, если он и так может быть получен из таблы авторизаций по user_hash?
 

igortik

Новичок
zerkms
хм... действительно...

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

-~{}~ 23.06.09 13:21:

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

Учитывая, что валидация стоит по приоритету на втором плане после инклуда с соединением к базе, то при подмене сработает защита и редирект.

Потому, полагаю, будет безопасно вытягивать из базы по $_COOKIE['user_id'].

-~{}~ 23.06.09 13:22:

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

zerkms

TDD infected
Команда форума
igortik
ты так и так проверяешь
$find_user = mysql_query("SELECT users.id,users.name,users_login_session.hash FROM `users`
INNER JOIN `users_login_session` ON users_login_session.user_id = users.id WHERE `user_id`='$user_id' LIMIT 1")

какая разница - WHERE user_id или user_hash?

после валидации на руках у тебя будет user_id
 

igortik

Новичок
zerkms
Да, ты прав!

стоит оптимизировать.

-~{}~ 23.06.09 13:37:

Епт...

А когда юзеров будет 1000 и более со своими хешами, то гораздо больше нагрузка будет на базу!
Ведь записи сессий не трутся из базы, а идут как уникальный лог для каждого....

Ведь валидация проходит каждый раз по мере перехода по страницам...

Какие мысли на этот счет?
 

zerkms

TDD infected
Команда форума
как будет проблема - так и озвучишь её.
пока - не забивай голову.
 

igortik

Новичок
P.S. Я не силен по части БД, потому не могу адекватно оценить степень разницы нагрузки при выборке по int значению и многозначному строчному
 

zerkms

TDD infected
Команда форума
P.S. Я не силен по части БД, потому не могу адекватно оценить степень разницы нагрузки при выборке по int значению и многозначному строчному
вот именно поэтому эта часть проекта у тебя НИКОГДА не будет самой медленной (см. самой перспективной для оптимизации).
 

igortik

Новичок
zerkms
Т.е. ты имеешь ввиду, что я не смогу загружать базу тяжелыми запросами? :)

Если да, то это так.. я стараюсь по мере возможности использовать простые селекты, когда есть возможность, но самокритика грызет уже какой месяц.
 

Фанат

oncle terrible
Команда форума
Я зарегистрировался. У меня пароль er'drgk09.99
меня не пускает. подскажите, пожалуйста, в чем может быть проблема?

-~{}~ 23.06.09 13:48:

а блокировка работает, проверял?
 

igortik

Новичок
*****
ты попал в точку.

Я не фильтрую при регистрации логин и пароль.
Проблема - незнание регулярных выражений.

Прошу поделить материалом на эту тему.

Мне необходимо проверить логин и пароль так, чтобы в нем были только числа или латинские буквы.
 

Фанат

oncle terrible
Команда форума
при чем здесь фильтрация?
чтобы в нем были только числа или латинские буквы
зачем это в пароле?

проблема твоя совсем не в незнании регулярных выражений, а в непонимании работы с базой данных

-~{}~ 23.06.09 13:50:

зачем куки ставятся при $rememberme = 0 я не понял
 

igortik

Новичок
*****
Да, проверял.

Если хеш кук совпадает с хешем из базы, то переменная $session_valid принимает значение '1'
 

prolis

Новичок
1. зачем нужна users_login_session, users не хватит?
2. если посетитель не разлогинется сам, что будет?
 

Фанат

oncle terrible
Команда форума
странно. на первый взгляд не должна работать
как и статуса

Если хеш кук совпадает с хешем из базы, то переменная $session_valid принимает значение '1'
а зачем вообще нужна эта проверка? почему сессии недостаточно?
 

igortik

Новичок
*****
куки ставятся, чтобы не стартовать сессию.
Фактически, я делаю то же самое, что и сессия, отличие лишь в методе.

хотя, чем дальше смотрю, тем более думаю, что все же здесь нужна сессия, чтобы было все корректно.

-~{}~ 23.06.09 13:56:

*****
1. Логин

- При успешной проверке логина и пароля генериуем хеш и пишем в отдельную таблицу
- пишем тот же хеш в куку

2. Валидация

- далее проверяем наличие кук
- при наличии значений проверяем их подлинность
- если все верно, то присваиваем ключевой переменной значение
 

Фанат

oncle terrible
Команда форума
Да нет, можно и без сессии. Я пропустил, что ее нету.
остаются проблемы с пониманием работы БД и протокола HTTP.
 

Фанат

oncle terrible
Команда форума
что делает строчка
$password = mysql_real_escape_string($_POST['password']);
и зачем она это делает?
 

igortik

Новичок
*****
экранирует кавычки, перед тем, как подставить в базу, чтобы не было возможности вместо пароля ввести часть mysql-запроса

-~{}~ 23.06.09 14:07:

а действительно, нафига его экранировать-то ...
 
Статус
В этой теме нельзя размещать новые ответы.
Сверху