PHP Урок 15. Авторизация пользователей
Предыдущие уроки:
Программируем на PHP - Введение
PHP - Запросы от браузера к серверу
PHP - Как работает сервер
PHP - Урок 4. PHP - интерпретатор
PHP - Урок 5. Переменные сервера и глобальные переменные
PHP - Урок 6. Конструкции print и echo. Кавычки одинарные и двойные и конкатенация строк
PHP - Урок 7. Переменные, константы и условия
PHP - Урок 8. Точка входа в приложение. Настройка mod_rewrite и файл .htaccess
PHP - Урок 9. Массивы и switch. Кодим основной каркас
PHP - Урок 10. COOKIE
PHP - Урок 11. Функции. Добавляем ядро системы core.php
PHP - Урок 12. Обзор модели MVC. Добавляем шаблоны страниц в наше приложение
PHP - Урок 13. Введение в базы данных и SQL. СУБД MySQL. Подключаемся к БД из нашего приложения
PHP - Урок 14. Регистрация пользователей на сайте
Теория
В прошлый раз создали форму и функцию для регистрации. Когда пользователь регистрируется на нашем сайте - запись о нем сохраняется в базе данных (в таблице ts_users). Сегодня мы напишем вторую часть необходимую для идентификации данных пользователей - авторизацию. Принцип авторизации прост:- Получить от пользователя идентификационные данные (логин и пароль).
- Проверить их корректность.
- Проверить существует ли запись с такими данными в базе.
- Если есть мы получаем ID пользователя, генерируем хэш и сохраняем их в куках браузера пользователя.
- В случае успешного завершения авторизации мы перенаправляем пользователя на страницу /user - профиль пользователя.
Таким образом пользователь попадает на свою страницу - профиль, куда загружаются данные этого пользователя.
Практика
Я думаю вы уже усвоили принцип, по которому мы строим свое php-приложение. На данный момент он такой:
Находим в switch-e нужный пункт (case) или добавляем новый - это web-страница нашего приложения.
Добавляем в sys/templates шаблон страницы, которую добавляем в приложение.
Добавляем sys/core.php функции, которые будут возвращать какие-то значения для переменных в шаблоне.
Вызываем функции в case и подключаем шаблон.
Добавляем шаблон sys/templates/login.tpl.php
Шаблон - это обычный HTML с использованием php-вставок (в основном переменных и циклов).
В моем сейчас есть классы стилей bootstrap - вскоре мы рассмотрим и подключим этот фреймворк.
<h1 class="cent">Пройдите авторизацию</h1>
<div class="row cent">
<div class="col-md-offset-4 col-md-4">
<form class="cent" action="" method="post">
<p><input class="form-control" name="mail" type="text" placeholder="e-mail" /></p>
<p><input class="form-control" name="password" type="password" placeholder="Пароль" /></p><br />
<p><input class="btn btn-primary" name="login_submit" type="submit" value="Представиться" /></p><br />
</form>
</div>
</div>
Добавляем функцию в sys/core.php
function login($pdo, $mail, $password){
$mail = $pdo->quote($mail);
$password = md5($password);
//print $mail.' '.$password;
$sql = "SELECT id, password FROM ts_users WHERE email=$mail";
if(!$stmt = $pdo->query($sql)){
return false;
} else {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if(!$row){
return false; // нет такого мыла в базе
} else {
$db_password = $row['password'];
$db_id = $row['id'];
if($password == $db_password){
$hash = md5(rand(0, 6400000));
$sql_update = "UPDATE ts_users SET hash='$hash' WHERE id='$db_id'";
if($pdo->exec($sql_update)){
setcookie("id", $db_id, time() + 3600);
setcookie("hash", $hash, time() + 3600);
return true;
}else{
print 'Exception';
}
}
return false;
}
}
}
Здесь мы в начале экранизируем email - это защита от SQL-инъекций.
Затем из переданного пароля извлекаем его md5 так как он хранится в базе именно в этом формате, следовательно и сравнивать его нужно именно с хэшем, а не оригинальным значением.
После мы делаем запрос, который по email находит запись и получает из нее поля id и password.
Далее если такие данные получены выполняется сравнение хэшей пароля из базы с полученным от пользователя.
Если пароли совпадают - авторизация пройдена. Но нам следует добавить еще несколько действий.
Во-первых, как вы помните из предыдущего урока мы в структуре нашей таблицы также задали поле hash. Так вот оно нужно для безопасности, чтобы не хранить хэш пароля в куках пользовательского браузера (откуда его легко хакнуть) мы будем хранить там вот этот hash - который генерируется каждый раз новый при каждой авторизации.
Для генерации такого hash-a мы используем очень простой подход:
$hash = md5(rand(0, 6400000));
На самом деле желательно написать для такой генерации отдельную более сложную функцию. Но мы на данный момент не будем себя утруждать. так как это сейчас не главное.
Затем мы добавляем полученный хэш в запись где хранятся идентификационные данные пользователя (в таблице ts_users) и сохраняем id пользователя и сделанный ему hash в куки браузера авторизировавшегося пользователя:
$sql_update = "UPDATE ts_users SET hash='$hash' WHERE id='$db_id'";
if($pdo->exec($sql_update)){
setcookie("id", $db_id, time() + 3600);
setcookie("hash", $hash, time() + 3600);
return true;
}
После чего функция возвращает true - что сообщает об успешном завершении авторизации при ее вызове из контроллера (switch - case) как мы это делали, или откуда либо еще.
Кстати заметили, что в первом параметре функции мы передаем объект соединения с нашей базой данных. Мы бы могли обойти это решением с использованием классов.
В ООП это будет называться инкапсуляцией. Когда мы определяем переменную внутри данных класса, а затем можем обращаться к ним из методов Этого класса.
На самом деле разница только в удобстве. По суте класс просто автоматически передавал бы эту переменную в функцию (метод). Это наглядно видно при программировании на языке Phyton.
Но в нашем учебном приложении мы благополучно обходимся без ООП.
Добавляем код в case login в index.php
case 'login':
$tpl = 'login';
if($_SERVER['REQUEST_METHOD'] == 'POST'){
$mail = clearStr($_POST['mail']);
$password = clearStr($_POST['password']);
if(strlen($mail) > 3 && strlen($password) > 2){
//print $mail.' '.$password;
if(login($pdo, $mail, $password)){
header("location: /user");
}else{
print 'Неверный логин или пароль';
}
}
}
break;
Теперь мы можем перейти на страницу http://thesite.loc/login
и авторизироваться под одним из зарегистрированных логинов. Я взял в качестве аккаунта логин и пароль, зарегистрированный в прошлом уроке ([email protected] test2).
После чего нас перебрасывает на страницу thesite.loc/user
- это работа функции header("location: /user");
которую мы вызываем в case login при успешной авторизации (если наша пользовательская функция login() возвращает true).
Соответственно тут мы еще не определили в case user ни шаблон ни функционал - поэтому у нас кроме предупреждения ни чего не получится увидеть. Но мы таким образом убедились, что наша авторизация работает (попробуйте ввести не правильный или не зарегистрированный логин и/или пароль и вы увидите что такой переход не произойдет).