Пишем авторизацию пользователя на 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.

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