Пишем авторизацию пользователя на PHP

Совсем недавно я рассказывал, как при помощи PHP написать систему регистрации для своего сайта. Такой же принцип мы использовали и в своём проекте, созданию которого посвящён раздел «Сайт с нуля» на этом блоге (сам проект я покажу вам гораздо позже). Сегодня же я опишу, как написать авторизацию на сайте, используя данные, полученные от пользователя при регистрации. То есть, будет использоваться таблица MySQL, структура которой была описана в статье про регистрацию. Поэтому я настоятельно рекомендую прежде прочитать ту статью, ибо данная статья является её непосредственным продолжением. Авторизация будет работать с использованием сессий и cookie. Также в статье будет рассмотрено несколько приятных дополнений, таких, как «разлогинивание» (выход) и время последней активности пользователя. Итак, приступим…

Для начала необходимо сверстать главную страницу сайта и поместить её в корне сайта в папку template. Для данного урока нам достаточно, чтобы в этом файле была форма ввода логина и пароля, а также кнопка «Вход». Далее приведён код этой формы:

1
2
3
4
5
<form action="/" method="post">
 Логин: <input type="text" name="login" />

 Пароль: <input type="password" name="password" />
 <input type="submit" value="Войти" name="log_in" />

 </form>

Файл назовём index.html.

Метод передачи post необходим. Ведь мы не хотим, чтобы при авторизации логин и пароль светились в адресной строке.

Как только форма готова, создадим самый важный файл будущего сайта — главный контроллер, т. е. файл, лежащий в корне сайта — index.php. Именно он и будет запускаться при входе на сайт. На момент написания статьи на нашем проекте код этого файла занимает 92 строки, нам же понадобится пока лишь около 25 строк. Вот его код:

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
<?
include ('lib/connect.php'); //подключаемся к БД
include ('lib/module_global.php'); //подключаем файл с глобальными функциями

 
if($_GET['action'] == "out") out(); //если передана переменная action, «разавторизируем» пользователя
 
if (login()) //вызываем функцию login, определяющую, авторизирован юзер или нет

{
	$UID = $_SESSION['id']; //если юзер авторизирован, присвоим переменной $UID его id
	$admin = is_admin($UID); //определяем, админ ли юзер

}
else //если пользователь не авторизирован, то проверим, была ли нажата кнопка входа на сайт
{
	if(isset($_POST['log_in'])) 
	{
		$error = enter(); //функция входа на сайт

		if (count($error) == 0) //если нет ошибок, авторизируем юзера
		{
			$UID = $_SESSION['id'];

			$admin = is_admin($UID);
		}
	}
}
include ('tpl/index.html'); //подключаем файл с формой

?>

Теперь более подробно разберёмся, как всё это работает.

В первых трёх строках мы просто подключаем файлы с функциями, которые будем использовать далее в коде. О них чуть позже. Далее проверим, был ли передан get-параметр action=out. Если он был передан, значит пользователь нажал на ссылку выхода с сайта. Вот, кстати, код этой ссылки. Добавьте его в файл с кодом формы для входа.

<a href="/?action=out">Выход</a>

Саму функцию, как и все остальные, рассмотрим позже. Сперва логика…

Далее идёт условие, проверяющее авторизирован ли ты (if (login())). Функция возвращает true в случае, если пользователь вошёл на сайт и false в противном случае. Если вернулось true, записываем в переменную $UID id юзера, а в переменную $admin — результат работы функции is_admin($UID). Данная функция определяет, является ли пользователь администратором и возвращает true, если юзер — админ и false в противном случае. В дальнейшем две эти переменные будут необходимы для вывода определённых элементов на странице. Так, следующим условием можно вывести форму авторизации:

1
2
3
4
5
6
7
8
9
10
<?
If($UID) //если переменной нет, выводим форму
{?>
<form action="/" method="post">

Логин: <input type="text" name="login" />
Пароль: <input type="password" name="password" />

<input type="submit" value="Войти" name="log_in" />
</form>
<?}
?>

Аналогично и с переменной $admin. Кстати, последний код можно включить в файл с формой.
Если же функция login() вернёт false, т. е. пользователь не вошёл на сайт, проверим, нажал ли он на кнопку входа на сайт в форме авторизации:

if(isset($_POST['log_in']))

Если да, запускаем функцию enter(), авторизирующую пользователя. Если ошибок не произойдёт и юзер успешно вошёл, создадим те же 2 переменные: $UID и $admin. В противном случае никакие переменные не создаются – пользователь является гостем. Алгоритм работы представлен на следующей схеме:

Теперь разберёмся со всеми функциями, вызываемыми в данном коде. В первую очередь опишу функцию входа на сайт:

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

