Безопасный метод авторизации на PHP
Давайте посмотрим вокруг: форумы, интернет магазины, гостевые книги и т.д. используют регистрацию и последующую авторизацию пользователей. Можно даже сказать, что это почти необходимая функция каждого сайта (только если это не домашняя страничка Васи Пупкина или не визитная карточка, какой-нибудь небольшой компании). Сегодня я хочу поделиться со всеми новичками информацией, о том, как лучше это все реализовать.
1. Модель (клиент)
Регистрация
— логин (a-z0-9)
— пароль
Вход
— логин
— пароль
Cookie
— уникальный идентификатор юзера
— хэш
Модель (сервер)
MySQL
Таблица users
user_id (int(11))
user_login (Varchar(30))
user_password (varchar(32))
user_hash (varchar(32))
user_ip (int(10)) по умолчанию 0
При регистрации в базу данных записываеться логин пользователя и пароль(в двойном md5 шифровании)
При авторизация, сравниваеться логин и пароль, если они верны, то генерируеться случайная строка, которая хешируеться и добавляеться в БД в строку user_hash. Также записываеться IP адрес пользователя(но это у нас будет опциональным, так как кто-то сидит через Proxy, а у кого-то IP динамический… тут уже пользователь сам будет выбирать безопасность или удобство). В куки пользователя мы записываем его уникальный индетификатор и сгенерированный hash.
Почему надо хранить в куках хеш случайно сгенерированной строки, а не хеш пароля?
1. Из-за невнимательности программиста, во всей системе могут быть дырки, воспользовавшийсь этими дырками, злоумышленик может вытащить хеш пароля из БД и подставить его в свои куки, тем самым получить доступ к закрытым данным. В нашем же случае, двойной хеш пароля не чем не сможет помочь хакеру, так как расшифровать он его не сможет(теоретически это возможно, но на это он потратит не один месяц, а может быть и год) а воспользоваться этим хешем ему негде, ведь у нас при авторизации свой уникальный хеш прикрепленный к IP пользователя.
2. Если злоумышленик вытащит трояном у пользователя уникальный хеш, воспользовать им он также не сможет(разве если только, пользователь решил принебречь своей безопастностью и выключил привязку к IP при авторизации).
2. Практика
—
— Структура таблицы `users`
—
CREATE TABLE `users` (
`user_id` int(11) unsigned NOT NULL auto_increment,
`user_login` varchar(30) NOT NULL,
`user_password` varchar(32) NOT NULL,
`user_hash` varchar(32) NOT NULL,
`user_ip` int(10) unsigned NOT NULL default ‘0’,
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251 AUTO_INCREMENT=1 ;
register.php
// Страница регситрации нового пользователя
mysql_connect ( «localhost» , «myhost» , «myhost» );
if(! preg_match ( «/^[a-zA-Z0-9]+$/» , $_POST [ ‘login’ ]))
Система авторизации с помощью cookie на PHP
В прошлом уроке мы изучили механизм взаимодействия с cookie в языке PHP.
Теперь давайте попробуем применить эти знания на практике — создадим простейшую систему авторизации с использованием этих самых cookie. Ведь для авторизации нам просто необходимо при каждом запросе идентифицировать пользователя — сохранив специальное значение в куки этого легко добиться!
Техническое задание
Начнём мы это дело с описания будущей системы. Пусть у нас будут следующие компоненты:
- Главная страница сайта с каким-либо содержимым. Вверху страницы выводится:
- если пользователь авторизован: Добро пожаловать, %username%.
- если пользователь неавторизован: Авторизация — слово является ссылкой, которая ведёт на форму авторизации.
Авторизован пользователь или нет, определяется с помощью cookie.
- Страница с формой авторизации. Два инпута для логина и пароля и кнопкой «Вход». Если введены правильные логин и пароль, устанавливаются cookie со значениями переданных данных, а затем пользователя автоматически редиректит (перенаправляет) на главную страницу.
- Страница для разлогинивания — при переходе на неё cookie будут удаляться из браузера пользователя, а затем выполняется редирект на главную страницу.
Решение
Продумываем архитектуру
Первое, о чём нам нужно подумать — это то, как будут храниться элементы этой системы, и сколько их вообще будет.
Начнем с простого — для начала у нас должно получиться 3 странички, которые мы описали в ТЗ.
Ещё нам потребуется функционал, который будет проверять, авторизован ли пользователь. Если мы перечитаем ТЗ, то поймём, что он используется в двух местах — и на главной странице и на странице авторизации. Значит, стоит вынести этот механизм в отдельный файл, и использовать его в двух местах сразу.
Ну и наконец, нам где-то нужно хранить самих пользователей, а именно — их логины и пароли. Создадим для этого простенькую «базу данных» — массив, с набором пар логин-пароль. Это ещё один файл.
Пишем код
Все исходники по данному заданию доступны здесь.
База данных
Ну вот, всё продумали, осталось только написать. Предлагаю начать с нашей базы данных. Создадим файл usersDB.php и запишем в него несколько пользователей.
'admin', 'password' => '[email protected]'], ['login' => 'moderator', 'password' => 'password'], ['login' => 'user', 'password' => '123'], ];
Функции проверки авторизации
Давайте теперь напишем функцию, которая будет проверять, являются ли переданные в неё логин и пароль правильными. Для этого создадим ещё один файл auth.php. В нём нам для получения списка пользователей потребуется подключить файл с базой данных.
В цикле мы пробегаемся по базе данных пользователей и пытаемся найти пользователя с переданными логином и паролем. Если такой пользователь в массиве найден — возвращаем 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.
Авторизуйтесь Добро пожаловать,
Выйти
Наша главная страничка готова. Можно зайти на неё и убедиться, что мы не авторизованы.
Форма авторизации
Давайте теперь сделаем форму авторизации — создаём файл login.php и для начала набрасываем саму HTML-форму. Шаблон получился следующим.
Давайте теперь добавим логику проверки переданных данных.
else < $error = 'Ошибка авторизации'; >> ?>
Логика простейшая — если был отправлен 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 – вот тут.