Система авторизации с помощью cookie на PHP
В прошлом уроке мы изучили механизм взаимодействия с cookie в языке PHP.
Теперь давайте попробуем применить эти знания на практике — создадим простейшую систему авторизации с использованием этих самых cookie. Ведь для авторизации нам просто необходимо при каждом запросе идентифицировать пользователя - сохранив специальное значение в куки этого легко добиться!
Техническое задание
Начнём мы это дело с описания будущей системы. Пусть у нас будут следующие компоненты:
- Главная страница сайта с каким-либо содержимым. Вверху страницы выводится:
- если пользователь авторизован: Добро пожаловать, %username%.
- если пользователь неавторизован: Авторизация — слово является ссылкой, которая ведёт на форму авторизации.
Авторизован пользователь или нет, определяется с помощью cookie.
- Страница с формой авторизации. Два инпута для логина и пароля и кнопкой «Вход». Если введены правильные логин и пароль, устанавливаются cookie со значениями переданных данных, а затем пользователя автоматически редиректит (перенаправляет) на главную страницу.
- Страница для разлогинивания — при переходе на неё cookie будут удаляться из браузера пользователя, а затем выполняется редирект на главную страницу.
Решение
Продумываем архитектуру
Первое, о чём нам нужно подумать — это то, как будут храниться элементы этой системы, и сколько их вообще будет.
Начнем с простого — для начала у нас должно получиться 3 странички, которые мы описали в ТЗ.
Ещё нам потребуется функционал, который будет проверять, авторизован ли пользователь. Если мы перечитаем ТЗ, то поймём, что он используется в двух местах — и на главной странице и на странице авторизации. Значит, стоит вынести этот механизм в отдельный файл, и использовать его в двух местах сразу.
Ну и наконец, нам где-то нужно хранить самих пользователей, а именно — их логины и пароли. Создадим для этого простенькую «базу данных» - массив, с набором пар логин-пароль. Это ещё один файл.
Пишем код
Все исходники по данному заданию доступны здесь.
База данных
Ну вот, всё продумали, осталось только написать. Предлагаю начать с нашей базы данных. Создадим файл usersDB.php и запишем в него несколько пользователей.
<?php
return [
['login' => 'admin', 'password' => '[email protected]'],
['login' => 'moderator', 'password' => 'password'],
['login' => 'user', 'password' => '123'],
];
Функции проверки авторизации
Давайте теперь напишем функцию, которая будет проверять, являются ли переданные в неё логин и пароль правильными. Для этого создадим ещё один файл auth.php. В нём нам для получения списка пользователей потребуется подключить файл с базой данных.
<?php
function checkAuth(string $login, string $password): bool
{
$users = require __DIR__ . '/usersDB.php';
foreach ($users as $user) {
if ($user['login'] === $login
&& $user['password'] === $password
) {
return true;
}
}
return false;
}
В цикле мы пробегаемся по базе данных пользователей и пытаемся найти пользователя с переданными логином и паролем. Если такой пользователь в массиве найден — возвращаем true. Иначе — false.
Давайте теперь ещё напишем функцию, которая будет возвращать логин текущего пользователя. Эта функция будет проверять текущие значения cookie с ключами login и password с помощью уже существующей функции checkAuth. При этом если пользователь найдётся, то она вернёт его login, а иначе — null. Назовём эту нашу новую функцию getUserLogin.
//продолжение файла auth.php
function getUserLogin(): ?string
{
$loginFromCookie = $_COOKIE['login'] ?? '';
$passwordFromCookie = $_COOKIE['password'] ?? '';
if (checkAuth($loginFromCookie, $passwordFromCookie)) {
return $loginFromCookie;
}
return null;
}
На этом всю логику проверки логина мы описали. Теперь займёмся непосредственно страничками.
Главная страница
Создадим файл index.php. Для простоты примера мы будем использовать только строку с приветствием авторизованного пользователя, либо ссылкой на авторизацию. В этой странице нам потребуется функция проверки авторизации через cookie, поэтому здесь нужно подключить файл auth.php.
<?php
require __DIR__ . '/auth.php';
$login = getUserLogin();
?>
<html>
<head>
<title>Главная страница</title>
</head>
<body>
<?php if ($login === null): ?>
<a href="/login.php">Авторизуйтесь</a>
<?php else: ?>
Добро пожаловать, <?= $login ?>
<br>
<a href="/logout.php">Выйти</a>
<?php endif; ?>
</body>
</html>
Наша главная страничка готова. Можно зайти на неё и убедиться, что мы не авторизованы.
Форма авторизации
Давайте теперь сделаем форму авторизации — создаём файл login.php и для начала набрасываем саму HTML-форму. Шаблон получился следующим.
<html>
<head>
<title>Форма авторизации</title>
</head>
<body>
<form action="/login.php" method="post">
<label for="login">Имя пользователя: </label><input type="text" name="login" id="login">
<br>
<label for="password">Пароль: </label><input type="password" name="password" id="password">
<br>
<input type="submit" value="Войти">
</form>
</body>
</html>
Давайте теперь добавим логику проверки переданных данных.
<?php
if (!empty($_POST)) {
require __DIR__ . '/auth.php';
$login = $_POST['login'] ?? '';
$password = $_POST['password'] ?? '';
if (checkAuth($login, $password)) {
setcookie('login', $login, 0, '/');
setcookie('password', $password, 0, '/');
header('Location: /index.php');
} else {
$error = 'Ошибка авторизации';
}
}
?>
<html>
<head>
<title>Форма авторизации</title>
</head>
<body>
<?php if (isset($error)): ?>
<span style="color: red;">
<?= $error ?>
</span>
<?php endif; ?>
<form action="/login.php" method="post">
<label for="login">Имя пользователя: </label><input type="text" name="login" id="login">
<br>
<label for="password">Пароль: </label><input type="password" name="password" id="password">
<br>
<input type="submit" value="Войти">
</form>
</body>
</html>
Логика простейшая — если был отправлен POST-запрос, проверяем правильные ли логин и пароль были переданы.
Если нет — то создаём переменную $error, в которой пишем об ошибке авторизации. Позже в шаблоне выводим эту ошибку, если эта переменная объявлена.
Если же авторизация прошла успешно, мы устанавливаем cookie с ключами login и password, в которые помещаем значения из POST-запроса. После этого выполняем редирект на главную страницу.
Редирект делается с помощью заголовка в HTTP-ответе. Этот заголовок называется Location и выглядит следующим образом:
Location: адрес_на_который_нужно_перейти
Для формирования заголовков в PHP используется функция header – ознакомиться с ней более детально вы можете здесь.
Теперь можно попробовать нашу страничку в действии. Давайте для начала введём несуществующую пару логина и пароля. Например, 123:123.
Мы увидим соответствующую ошибку.
Теперь давайте зайдем под пользователем user. В нашей БД для него указан пароль 123. Пробуем...
Успех! Нас автоматически перекинуло на главную страницу, где мы видим приветствие для данного пользователя!
Безопасная система авторизации
Однако, данная схема имеет недостаток - пароль используется в открытом виде, а это небезопасно. Всё что идёт дальше - только для ознакомления, пока это слишком сложно реализовать без хорошей архитектуры и полноценной базы данных.
Хеширование паролей
В более совершенных системах авторизации используют хеш от пароля.
Если по-простому, то это такое вычисленное значение, полученное в результате выполнения над паролем определенных манипуляций. В результате этих действий мы получаем строку, из которой нельзя восстановить исходный пароль.
Но мы можем снова повторить над паролем те же действия и сравнить получившиеся значения. То есть сравниваются хеши, а не исходные пароли. И в базе данных тоже хранят хеши, а после того как от клиента пришел пароль в открытом виде, вычисляют его хэш и сравнивают со значением в базе. Если они равны - значит от пользователя пришел верный пароль.
Для чего это делается? Да просто потому, что если сайт будет каким-то образом взломан, то злоумышленник в базе данных не найдёт паролей в открытом виде - только хеши. А так как из хеша получить пароль довольно сложно (при условии, что хеш-функция надежна и используется надёжный пароль), то пароль он не узнает. Следовательно:
- злоумышленник не сможет использовать пароль для входа на взломанный сайт;
- он также не сможет использовать этот пароль для входа под тем же логином и паролем в другие места (ведь довольно часто люди используют одинаковые пароли для всего).
Вычисляются хеши с помощью хеш-функции. Хеш-функции при этом вычисляют хеши следуя алгоритмам хеширования. Сейчас в PHP для хеширования следует использовать функцию password_hash(), а для проверки хеша - password_verify(). Если вы в каком-то уроке увидите, что для хеширования паролей используется md5 - бегите оттуда, такие хеши вскрываются за несколько минут, она устарела ещё лет 10 назад.
Авторизационные токены
Помимо хеша пароля в базе данных так же принято хранить так называемые авторизационные токены (AuthToken). Это комбинация символов (желательно подлиннее и с кучей кракозябр), которая генерируется при успешной авторизации пользователя и сохраняется в базе данных. А ещё она и пользователю отправляется.
И потом пользователь с помощью cookie передает этот токен на сервер, где он сравнивается со значением в базе данных. Если они равны, то считаем пользователя авторизованным. Для чего? Дело в том, что куки могут быть похищены злоумышленниками (очень многими способами, не будем об этом в этой статье, кому интересно - погуглите). И если злоумышленнику попадет в руки токен - он не сможет получить исходный пароль. О том, почему это так важно, я уже объяснил.
Заключение
Повторюсь, два последних параграфа здесь только для ознакомления. Реализуем мы их в дальнейших уроках, когда будем знать чуть больше вещей. Пока что - вот такая простейшая, но небезопасная система авторизации.
Ах да, чуть не забыл, все исходники к каждому уроку я для вашего удобства буду выкладывать на github – вот тут.
Комментарии