function enter ()
 { 
$error = array(); //массив для ошибок 	
if ($_POST['login'] != "" && $_POST['password'] != "") //если поля заполнены 	

{ 		
	$login = $_POST['login']; 
	$password = $_POST['password'];

	$rez = mysql_query("SELECT * FROM users WHERE login=$login"); //запрашиваем строку из БД с логином, введённым пользователем 		
	if (mysql_num_rows($rez) == 1) //если нашлась одна строка, значит такой юзер существует в БД 		

	{ 			
		$row = mysql_fetch_assoc($rez); 			
		if (md5(md5($password).$row['salt']) == $row['password']) //сравниваем хэшированный пароль из БД с хэшированными паролем, введённым пользователем и солью (алгоритм хэширования описан в предыдущей статье) 						

		{ 
		//пишем логин и хэшированный пароль в cookie, также создаём переменную сессии
		setcookie ("login", $row['login'], time() + 50000); 						
		setcookie ("password", md5($row['login'].$row['password']), time() + 50000); 					
		$_SESSION['id'] = $row['id'];	//записываем в сессию id пользователя 				

		$id = $_SESSION['id']; 				
		lastAct($id); 				
		return $error; 			
	} 			
	else //если пароли не совпали 			

	{ 				
		$error[] = "Неверный пароль"; 										
		return $error; 			
	} 		
} 		
	else //если такого пользователя не найдено в БД 		

	{ 			
		$error[] = "Неверный логин и пароль"; 			
		return $error; 		
	} 	
} 	
 

	else 	
	{ 		
		$error[] = "Поля не должны быть пустыми!"; 				
		return $error; 	
	} 

}

Первым делом, функция проверяет, заполнил ли пользователь поля для ввода логина и пароля. Если да — продолжаем работу программы, если нет — пишем в массив $error текст ошибки и возвращаем его в основную программу, которая, узнав размерность полученного массива, не авторизирует пользователя.
Если же работа функции enter() продолжится, проверим, существует ли в БД запись с таким ником, какой ввёл юзер. Если такой записи не оказалось, вернём опять же массив с соответствующей ошибкой. Если в БД есть один пользователь с таким ником, сравним введённый пароль с паролем, хранящимся в базе данных и соответствующим нашему нику.

Сравниваем мы пароли не в чистом виде. Ведь в БД они хранятся хэшированными функцией md5(). Поэтому, прежде чем сравнивать их, необходимо тем же алгоритмом хэшировать и введённый пользователем при авторизации пароль. Если хэши совпадут, значит логин и пароль совпали и скрипт авторизирует пользователя. Если совпадения не произошло, вернём ошибку.

Теперь объясню, что же значит «авторизироваться». В данном скрипте данные об авторизации хранятся в сессии и cookie. В сессию записываем id пользователя:

$id = $_SESSION['id'];

И создаём два cookie: login и password с продолжительностью жизни — 50000 секунд. В первый пишем логин, а во второй — хэш пароля.

lastAct($id);

В этой строке мы выполняем функцию, отвечающую за установку времени последней активности юзера. Вот код этой функции:

1
2
function lastAct($id)
{ 	$tm = time(); 	mysql_query("UPDATE users SET online='$tm', last_act='$tm' WHERE id='$id'"); }

Функция перезаписывает поля online и last_act в БД. Кстати, предварительно, необходимо убедиться в существовании этих полей. Оба они имеют тип int.

Алгоритм работы функции enter() приведён на следующей иллюстрации:

Следующая функция отвечает за проверку, авторизирован ли пользователь на сайте или нет — login().

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
function login () { 	
ini_set ("session.use_trans_sid", true); 	session_start();  	if (isset($_SESSION['id']))//если сесcия есть 	

{ 		
if(isset($_COOKIE['login']) && isset($_COOKIE['password'])) //если cookie есть, то просто обновим время их жизни и вернём true 		{ 			
SetCookie("login", "", time() - 1, '/'); 			SetCookie("password","", time() - 1, '/'); 			

setcookie ("login", $_COOKIE['login'], time() + 50000, '/'); 			

setcookie ("password", $_COOKIE['password'], time() + 50000, '/'); 			

$id = $_SESSION['id']; 			
lastAct($id); 			
return true; 		

} 		
else //иначе добавим cookie с логином и паролем, чтобы после перезапуска браузера сессия не слетала  		
{ 			
$rez = mysql_query("SELECT * FROM users WHERE id='{$_SESSION['id']}'"); //запрашиваем строку с искомым id 			

if (mysql_num_rows($rez) == 1) //если получена одна строка 			{ 		
$row = mysql_fetch_assoc($rez); //записываем её в ассоциативный массив 				

setcookie ("login", $row['login'], time()+50000, '/'); 				

setcookie ("password", md5($row['login'].$row['password']), time() + 50000, '/'); 

$id = $_SESSION['id'];
lastAct($id); 
return true; 			

} 
else return false; 		
} 	
} 	
else //если сессии нет, то проверим существование cookie. Если они существуют, то проверим их валидность по БД 	
{ 		
if(isset($_COOKIE['login']) && isset($_COOKIE['password'])) //если куки существуют. 		

{ 			
$rez = mysql_query("SELECT * FROM users WHERE login='{$_COOKIE['login']}'"); //запрашиваем строку с искомым логином и паролем 			
@$row = mysql_fetch_assoc($rez); 			

if(@mysql_num_rows($rez) == 1 && md5($row['login'].$row['password']) == $_COOKIE['password']) //если логин и пароль нашлись в БД 			

{ 				
$_SESSION['id'] = $row['id']; //записываем в сесиию id 				
$id = $_SESSION['id']; 				

lastAct($id); 				
return true; 			
} 			
else //если данные из cookie не подошли, то удаляем эти куки, ибо нахуй они такие нам не нужны 			
{ 				
SetCookie("login", "", time() - 360000, '/'); 				

SetCookie("password", "", time() - 360000, '/');	 				
return false; 			

} 		
} 		
else //если куки не существуют 		
{ 			
return false; 		
} 	
} 
}

Почему для авторизации мы будем использовать и COOKIE и сессию? Дело в том, что после закрытия браузера, сессия «умирает» и пользователь автоматически разлогинивается. Cookie же хранятся определённое, задаваемое нами, время. В данном случае это 50000 секунд.

Итак, разберёмся, как же эта функция работает. Её стоит запускать первой на всех страницах и модулях будущего сайта. Во-первых, она проверяет, авторизирован ли пользователь, что для дальнейшей работы скрипта крайне важно. Во-вторых, она обновляет время последней активности пользователя, а также поможет в будущем ввести систему онлайн-пользователей.

Функция вернёт true, если юзер авторизирован и false в противном случае. Пичём, в процессе её работы, будет обновлено время жизни cookie, а также они будут созданы, если не существуют.

Лучше всего работу функции online описывает эта иллюстрация:

Если есть сессия и cookie, мы обновляем время жизни cookie. Для этого мы их удаляем, устанавливая время смерти на одну секунду раньше текущего момента времени, а затем устанавливаем заново. Также функцией lastAct() обновлем время последней активности. Возвращаем true.

Если же сессия есть, а cookie по какой-то причине не оказалось, то по id пользователя получаем из БД логин и хэш пароля и пишем их в cookie. Возвращаем true.

Если нет сессии, проверим, быть может существуют cookie. Классический пример авторизации после перезапуска браузера — сессия слетела, но cookie-то живы. Тут уже сложнее, мы должны проверить, совпадает ли пара логин-пароль с какой-либо строкой из БД. Ведь юзер мог заменить в настройках для сайта cookie ручками или написать любую чушь. Если такая пара нашлась, создаём переменную сессии и возвращаем true. Если же пара не найдена, посылаем пользователя на йух и возвращаем false.

Последний, самый печальный вариант — когда ни сессии, ни cookie не оказалось… Возвращаем false.

Теперь обратим взор на функцию is_admin($UID). Она определяет, является ли юзер администратором сайта. Возможно, вам это не нужно, тогда можете опустить эту функцию и все её вызовы в контроллере. Но она может быть полезна для вывода какого либо контента на страницу, предназначенного для администраторов, а не для обычных пользователей. Функция простая и основана на ещё одном созданном столбце в БД в таблице users. Столбец называем prava. Тип int. Если юзер является обыкновенным пользователем, то присваиваем значению в этом столбце 0, если же этот юзер — админ, то присваиваем единицу. Следующая функция и определяет, что стоит в столбце prava; если единица, то возвращается true (пользователь – админ), иначе false.

1
2
3
4
5
6
7
8
9
10
function is_admin($id) { 	
@$rez = mysql_query("SELECT prava FROM users WHERE id='$id'"); 	

if (mysql_num_rows($rez) == 1) 	
{ 		
$prava = mysql_result($rez, 0); 		

if ($prava == 1) return true; 		
else return false; 

} 	
else return false;	 
}

Ну и последняя, на самом деле очень лёгкая, функция — out(). Принцип её работы прост — удалить все «следы» пользователя – сессию и cookie.

1
2
3
4
5
6
7
8
function out () { 	
session_start(); 	
$id = $_SESSION['id'];			 	

mysql_query("UPDATE users SET online=0 WHERE id='$id'"); //обнуляем поле online, говорящее, что пользователь вышел с сайта (пригодится в будущем) 	
unset($_SESSION['id']); //удаляем переменную сессии 	
SetCookie("login", ""); //удаляем cookie с логином 	

SetCookie("password", ""); //удаляем cookie с паролем  	
header('Location: http://'.$_SERVER['HTTP_HOST'].'/'); //перенаправляем на главную страницу сайта }

Код всех описанных функций помещаем в файл lib/module_global.php, который подключается в самом начале работы контроллера.

Таким образом, мы написали простую, однако достаточно функциональную регистрацию для своего будущего сайта. В придачу ко всему мы заложили некоторый фундамент для ещё не рассмотренных возможностей: администрирование, онлайн-пользователи, время последней активности. Также авторизация не будет слетать после перезапуска браузера, что достигнуто путём использования cookie. Кроме этого мы предусмотрели и выход с сайта.

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

Чтобы не пропустить следующие статьи, подпишитесь на RSS.

Удачи и до следующих статей.

  • Отличный пост, отличный код!!! Один у меня только маленький вопрос будет: а что если перехватить куки? Насколько я понял по беглому прочтению, для авторизации достаточно. чтобы ID куки совпал с ID какого-нибудь пользователя. То есть для взлома достаточно подменить ID в кукисах — и все дела, можно войти от любого имени?

  • При таком виде авторизации у нас будет создано 3 кука. Первый — с ником юзера, второй — md5 пароля, а третий — PHPSESSID — идентификатор сессии, который удалится после закрытия браузера. Да, если украсть PHPSESSID, то можно будет авторизоваться под именем другого юзера. Такой способ часто использовался злоумышленниками. Например, если на сайт в комментарии можно вставить ссылку, которая после добавления будет активной, а не простым текстом, то злоумышленник мог вставить код ссылки, написанные с помощью псевдопротокола «javascript:» js-код… После клика на такую ссылку выполнялся бы этот код. Например, PHPSESSID запостился бы в комментарии. Злоумышленнику осталось бы лишь создать себе на этом сайте такой кук и спокойно пользоваться правами нерадивого юзера под его именем. Однако, создать этот кук необходимо было бы незадолго после того, как кук запостился бы в комментариях, ибо в скором времени он возможно уже не являлся бы идентификатором реально существующей сессии (реальные пользователь покинул бы сайт, закрыл брайзер).

    У себя на проекте мы позаботились о таком исходе событий и при вставке злоумышленником ссылки по псевдопротоколу javascript, после того, как он запостит свой комментарий, вместо его вредоносной ссылки будет простая и лаконичная фраза, типа «Я мудак»!) В общем, у него не получится таким образом украсть идентификатор сессии.

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

    Решить проблему с кражей кука с PHPSESSID можно, и меня на эту идею навёл Ваш комментарий). Можно в функции login(), при создании переменной сесcии $_SESSION[‘id’] создавать и переменную $_SESSION[‘ip’], хранящую IP пользователя. А затем при каждом выполнении функции login() проверять, равен ли ip в сессии ip пользователя. Если злоумышленник украдёт идентификатор сессии, скрипт удостоверится, что ip адрес вора не совпадает с тем, что хранится в сессии и, например, удалит все куки пользователя, а также обязательно уничтожит сессию:
    session_unset();
    session_destroy();

  • IMO, слишком много геморроя, в том числе и для пользователя. Что если у него динамический IP? В этом случае сессия для него, бедолаги, не сохранится никогда, хоть он утыкайся в «запомнить меня». Но это лично мое мнение. А вот код нужно будет посмотреть повнимательнее (мне). Чувствую, много полезного для себя найду. Спасибо за пост.

    • KittCat

      LOL))))))»Что если у него динамический IP? В этом случае сессия для него, бедолаги, не сохранится никогда, хоть он утыкайся в «запомнить меня»»))))

  • ZeroXor, рассчёт был в том, что скорее всего у него динамический IP. Смысл и идея в том, что пока жива сессия, то и IP не изменится.
    Когда же адрес юзера всё же поменяется, сессия слетит, в ход пойдут куки с логином и паролем. Там уже без проверки IP. По этим кукам будет проверено, существует ли такой юзер в БД и если это так, тогда всё заново — создаём сессиию и пишем в неё IP. Теперь при вызове функции login() юзаться будет только сессия, до очередного закрытия браузера пользователем. Как-то так)
    Рад стараться)

  • Тема довольно сложная для новичка, но справимся-)

  • Мне все понравилось

  • Бесплатный совет: заведи у себя в блоге рубрику типа «самые горячие обсуждения» или что-то в этом роде. Там можно будет комментировать самые обсуждаемые темы блога…

  • Юрий

    Добрый день!
    Хочу выразить свои мысли по поводу авторизации. Думаю, что это пригодится тем, кто ищет наиболее неуязвимый и правильный способ.
    1. Кратко, ваша схема такова:
    — храните в БД хеш пароля (32 симв.), вычисляемый функцией
    $hash_pass = md5(md5($password).$row[‘salt’])
    — соль (3 симв.)
    — в куке хранится md5($login.$hash_pass)
    А я говорю, что ваша схема уязвима к взлому. Почему уязвима:
    Во-первых, соль — 3 символа. Вариантов соли всего 1000.
    Во-вторых, пароли придумывают некоторые пользователи несложные, учтём это.
    В-третьих, взломщик может угадать вашу схему «md5(md5($password).$row[‘salt’])» или любую другую.
    С учётом этого, можно завести базу различных паролей (возможных, или найти их в интернете) и следующий алгоритм:
    $maybe_passwords — массив различных паролей, которые люди обычно придумывают, очень часто они совпадают у многих людей.
    foreach ($maybe_passwords as $pass){
    $for ($i=1; $i$login,
    ‘pass’=>$pass_cookie
    );
    }
    }

    Всё. Массив $variants можно использовать для подстановки в браузер и поиска. Думаю, что обязательно найдётся совпадение. В ручную конечно тяжело, но умелый взломщик автоматизирует процесс.

  • Юрий

    2. Вывод у меня такой: в куках нельзя хранить не только пароли, но и хеши.
    Наиболее простой способ, обеспечивающий максимальную безопасность следующий (моё мнение).
    Каждому пользователю при регистрации назначается уникальная случайная длинная сложно угадываемая строка.
    При авторизации эта строка записывается в куки.
    Взломать этот способ можно только если украсть саму куку. Взлом путём подбора практически невозможен.
    Возможно также на каждую сессию пользователя генерировать новую строку, что несколько усложнит воровство куки.
    Кроме того, строку можно хранить в отдельной таблице, вместе с user_id пользователя.

    3. Воровство кук можно предотвратить, если шифровать соединение (https), хотя бы самые важные страницы сайта.
    Для этого заход на такие страницы осуществлять только по https. Для идентификации пользователя на таких страницах использовать ещё одну строку, возможно с ещё большей длиной. Это обязательный момент. Украсть эту куку путём подслушивания трафика невозможно (очень и очень сложно, как — другой разговор).

    Таким образом наиболее безопасный способ авторизации, по моему мнению, состоит в назначении пользователю на время сессии 2-х сложных ключей (например по 32 символа), хранимых в куках. Первая кука обычного вида, вторая — isSecure+httpOnly. Если нет возможности использовать https, то достаточно одного ключа, он наиболее прост и максимально неуязвим, в отличие от способа, описанного в статье.

  • Юрий, спасибо за развёрнутый комментария.
    Да, схема хранения паролей достаточно проста, но это пример. В последнем проекте я реализовывал несколько более сложное хэширование. Для хэша использовал больше параметров и соль вставлял внутрь, например, оригинального (нехэшированного) пароля. Это не главное. Если так страшишься кражи, то алгоритм хэширование можно до маразма запутанно усложнить. Суть ведь не в этом.
    Да, в интернете есть таблицы хэшей многих паролей. Называются они радужными таблицами, кстати. Однако это хэши чистых строк. А как же потом (или сначала) приконкатенованые вначале (а может в конце или где угодно внутри) хэши? А какие хэши? А сколько раз хэшировано? А примешаны ли к этим строкам между хэшированиями другие параметры? Какие параметры (логин, дата, мыло). Таких таблиц нету. Есть шанс коллизии, конечно, но это почти невероятно. Каким бы простым пароль ни был, все предыдущие пароли сделают из него такой хэш, который если и будет в таблице, то скорее всего будет являться коллизией другого пароля. Ладно, если это недостаточно убедительно, то можно придумать правила для пароля (цифры и буквы, а также регистр), чтобы пользователь не выбрал для себя пароль «12345».

    Но это всё не столько важно. Почему Вы не учли сам фактор подбора пароля при входе на сайт? Не зная логин подбор скорее всего затянется на десятки лет, т. к. придётся подбирать пару. Ладно, предположим, что вход осуществляется по e-mail, а он находится в открытом доступе в профилях юзеров. То есть, мы знаем логин, пишем скрипт, которые будет брутфорсом подбирать пароль. Не забываем, что подбираем пароль не к программе на ПК, а к профилю на сайте. Есть такие факторы, как пинг и естественные тормоза сети. Скорость подбора будет лишь пару паролей в секунду, что безумно мало. Не забываем — мы предположили, что мыльник мы знаем и подбираем по базе простых(!) хэшей. Если такая скорость подбора Вас страшит, то легко можно реализовать механизм, не позволяющий частые попытки входа на сайт. Писал такой скрипт. После третьей попытки входя за какой-то небольшой промежуток времени возможность входа блокируется на время. Можно конечно ещё и запоминать IP злоумышленника, но это несерьёзно, ибо большинство IP сейчас динамические. Что мы выигрываем? Теперь хацкер пишет скрипт, который кроме всего прочего и переподключается переодически к нэту, а так как большинство провайдеров этого не любят, то происходить это будет с некоторой задержкой. Теперь скорость подбора падает, и падает существенно. Этот механизм легко реализовать с помощью сессий.

    Слишком большое количество «но». На Вашем месте я лучше обратил бы внимание на строку:
    mysql_query(«SELECT * FROM users WHERE login='{$_COOKIE[‘login’]}'»)

    Тут есть действительно большая уязвимость. А точнее — возможность MySQL инъекции. Юзер может записать в кук вредоносный запрос. Это действительно уязвимость. Извиняюсь, просто код писался ооочень давно и в реальных проектах давно переписан.

    Данная статья объясняет принцип авторизации. Усложнить хэширование — дело минут и зависит лишь о степени паранойи у программиста и его фантазии.

  • Юрий

    Прошу прощения, снова не вышло (если можно удалите предыдущее сообщение). Попробую ещё раз, полный код из первого сообщения:


    // $maybe_passwords - массив различных паролей, которые люди обычно придумывают, очень часто они совпадают у многих людей.
    foreach ($maybe_passwords as $pass){
    for ($i=1; $i$login,
    'pass'=>$pass_cookie
    );
    }
    }

  • Юрий, а почему Вам запутанный md5 пароля с мылом, солью и т. д. не нравится? По сложности он никак не уступит случайной строке.
    Хотя, впрочем, идея хранить постоянно разную строку в куках интересна.

  • Юрий

    К сожалению алгоритм так и не получается опубликовать. Проверял — код даже верный. Я там хорошо объясняю, как взломать способ с солью.
    Я говорю о том, что не стоит придумывать сложные алгоритмы создания хешей. Достаточно создать уникальную сложно угадываемую строку, достаточно длиную, например так
    $str = md5(uniqid(mt_rand(), true));
    Это обеспечит невозможность ни подбора, ни угадывания. Кроме того, это упростит программу.
    Замечу, что для авторизации можно использовать php-сессии. Но они обладают тем недостатком, что сборщик мусора их может удалить. В этом случае сессии нужно хранить в таблице БД (key, user_id). И этого вполне достаточно для невозможности подбора.

    Насчёт SQL-инъекций — да, Вы правы. Но в данном случае я говорю о схеме идентификации пользователя. Что наиболее неуязвимый способ — хранить в куке случайную, трудно угадываемую строку. Все манипуляции с солью — всего лишь от непонимания сути. Вы только поймите это, и вам станет ясно :).

  • Если подбор осуществлять на ПК, то это, естественно, будет быстрее в десятки раз, однако благодаря многократному хэшированию с примесью различных параметров, включая случайные, сводит на «нет» шансы. Радужные таблицы тут не помогут, ибо материал для хэширования — также хэш, а те таблицы строятся для чистых паролей.

    Если же реализовать подбор, генерирую последовательно хэши ВСЕХ возможных комбинаций символов и сравнивая получившийся хэш с хэшем из кука, а также учитывая, что это пароль в куках хэширован лишь один раз и без примечей, то…
    16^32 — количество вариантов (минус небольшое количество коллизий). Считать не стану, да и едва ли получится)

  • Юрий, а что мне мешает, если авторизация производится с участием кука со случайной строкой, подбирать пароль для входа? 🙂

  • Юрий

    Потому что вы к сожалению не увидели алгоритм, который я приводил (он так и неопубликовался).
    Схема, приведённая в статье, небезопасна.
    Соль нужно делать длиной, трудно угадываемой и случайной. А это и приводит нас к идентификации пользователя по этой соли, то есть соль становится идентификатором пользователя.
    Этот SID можно делать постоянным или каждый раз разным в новой сессии — не столь важно. Главное то, что он наиболее математически прост и в то же время безопасен (остаётся только кража). Это важно понять.

  • Юрий

    Насчёт подбора паролей. Это другой вопрос. Я его просто не затронул до сих пор. Я говорил о математически простом и безопасном способе идентификации пользователя.
    Насчёт подбора пароля. Я реализовал это так. После 3-х неудачных попыток выводится капча. А также можно применить и временную задержку.

  • Спасибо большое. То что нужно. Подписываюсь на обновления:)

  • Александр

    Спасибо!
    А можно на страничке поместить «исходники» , как и в прошлом посте.

  • Александр, куда вы пропали? Это я 625-978-213 ( Эрик )
    Проект nilbine.com нужно доделывать!
    Немалые деньги уже прогорают.

  • Greg

    Здравствуйте, а могли бы выложить исходники?

  • Отличная статья. Можно ли еще добавить востановление пароля?

  • Роман

    Дочитал до SELECT * FROM users WHERE login=$login и дальше читать не стал.
    Это кто же вас учил звездочкой делать выборку из базы? Вверх идиотизма.

    • Alpex

      +100500
      о каком тру может идти речь если выборка из базы делается звездочкой

    • logorianen

      SELECT * FROM users WHERE login=’$login’

  • Многоуважаемый Роман, меня никто не учил. Звёздочку я использовал в данном случае по вполне понятным причинам, — по причинам, для которых она существует, — для выборки ВСЕХ полей. Согласен, что в РЕАЛЬНЫХ проектах такого делать не стоит, а, разумеется, нужно прописывать через запятую все выбираемые поля, но неужели вы не заметили, что это — ОБУЧАЮЩИЙ скрипт про авторизацию, не имеющий ничего общего с реальными проектами и имеющий своей целью лишь демонстрацию принципов авторизации с простыми примерами кода.

    Впрочем, на вашем месте я не стал бы бахвалиться и называть что-либо идиотизмом, ибо реальной ошибки в коде вы не заметили, а она находится именно в той строчке, которые вы цитировали в своём посте 🙂 По-моему, идиотизм — обратить внимание на звёздочку, которая, впрочем, в тестовом проекте имеет право на существование и ошибкой не является по определению, но не обратить свой взор на реальную уязвимость…

    Почему ошибка до сих пор существует? Обращаем внимание на дату статьи. Ей чуть больше года. Возможно, вскоре будет написана новая версия статьи про авторизацию.

  • AlexanderC

    По моему стиль попова на лицо…
    Форматирование- ужасное + ошибки…
    Во первых существует ли вообще переменная проверяется через isset(),
    и перед проверкой на валидность неплохо бы через trim() провести, и проверять empty() а не ==
    Это кроме грубых нарушений безопасности

  • AlexanderC, во-первых, к Попову автор статьи никакого отношения не имеет.
    Во-вторых, рекомендую вам прочитать последний комментарий автора. Это лишь обучающая заметка, призванная натолкнуть людей на определенные мысли, на алгоритмы написания той или иной вещи, но никак не позиционируется в качестве готового скрипта авторизации.

  • Александр

    Хорошая статья.
    блин был бы пример — где можно было б все скопом посмотреть —
    сам пытался писать авторизацию и регистрацию используя данную статью и другие из инета — не очень понимаю процесс переаттестации страниц — чтоб было видно например сообщение — вы авторизованы — погодите пять сек чичас вас перекинет на главную — где автоматом вы авторизуйтесь либо просят ввести ваш логин или пароль. В будущих статьях если будет возможность опишите данный момент.
    так же не очень понятно с флагом regged — переменная ж до этого где-то должна фигурировать ? у меня она теряется и возвращает пустую строку — но это я думаю нормально я не давно стал разбирать php — до этого использовал только чистый html и java S .
    Еще раз спасибо за статью — давайте ещё.

  • Анатолий не годует

    Тупее авторизации еще я не видел.
    Зачем постоянно лазить в базу? Ума не хватает умнее придумать? Так зачем народ этому говну учить.

    • Example

      +1

      • prog

        +2

  • Example

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

  • prog

    Да и вообще, нафига писать «для ознакомления»? Если писать, так нормальные примеры, где все нормально работает. В нете и так говно кода хватает, и тут еще один добавлен!!!

  • Прохожий

    Меня зовут Андрей, мне 20 лет.

    Ребятки, вы чего на пацана напали?, у него еще все впереди…

  • Den

    Как бэ статья 2011 года, но такое чувство что автор живет году эдак в 2000 и наверно еще на php4 версии кодит. Много уязвимостей, код ужасен. Зачем новичков учить такому? Непонятно.

  • Nikitich

    lastAct($id);
    return true;
    }
    else //если данные из cookie не подошли, то удаляем эти куки, ибо нахуй они такие нам не нужны
    {

    понравилось)))

  • kiber

    Хороший материал, часа три въезжал и встраивал в сайт.
    В итоге всё работает.
    Автору спасибо.

  • FIle_not_dead

    Классная статья, помогла)

  • Олег

    Честно говоря, из всей статьи и дискуссии так и не понял, как защититься от логина по краденой куки.

  • ислям

    здравствуйте, извините может за глупый вопрос, я новичок в php, меня интерисует вопрос связанный с функцией function is_admin , как он понимает что ты админ или юзер ??
    помогите разобраться

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

      Функция is_admin обращается к БД к таблице users, где смотрит на флаг в определённом столбце, определяющем, к какой группе пользователей относится данный конкретный юзер. Скажем, для обычного пользователя значение в этом столбце равно нулю, у модератора — единице, у админа — двойке. Тут вы вольны решать сами. Функция же is_admin возвращает true или false, в зависимости от того, админ ли данный юзер.

  • ислям

    и еще, зачем нужна
    ini_set («session.use_trans_sid», true);
    я не могу понять для чего она??? ответь )

    • Буду отвечать, полагая, что вы знакомы с понятием SID (идентификатор сессии). Обычно этот идентификатор хранится в куках. Но, во-первых, они могут быть отключены у пользователя, а во-вторых, бывает, при плохом соединении и различных ошибках, этот кук не приходит ему. Но не имея SID, мы потеряем авторизацию. Так вот, данный параметр позволяет в двух вышеописанных случаях прикреплять в конце каждой ссылки на странице дополнительный GET-параметр — этот самый SID. Тут вы сами вольны решать,нужно ли это.

  • ислям

    функция out не работает

    • Впринципе, она должна работать, ибо в ней нет каких-либо сложных действий. Что именно у вас происходит при вызове этой функции?

  • Андрей, пожалуйста перепишите авторизацию, выложите исходники. А также было бы хорошо если бы Вы сделали полный комплекс (Войти, зарегистрироваться, активация ссылкой с E-mail, форма «забыл пароль»). Также было бы интересно посмотреть реализацию личного кабинета с редактированием полей личной информации, исполнение реферальных ссылок путем реализации на html PHP
    Для начинающих бедкодеров таких как я будет очень полезно, т.к хорошей инфы в ПС нет:)

  • Василий

    У меня система ругается на строку else return false; в функции login() в чем причина?

  • дю Барнстокр

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

  • Анастасия

    Очень понятная статья. Но есть вопрос. Имеется готовый сайт, на нем сделана авторизация админа, после которой открывается html код страницы для редактирования. Вопрос, как добавить туда еще проверку для обычных юзеров. Т.е чтобы при входе обычного пользователя было перенаправление в личный кабинет.
    Не могу понять куда добавить код чтобы ничего не испортить в работе ))

    1
    
     
  • roman

    у меня выходит ошибка Parse error: syntax error, unexpected $end in Z:\home\RCP1.KZ\www\lib\function_global.php on line 17

  • Roman

    у меня ругается на функцию lastAct
    Fatal error: Call to undefined function lastAct() in Z:\home\RCP1.KZ\www\lib\function_global.php on line 80

  • Блин, друг, сделай что-нибудь со своим оформлением кода, читать просто нереально — тупо каша.

  • Mr. Brightside

    Согласен с ораторами, выразившими определенную критику в адрес примера:

    — Слишком много ненужных запросов в БД
    — Мало проверок и обработки получаемых строк
    — Нету try/catch — давайте красиво обрабатывать исключения!
    — Вообще не хватает кроссплатформенности. Регекспы, конечно, исключают левые символы, но ту же проверку включенности магических кавычем делать надо
    — Пароль и логин в куках вообще никто не хранит. Храните там произвольный хеш и айдишник в течение суток, например.
    — В самом приведённом коде присутствуют ошибки, что вызывает необходимость заниматься его правкой

    Единственное, что мне понравилось — это действительно понятный алгоритм работы + хоть какая то сортировка скриптов по папкам/подпапкам.

    Автора можно похвалить за попытку и за старание.

  • SARGE

    Вот многие мастера критиковать, ругать и т.д. А вы дайте в таком случае ссылки на лучшие по вашему мнению материалы! Я вот щас сижу ищу статьи. Я совсем новичок. Мне нужна авторизация с нуля, чтобы каждая строка была описана. Где мне такую статью найти без «дыр» и прочего «мусора»? Подскажите ресурсы, будьте так добры!

  • Arris

    $rez = mysql_query("SELECT * FROM users WHERE login=$login"); //запрашиваем строку из БД с логином, введённым пользователем

    Лол. Где экранирование запросов? Где LIKE ?