Как сделать авторизацию на php
Перейти к содержимому

Как сделать авторизацию на php

  • автор:

Авторизация на PHP через сессии

Авторизация на PHP через сессии

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

Итак, есть задача — сделать регистрацию в системе и возможность авторизации. Как это делать? Начнем по порядку.

Регистрация на php

Тут все просто. Делается хранилище данных для пользователей. Обычно это таблица БД. В нее входят такие поля как id, username и password. Остальные поля опциональны. Вы можете собирать e-mail пользователей, их адреса, возможные ip, время выхода в сеть, кодовые слова от банковских карт, секретные вопросы.

В общем, главное — это пара логин-пароль.

Первый важный момент — нельзя хранить пароли пользователей в открытом виде. То есть, как текст. Нельзя потому что если к БД получит доступ кто-то посторонний — он получит базу логин-паролей пользователей, что вряд ли им понравится. А если учесть что логин-пароль у многих пользователей на разных сервисах одни и те же — это ставит под угрозу и личные данные и финансы и личную переписку и все что угодно.

Пароли надо хранить в виде хэша. Хэширование — это операция, которая превращает исходные данные в какой-то слепок известной длины. Это хорошо тем, что слепок уникален. То есть, если мы изменим в исходных данных хотя бы один символ — слепок на выходе операции хэширования получится совершенно, до неузнаваемости другим. Операция хэширования необратима, то есть, из этого слепка не получится развернуть исходные данные. Этим оно и отличается от шифрования.

В MySQL и php есть одна и та же распространенная и безопасная функция хэширования — md5. Этот алгоритм принимает данные и отдает отпечаток.

Итак, мы получаем данные от пользователя, проверяем не пустые ли они, не содержат ли всяких ненужных нам символов (не ограничивайте пользователей слишком сильно). И теперь делаем запрос вроде этого:

 INSERT INTO `users` (`id`,`username`,`password`) VALUES('',$quoted_username,MD5($quoted_password)); 

Обратите внимание — я специально обозвал переменные $quoted_ потому что перед запихиванием их в запрос их обязательно надо отэскейпить функцией mysql_real_escape_string(). Так как эта функция очень часто используется, а пишется очень длинно (люблю архитекторов пхп) — я рекомендую запихать ее в свою оболочку. Например, так:

 function quote($var)

Авторизация на php

Мы добавили нового пользователя и теперь он авторизуется. Мы рисуем ему форму логина-пароля и ловим его данные. Что дальше? Ведь пароль нам пришел в открытом виде, а в базе — хэш пароля. Придется конвертировать пароль в хэш, потом сравнивать их? Не, можно сделать проще — в один запрос.

 SELECT * FROM `users` WHERE `login`=$qoted_login AND `password`=MD5($quoted_password); 

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

Запомнить пользователя

Теперь нам надо запомнить что пользователь авторизован и точно знать кто это. Первое что приходит в голову — использовать для этого куки. Действительно — запихать в куки логин и id пользователя и всегда знать кто запрашивает страницу в данный момент.

Но это порочная практика. Почему? Потому что куки — это файл, который хранится у пользователя в браузере и данные из этого файла передаются серверу в каждом запросе. Во-первых, они передаются как текст, а это значит — их легко перехватить. Во-вторых, это простой текст, посылаемый пользователем. Поэтому его можно буквально переписать. Например, если мы решили хранить в куках логин пользователя «Вася» он может открыть управление куками в своем браузере, найти нужную куку и исправить ее на, скажем, «Admin». И все. Теперь при каждом запросе мы будем получать куку, которая нам будет сообщять юзернейм пользователя — «Admin».

Поэтому безопаснее хранить все данные на сервере в недоступном из веба месте. В какой-то папке, к которой нельзя получить доступ из браузера. В эту папку нужно писать все данные о пользователе и читать их оттуда каждый раз, когда он запрашивает документ. Чтобы узнать какому пользователю принадлежит какой файл данных — надо обзывать файл уникальным именем и вот это имя кидать уже пользователю в куки. Таким образом, пользователь не сможет узнать как называется файл для пользователя Admin — этот файл генерирует система на сервере. И это позволяет таскать от документа к документу хоть пароли в открытом виде.

То что я описал — это механизм сессии. В Perl, например, для использования сессий нужно загружать модули. А в php сессии поддерживаются из коробки. Фактически, все что вам нужно знать — это функция session_start() и массив $_SESSION. Это все. Сейчас расскажу.

В каждом скрипте, где вы будете писать в сессию или читать из нее, вам надо до того, как вы выведете какую-то информацию вызвать функцию session_start(). Это запустит сессию. Эта функция создаст файл сессии если его нет или прочитает его если скрипту была передана специальная кука.

Чтобы записать в сессию данные нужно просто записать их в массив $_SESSION. Сейчас нам надо помнить id юзера.

 $_SESSION['userid'] = $userinfo['id']; 

Все. Теперь каждый раз когда пользователь будет запрашивать скрипт, который использует сессии — вам будет доступно значение элемента $_SESSION[‘userid’].

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

Узнать авторизован ли пользователь

Ну это уж проще простого! Теперь, когда вы знаете как работают сессии — узнать авторизован ли пользователь — дело одной строки. Вот она:

 if(isset($_SESSION['userid']))

Если в сессии определен id пользователя, значит он уже прошел авторизацию. Но как узнать какой именно это пользователь? Какой его логин? Дата рождения? Кодовое слово для банковской карты? Девичья фамилия матери?

Ну очевидно — сделать один запрос к таблице users. У нас же есть id этого пользователя.

 SELECT * FROM `users` WHERE `id`=$quoted_userid 

Как разлогинить пользователя, сделать выход

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

unset($_SESSION['userid']);

Еще можно убить сессию для верности. Это очистит куку у пользователя и уничтожит файл сессии на сервере. При этом из нее пропадут все данные. Делается так:

 session_destroy(); 

На этом все. Если остались какие-то вопросы — не стесняйтесь обращаться. Более того, вы можете обратиться ко мне по icq или почте и попросить помочь с чем-нибудь. Я обычно не отказываю. Если помощь потребуется серьезная — я могу попросить небольшую оплату. Кроме того, я могу дистанционно учить вас делать сайты! Вот такой я коуч и гуру:) А если вы хотите получать уроки и трюки просто так, бесплатно — подпишитесь на RSS моего блога.

Спонсор этого поста — ibooknet.ru, который предлагает ремонт ноутбуков по приятным ценам. Лично мой ноут в порядке и надеюсь, мне не придется его чинить. Чего и вам желаю.

  • Домены .рф
  • Грабим погоду с Gismeteo.ru
  • Получение всех атрибутов тега с помощью регулярных выражений
  • Как предотвратить повторную отправку сообщения из формы?
  • Работа с кэшированием через API MODx
  • Сниппет для MODx импорт RSS
  • RSS импорт для MODx версия 2
  • Сниппет для MODx граббер погоды с Gismeteo.ru

Как сделать авторизацию на php

Я только начинаю. Подскажите, плиз, как можно сделать авторизацию на PHP? Т.е., чтоб не пользоваться стандартными средствами Апача, а написать собственный скрипт.
————————
p.s. У вас очень хороший форум, много разных вопросов задавал на других форумах и как правило одно и тоже — либо читать man, либо рожей в грязь. У вас такого нет — спасибо большое за это. Кроме того, спасибо за ДЕЙСТВИТЕЛЬНО работающие скрипты — тоже редко где встретишь.
p.s.2 Вопросов у меня было много, но на многие свои вопросы я уже нашел подробные ответы в других топиках форума.

автор: cheops (15.07.2004 в 17:58) письмо автору

Хм давайте начнём с простого примера, а вы скажите достаточно этого или нужно что-то более мощное.
Для начала сделаем HTML-форму и поместим её в файл index.php:

Имя посетителя :

Пароль :

HTML-форма имеет два поля: текстовое поле для имени посетителя, а так же поле типа password для ввода пароля. Кроме того, в форме присутствует кнопка, позволяющая отправить обработчику handler.php данные из формы.
В обработчике handler.php необходимо сверить полученные данные с именем посетителя и его паролем

// Здесь следует поместить код, например, извлекающий пароль
// и имя посетителя из базы данных, и помещающий их в переменные
// $password и $name, соответственно.
// .
// если хеш-коды совпадают, авторизация прошла успешно
if ( $_POST [ ‘password’ ] == $password && $_POST [ ‘name’ ] == $name ) echo «Hello, $name » ;
else echo «Введен неправильный пароль» ;
?>

Следующим логическим шагом будет сохранение предварительно зашифрованного пароля в cookie, чтобы посетитель один раз авторизовавшись мог потом некоторое время не вводить пароль. Если хотите, можем рассмотреть и этот вариант.

PS Спасибо на добром слове 🙂 мы как раз и хотели сделать форум, где люди могли бы получить ответ, а не наставления. Меня тоже раздражает стиль новолинуксоидов. ведь сетевое сообщество ещё несколько лет назад было очень дружелюбно к новичкам :(((

автор: грант (15.07.2004 в 18:23)

А как поместить это в базу данных? Если не сложно, подскажите, а то требуют 🙁

автор: cheops (15.07.2004 в 19:20) письмо автору

Это зависит от числа посетителей, для которых создаётся система авторизации. Если посетитель один или несколько штук, то можно вручную создать таблицу и поместить туда несколько записей. Для этого, например, в клиенте mysql необходимов выполнить ряд SQL-запросов. Для создания таблицы user в базе данных test необходимо выполнить SQL-оператор CREATE:

CREATE TABLE users (
id_user INT NOT NULL auto_increment,
name TINYTEXT,
password TINYTEXT,
PRIMARY KEY (id_user)
) TYPE=MyISAM;

Теперь таблицу можно заполнить

INSERT INTO users VALUES(NULL,’user1′,’pass1′);
INSERT INTO users VALUES(NULL,’user2′,’pass2′);
INSERT INTO users VALUES(NULL,’user3′,’pass3′);

В результате в таблице users будет 3 записи для посетителей user1, user2, user3 с паролями pass1, pass2, pass3, соответственно. Теперь обработчик формы можно переписать:

// Имя сервера базы данных, например $dblocation = «mysql28.noweb.ru»
// сейчас выставлен сервер локальной машины
$dblocation = «localhost» ;
// Имя базы данных
$dbname = «test» ;
// Имя пользователя. Скорее всего будет совпадать с вашим доменным именем
// у нас, например $dbuser = «softtime»;
$dbuser = «root» ;
// Пароль — в комментариях не нуждается 😉
$dbpasswd = «» ;
// Соединяемся с сервером базы данных
$dbcnx = @ mysql_connect ( $dblocation , $dbuser , $dbpasswd );
if (! $dbcnx ) <
echo( «

В настоящий момент сервер базы данных не
доступен, поэтому корректное отображение
страницы невозможно.

» );
exit();
>
// Выбираем базу данных
if (! @ mysql_select_db ( $dbname , $dbcnx ) ) <
echo( «

В настоящий момент база данных не доступна,
поэтому корректное отображение страницы
невозможно.

» );
exit();
>
// Формируем и выполняем SQL-запрос для посетителя с
// именем $_POST[‘name’]
$query = «SELECT password FROM users WHERE name='» . $_POST [ ‘name’ ]. «‘» ;
$nme = mysql_query ( $query );
if(! $nme )
<
echo «Ошибка выполнения запроса» ;
exit();
>
// Если запрос вернул результат — производим дальнейшую обработку
if( mysql_num_rows ( $nme ) > 0 )
<
// Получаем пароль
$password = mysql_result ( $nme , 0 );
// Сравниваем пароль из базы данных и введённый посетителем
if ( $_POST [ ‘password’ ] == $password ) echo «Hello, $name » ;
else
<
echo «Ошибка идентификации: неправильный пароль» ;
exit();
>
>
// Если в результате запроса не получено ни одной
// строки — посетитель с таким именем не зарегистрирован
else
<
echo «Ошибка идентификации: посетитель не зарегистрирован» ;
exit();
>
?>

Если посетителей много, то следует организовать систему регистрации, примерно, как у нас на форуме — тогда база данных будет заполнена самими посетителями.

автор: Shady (07.08.2004 в 13:21) письмо автору

А как к этому скрипту сессии прикрутить?

автор: cheops (07.08.2004 в 13:34) письмо автору

Можно. постараюсь сегодня ближе к вечеру выложить здесь такой скрипт.

автор: cheops (07.08.2004 в 16:36) письмо автору

Вот форма для ввода имени посетителя и пароля (index.php):

// Это файл index.php
session_start ();
?>

Имя посетителя : >

Пароль : >

// Если посетитель «вошёл» — приветствуем его
if(isset( $_SESSION [ ‘user’ ])) echo «Здравствуйте, » . $_SESSION [ ‘user’ ]. «!» ;
?>

Обработчик данной формы должен выглядеть следующим образом (handler.php):

// Это файл handler.php
// Имя сервера базы данных, например $dblocation = «mysql28.noweb.ru»
// сейчас выставлен сервер локальной машины
$dblocation = «localhost» ;
// Имя базы данных
$dbname = «test» ;
// Имя пользователя. Скорее всего будет совпадать с вашим доменным именем
// у нас, например $dbuser = «softtime»;
$dbuser = «root» ;
// Пароль — в комментариях не нуждается 😉
$dbpasswd = «» ;
// Соединяемся с сервером базы данных
$dbcnx = @ mysql_connect ( $dblocation , $dbuser , $dbpasswd );
if (! $dbcnx ) <
echo( «

В настоящий момент сервер базы данных не
доступен, поэтому корректное отображение
страницы невозможно.

» );
exit();
>
// Выбираем базу данных
if (! @ mysql_select_db ( $dbname , $dbcnx ) ) <
echo( «

В настоящий момент база данных не доступна,
поэтому корректное отображение страницы
невозможно.

» );
exit();
>
// Формируем и выполняем SQL-запрос для посетителя с
// именем $_POST[‘name’]
$query = «SELECT password FROM users WHERE name='» . $_POST [ ‘name’ ]. «‘» ;
$nme = mysql_query ( $query );
if(! $nme )
<
echo mysql_error ();
echo «Ошибка выполнения запроса» ;
exit();
>
// Если запрос вернул результат — производим дальнейшую обработку
if( mysql_num_rows ( $nme ) > 0 )
<
// Получаем пароль
$password = mysql_result ( $nme , 0 );
// Сравниваем пароль из базы данных и введённый посетителем
if ( $_POST [ ‘password’ ] == $password )
<
// Идентификация прошла успешно — осуществляем
// «вход» посетителя. Для того, чтобы в течении текущей
// сесси посетитель не вводил своё имя пароль повторно —
// передаём их через сессию
if( session_start ())
<
$_SESSION [ ‘user’ ] = $_POST [ ‘name’ ];
$_SESSION [ ‘password’ ] = $_POST [ ‘password’ ];
// Осуществляем автоматический переход на
// страницу index.php чтобы убедится, что
// посетитель «вошёл»
echo »

» ;
>
>
else
<
echo «Ошибка идентификации: неправильный пароль» ;
exit();
>
>
// Если в результате запроса не получено ни одной
// строки — посетитель с таким именем не зарегистрирован
else
<
echo «Ошибка идентификации: посетитель не зарегистрирован» ;
exit();
>
?>

автор: pavlik (24.09.2004 в 19:39)

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

и вот самое главное =)
есть база в sql, в ней 5 таблиц, таблица с предметами (предмет и id предмета) , таблица с информацией об учащемся (id учащегося, имя, фамилия и т.д.), таблица с id и pass, успеваемость текущая (код предмета, id ученика, оценка и дата оценки) и успеваемость итоговая ..

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

автор: LimP (24.09.2004 в 20:05) письмо автору

вот,я когда-то писал авторизацию,прикрепил архив в аттаче.

автор: pavlik (24.09.2004 в 20:22)

ну это не совсем то, что мне надо =) пользователей никаких нет. у меня успеваемость класса. кол-во учащихся определено и надеюсь не изменится до конца года =) мне просто надо сделать, чтобы выводилась успеваемость учащегося из базы с успеваемостью.. =)

автор: Igorek (24.09.2004 в 20:37) письмо автору

Если я вас правильно понял, то делается это примерно так:
Простенький запрос типа такого: «SELECT mark FROM tableName WHERE AND codPredmeta = $cod». Получаем оценку для определенного юзера и определенного предмета, если нужно для всех предметов, то «AND codPredmeta = $cod» не надо писать.

автор: pavlik (24.09.2004 в 23:38)

не подскажете еще как сделать, чтобы после успешной авторизации человека перебрасывало на страницу с успеваемостью ? =)

автор: glsv (Дизайнер) (24.09.2004 в 23:56) письмо автору
автор: cheops (24.09.2004 в 23:58) письмо автору

В участке кода

echo »

» ;
?>

Вместо index.php пропишите адрес страницы с успеваемостью.

автор: eladan (07.11.2004 в 17:32) письмо автору

pri popitke vipolnit’ etot script voznikaet nechto vrode etogo:

Warning: session_start(): Cannot send session cookie — headers already sent by (output started at /home2/p/ptk2001.h12.ru/WWW/login.php:21) in /home2/p/ptk2001.h12.ru/WWW/login.php on line 22

Warning: session_start(): Cannot send session cache limiter — headers already sent (output started at /home2/p/ptk2001.h12.ru/WWW/login.php:21) in /home2/p/ptk2001.h12.ru/WWW/login.php on line 22

s chem bi eto moglo bit’ sviazano?

автор: cheops (07.11.2004 в 18:18) письмо автору

Это значит, что до вызова session_start() имеется вывод в окно браузера, которое заставляет отослать заголовки до того, как интерпретатор доберётся до session_start(). Так как заголовки уже отосланы сессии не инициализируются. Функция session_start() должна быть расположена до любого вывода в окно браузера операторами echo, print и просто HTML-тэгов.

автор: Guest (05.04.2005 в 23:25)

Помогите please! Приведённый пример не работает! Уже устал бться с авторизацией, если не поможете разобью комп чесн. слово

автор: cheops (06.04.2005 в 10:47) письмо автору

Давайте новую тему заведём, а то эта уже очень длинная — здесь просто не удобно разбираться что к чему.

автор: Hemp (08.12.2004 в 10:06) письмо автору

Имеется Windows2000Professional SP4, установлен пакет Денвер-2 (Apache 1.3.27, PHP Version 4.3.6).
______________________________________________
Пытаюсь сделать авторизацию пользователей, используя механизм сессий, следующим образом (на базе найденного мной примера):
Есть, три файла:
1 — reg1.php (выводит форму, для ввода пароля и логина и методом POST передаёт, введённый в поле формы пароль и логин для обработки, скрипту, находящемуся в файле authorize.php )

Введите пароль

Введите логин и пароль.

Логин:

Пароль:


2 — authorize.php (сравнивает переданный формой пароль и логин с «правильным» паролем и логином и, либо выраёт ошибку, либо переключает на приватную страничку)

session_start ();
if( $Submit ) <
if(( $user_name == «1» )&&( $user_pass == «1» )) <
$logged_user = $user_name ;
exit;
>
>

register_globals — on
__________________________________________

Теперь проблема.
При вводе неверного пароля, скрипт выводит надпись, что, введён неверный пароль.
А, при вводе правильного пароля, переключает на файл secretplace.php, и выдаёт, следующее (см. двумя постами выше):
———————————————————————————
Warning: session_start(): Cannot send session cookie — headers already sent by (output started at z:\home\test2.ru\www\secretplace.php:2) in z:\home\test2.ru\www\secretplace.php on line 4 Warning: session_start():

Cannot send session cache limiter — headers already sent (output started at z:\home\test2.ru\www\secretplace.php:2) in z:\home\test2.ru\www\secretplace.php on line 4 òèïà íàäïèñü êàêàÿ-òî.
————————————————————————————
В, чём, может быть пролема?
В, коде?
В настройках Apache, либо PHP?
Помогите разобраться.

автор: coloboc66 (08.12.2004 в 10:36) письмо автору

Проблема в коде. Это стандартная ошибка у новичков типа меня. А вот найти ошибку очень сложно, потому что она не синтаксическая, по крайней мере у меня сложилось такое впечатление. Характер ошибки кроется где-то в структуре файла РНР. У меня получалось устранить эту ошибку только перписыванием файла по-новому.

автор: Hemp (08.12.2004 в 10:52) письмо автору

Извиняюсь, файл authorize.php, не верно, выложил. Он, должен выглядеть так:

session_start ();
if( $Submit ) <
if(( $user_name == «1» )&&( $user_pass == «1» )) <
$logged_user = $user_name ;
session_register ( «logged_user» );
header ( «Location: secretplace.php» );
exit;
>
>
?>

Вы ввели неверный пароль!

А, проблема осталась.

автор: coloboc66 (08.12.2004 в 12:06) письмо автору

Не знаю, в этом ли причина твоей ошибки, но у тебя в файле authorize.php сеессия запускается два раза: session_start(); и session_register(«logged_user»);
,а так делать нельзя. Попробуй убрать session_start();
И еще: если ты сессию запустил, то по окончании работы тебе необходимо разрегистрировать переменные session_unregister(«logged_user»); а затем закрыть сессию session_destroy();

автор: Hemp (08.12.2004 в 12:30) письмо автору

Не помогло, да, и не могло помочь, т.к.

session_start(); — проверяет, существует ли идентификатор сессии, и, если нет, то создает его. Если идентификатор текущей сессии уже существует, то загружаются зарегистрированные переменные сессии.

Сессия, у меня не была создана, до этого момента (authorize.php), поэтому, её нужно создавать, с помощью функции session_start();

Регистрация переменных сессии осуществляется при помощи функции session_register:

session_register(«logged_user»); — здесь, я регистрирую переменную сессии logged_user, которая имет в данном случае значение 1

Параметр register_globals включен.

автор: cheops (08.12.2004 в 12:48) письмо автору

Хм. судя по предупреждениям session_start() у вас в 4 строке, а во второй какой-то вывод в окно браузера — это тот же самый файл и нет ли перед тэгом
автор: Hemp (08.12.2004 в 13:23) письмо автору
автор: coloboc66 (08.12.2004 в 14:38) письмо автору
автор: cheops (08.12.2004 в 14:58) письмо автору

Если происходит работа с cookies, session или отправляются какие-либо другие заголовки, то нельзя, так как пробел воспринимается как начала HTML-документа, после отправки которого заголовки послать уже невозможно.

Создание форм регистрации и авторизации на PHP

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

Разработка форм регистрации и авторизации

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

Код регистрационной формы

Ниже приведен HTML-код необходимый для создания формы регистрации. Сохраните его вфайле register.php.

 

Несмотря на простоту данной формы, для проведения простейшей валидации данных в ней используется HTML5. К примеру, использование type=»email» обеспечит уведомление пользователя о том, что он неправильно ввел адрес электронной почты. Кроме того, применение pattern позволит провести проверку имени пользователя – логин может состоять только из латинских букв и цифр.

Наиболее продвинутый способ валидации данных подразумевает использование jQuery – в этом случае разработчик получает полный контроль над показом, расположением и внешним видом сообщений об ошибках ввода. Подробнее о валидации на стороне клиента с использованием jQuery рассказывается в этой статье.

Исходный код страницы авторизации

HTML-код страницы входа в систему приведен ниже. Сохраните его в файле login.php .

 

CSS-стили для оформления форм

Для улучшения внешнего вида форм примените к ним следующие CSS-стили:

* < padding: 0; margin: 0; box-sizing: border-box; >body < margin: 50px auto; text-align: center; width: 800px; >h1 < font-family: 'Passion One'; font-size: 2rem; text-transform: uppercase; >label < width: 150px; display: inline-block; text-align: left; font-size: 1.5rem; font-family: 'Lato'; >input < border: 2px solid #ccc; font-size: 1.5rem; font-weight: 100; font-family: 'Lato'; padding: 10px; >form < margin: 25px auto; padding: 20px; border: 5px solid #ccc; width: 500px; background: #eee; >div.form-element < margin: 20px 0; >button < padding: 10px; font-size: 1.5rem; font-family: 'Lato'; font-weight: 100; background: yellowgreen; color: white; border: none; >p.success, p.error < color: white; font-family: lato; background: yellowgreen; display: inline-block; padding: 2px 10px; >p.error

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

Создание таблицы с учетными данными и подключение к базе данных

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

  1. Порядковый номер ID, который для каждого нового пользователя увеличивается автоматически.
  2. Уникальное имя пользователя.
  3. Адрес электронной почты.
  4. Пароль.

Для быстрого создания таблицы базы данных можно использовать следующий SQL-запрос:

CREATE TABLE `users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(25) NOT NULL, `password` varchar(255) NOT NULL, `email` varchar(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

Теперь создайте файл config.php и сохраните в нем приведенный далее код для подключения к базе данных:

 catch (PDOException $e) < exit("Error: " . $e->getMessage()); > ?>

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

Исходный код для регистрации пользователей

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

Сохраните приведенный далее код в начале файла registration.php :

prepare("SELECT * FROM users WHERE email=:email"); $query->bindParam("email", $email, PDO::PARAM_STR); $query->execute(); if ($query->rowCount() > 0) < echo '

Этот адрес уже зарегистрирован!

'; > if ($query->rowCount() == 0) < $query = $connection->prepare("INSERT INTO users(username,password,email) VALUES (:username,:password_hash,:email)"); $query->bindParam("username", $username, PDO::PARAM_STR); $query->bindParam("password_hash", $password_hash, PDO::PARAM_STR); $query->bindParam("email", $email, PDO::PARAM_STR); $result = $query->execute(); if ($result) < echo '

Регистрация прошла успешно!

'; > else < echo '

Неверные данные!

'; > > > ?>

На первом этапе выполнения кода включается config.php, начинается сессия. Так мы получаем возможность сохранить любую информацию для дальнейшего использования на всех страницах сайта.

Далее, с помощью $_POST[‘register’] мы проверяем, нажал ли пользователь кнопку «Регистрация». Следует помнить, что пароли нельзя сохранять в виде незашифрованного текста. Поэтому наш код использует функцию password_hash() и сохраняет пароль в хэшированном виде. Эта функция записывает пароль в базу данных в виде хэш-строки, состоящей из 60 случайных символов.

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

Функция авторизации

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

Приведенный далее код должен располагаться в начале файла login.php:

prepare("SELECT * FROM users WHERE username=:username"); $query->bindParam("username", $username, PDO::PARAM_STR); $query->execute(); $result = $query->fetch(PDO::FETCH_ASSOC); if (!$result) < echo '

Неверные пароль или имя пользователя!

'; > else < if (password_verify($password, $result['password'])) < $_SESSION['user_id'] = $result['id']; echo '

Поздравляем, вы прошли авторизацию!

'; > else < echo '

Неверные пароль или имя пользователя!

'; > > > ?>

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

Как только мы получаем подтверждение правильности пароля, мы назначаем переменную $_SESSION[‘user_id’] для ID пользователя из базы данных. При необходимости на этом этапе передаются и значения для других переменных.

Ограничение доступа к страницам

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

 else < // Покажите пользователю страницу >?>

Все, что нужно сделать для ограничения или предоставления доступа – это использовать в начале приведенного выше скрипта строку session_start().

Типичные ошибки и способы их решения

При использовании скрипта для ограничения доступа неавторизованных пользователей обычно возникают три типа ошибок.

Некорректное имя переменной

Чаще всего ошибки в работе скрипта связаны с неверными именами переменных – как правило, с использованием букв в неправильном регистре. Именно поэтому крайне важно придерживаться одного и того же шаблона при выборе имен. К примеру, ключи в функции $_POST основаны на значениях, полученных из полей ввода в формах. Это означает, что $_POST[‘USERNAME’] и $_POST[‘username’] получат разные значения.

«Заголовки уже отправлены»

Некоторые функции, например session_start() и header(), изменяют HTTP-заголовки. Поскольку PHP сбрасывает все заголовки перед выводом любых данных, важно вызывать все подобные функции до того, как вы начнете что-либо выводить – включая фрагменты сырого HTML или случайные пробелы перед открывающим тегом

Переменные сессии не сохраняются при переходах между страницами

Вы можете использовать переменные сессии только в том случае, если на странице осуществлен вызов функции session_start() . Если значения суперглобальной переменной $_SESSION вам не доступны, причина этого заключается в том, что вы забыли вызвать session_start() . Помните о том, что функцию надо вызывать перед выводом чего-либо на страницу сайта. В противном случае вы получите ошибку «Заголовки уже отправлены», рассмотренную выше.

Авторизация на сессиях своими руками. Делаем логин в админке

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

Сначала выберем проект, в котором будем делать авторизацию. Сначала я хотел сделать отдельный, но потом подумал, а зачем? У нас есть админка интернет-магазина, которая прекрасно подойдет для экспериментов. Админка написана на vue.js, но если вы им не интересуетесь, не беспокойтесь. Мы сделаем так, чтобы авторизацию можно было прикрутить к любому проекту. Не копипастнуть, конечно, но весь код можно будет переиспользовать.

Давайте начнем. Осторожно, это прям лонгрид.

Мы будем писать код на php, как обычно, а авторизацию сделаем на сессиях. Если описать все одной строчкой кода, то вот

$_SESSION['authorized'] = 1;

Так мы авторизовали пользователя. Серьезно, это самый важный код. Но есть и другие вопросы:

— Как выводить для неавторизованных пользователей страницу логина, а для остальных — самого сервиса?
— Как сделать форму авторизации и написать клиентский код?
— Можно ли передавать логин и пароль по сети в открытом виде?
— Какое будет серверное api?
— Логин, разлогин, какой функционал еще понадобится?
— Какие http-коды ошибок использовать?
— Как хранить пароли, в коде или в базе?
— А что если взломают ftp или уведут базу?
— В каком виде хранить пароли?
— Нужно ли их как-то шифровать?
— А как потом расшифровывать?
— Или есть еще какие-то способы?
— Хэширование? Что это вообще такое?
— Так какой все-таки способ выбрать и как его реализовать?
— Сессии, что это и как работают?
— Та строчка с $_SESSION[‘authorized’] — неужели это все, что нужно?
— Как закрыть не только страницы, но и апишные запросы для неавторизованных юзеров?
— Если ли какие-то особенности работы с vue и vue-cli (спойлер, у нас будут, но все решаемо)

Если я еще вас не напугал, то это очень здорово. Устраивайтесь поудобнее, сейчас мы шаг за шагом разберемся со всеми вопросами. Разговор будет долгий.

Для начала определим в общих чертах, как это будет работать у нас.

Общая схема авторизации в админке

Прямо сейчас у нас есть админка интернет-магазина на vue, которая по адресу http:///admin/vue/ открывает список товаров, категорий и брендов.

Напомню, как она выглядит

Админка интернет-магазина на vue.js

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

Авторизация в админке интернет-магазина на vue.js

Это простейшая страница с двумя полями ввода: логин и пароль. И единственная кнопка «Войти в админку».

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

Если мы не будем динамически заводить пользователей, то значит, забьем их заранее. Это сильно упростит задачу, а функционал не пострадает. В конце концов, сколько там будет админов самописного интернет-магазина? Скорее всего один или два. Без разграничения прав заводить больше пользователей особого смысла нет. Вот мы и заведем всего двоих 🙂

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

Вот такой план. По идее, задача авторизации серверная, но мы будем писать так, чтобы с каждым новым куском кода был виден прогресс. Чтобы можно было тыкать кнопочки и проверять, что все идет как задумано. Поэтому начнем работу с клиентской части и по мере необходимости будем писать нужные куски на php. А также между делом рассуждать о паролях, хэшированиях, сессиях и прочих важных штуках.

Заменяем index.html на index.php

Сейчас index.html — входной файл админки. Но когда мы подключаем авторизацию, быть главным файлом он уже не может. Он должен быть лишь одним из двух шаблонов, который мы покажем пользователю: шаблон самой админки (index.html) и шаблон страницы логина (login.html). Определять, авторизован ли юзер и какой из шаблонов ему показывать, будет сервер, то есть php. Поэтому стартовым файлом теперь будет index.php, а файл index.html переместим в отдельную папку templates и будем подключать его оттуда.

Создадим в корне админки (папке admin/vue/) папку templates и перенесем в нее файл index.html. А в корне создадим index.php с таким содержимым

include_once './templates/index.html';

Теперь у нас все работает, как и раньше. В этом можно убедиться, зайдя на ваш локальный сервер, у меня это http://w-shop.lc/admin/vue/. Если php работает нормально, то мы увидим страницу товаров в админке.

Идем дальше, нужно сделать страницу логина.

Создаем страницу логина login.html

Это отдельный html-файл, который лежит в той же папке templates рядом с index.html. Давайте сделаем простую html-заготовку для нового файла. В секции head напишем так. Прошу извинить, приходится писать html-теги с тупыми _, чтобы браузер не сильно ругался.

 <_title>Авторизация | Webdevkin 
<_h1>Форма логина <_script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"> <_script src="https://webdevkin.ru/posts/backend/templates/login.js"> 

Пока ничего лишнего, это просто заглушка. Стили для css подключаем из cdn minicss, как и во всей админке. В body просто заголовок и 2 подключенных скрипта: jquery и login.js. Содержимое login.js

var app = (function($) < function init() < console.log('Init login'); >return < init: init >>)(jQuery); jQuery(document).ready(app.init);

Это заготовка модуля логина с единственной функцией init, которая выводит надпись в консоль. Давайте проверим, что новая страница login.html тоже подключается. Для этого в index.php заменим index.html на login.html, проверим — работает.

Теперь у нас есть 2 шаблона и мы можем написать заглушку для проверки авторизации пользователя.

Заглушка для авторизации

Функции, связанные с авторизацией, будут лежать в отдельном файле и своем пространстве имен. Создадим файл auth.php в api/v1/common — там, где уже лежит helpers.php. Если вы разбирали уроки админки, особенно третий, про серверную часть, то эти пути вам будут знакомы. Если у вас свой проект, то кладите auth.php куда удобно. Главное, потом правильно указать пути.

namespace Auth; // Проверка, залогинен ли пользователь function isLogged()

Пока так просто. Единственная функция isLogged, которая возвращает булево значение false — пользователь не залогинен. Реализацию мы напишем чуть позже, когда закончим с клиенсткой частью. А пока подключим и используем auth.php в главном файле index.php. До этого было так

include_once './templates/login.html';

То есть мы подключали шаблон статично. А стало так

include_once '../api/v1/common/auth.php'; $template = \Auth\isLogged() ? 'index' : 'login'; include_once "./templates/$template.html";

Мы подключили модуль авторизации и вывели шаблон в зависимости от результата функции isLogged. Не беда, что сейчас в ней просто return false, допишем код в нужное время. А пока можно еще раз проверить, что мы движемся верно: поменять return false на true и убедиться, что в зависимости от результата мы показываем шаблон index или login.

Как проверим, оставим return false и продолжим работать со страницей логина. Пора добавить в нее форму авторизации и написать javascript-код.

Добавляем форму логина

Как мы договаривались, в форме будут 2 инпута (логин и пароль) и кнопка «Войти». С учетом стилей фреймворка minicss это будет выглядеть так

 

Вход в админку


Можете не обращать внимания на всякие container, col-sm-3 и row — это сетка minicss. Верстайте форму как угодно. Но стоит присмотреться к таким вещам.

1. В форме помимо инпутов и кнопки есть mark — сообщение об ошибке авторизации. По умолчанию скрыто классом hidden
2. У некоторых элементов, а именно формы, сообщения об ошибке и инпутов, есть id. Для первых двух они понадобятся в javascript-коде, а для инпутов навешаны только для связи инпута с label
3. У инпутов есть атрибуты name — так будет удобнее сериализовать данные с формы для отправки на сервер
4. На кнопку не навешан id и даже не будет события по клику — вместо этого мы будем использовать сабмит формы

С html все, пора написать javascript-код.

Авторизация на клиенте

Что же нужно сделать на клиенте? Отправить ajax-запрос с логином и паролем, дождаться ответа и если прилетит ошибка, вывести ее в форме. С клиентской валидацией мы в этот раз связываться не будем. Отправим логин-пароль как есть, хоть пустые. Пусть что нужно, сервер проверяет. Пишем код.

Сначала заведем объект ui, куда сложим нужные элементы dom

var ui = < login: < $form: $('#form'), $error: $('#error') >>;

Затем конфиг, где укажем url, по которому будем пытаться авторизоваться

var config = < api: < login: '/admin/api/v1/auth/login' >>;

Почему url не просто вбить строкой в ajax-запросе? Потому что отдельный объект конфига это чертовски удобная штука. Захотим мы завтра добавить регистрацию и восстановление пароля, а у нас опа — и уже конфиг есть. Останентся в блок api положить рядом 2 урла в полях register и restore — и все. По таким же соображениям и объект ui — пока в нем 2 элемента, но по мере усложнения формы туда можно добавить все остальное. Все лежит в одном месте, легко читается и редактируется.

Дальше вспомним про обработку ошибок, точнее про показ и скрытие ошибки. Напишем 2 вспомогательные функции

function _showError(message) < ui.login.$error.text(message); ui.login.$error.removeClass('hidden'); >function _hideError()

В первую функцию передаем message, чтобы было какой текст показать. И снимаем с элемента error класс hidden. В функцию скрытия ошибки передавать нечего, просто навешиваем обратно hidden.

Дальше идем в функцию init и вместо бесполезного console.log напишем полезные навешивания событий

function init()

По сабмиту формы запустим функцию _login, где и будет отправка данных на сервер, а при нажатии на кнопку в инпутах скроем ошибку, если она до этого была, конечно. На кнопку, напоминаю, мы ничего не вешаем — всем полезным делом займется сабмит формы.

Осталось реализовать _login

function _login(e) < e.preventDefault(); _hideError(); $.ajax(< url: config.api.login, method: 'POST', data: ui.login.$form.serialize(), contentType: 'application/x-www-form-urlencoded', dataType: 'json', success: function() < alert('success'); document.location.reload(); >, error: function(response) < var errorMessage = response.responseJSON.message || 'Что-то пошло не так, попробуйте еще раз'; _showError(errorMessage); >>); >

Что здесь интересного?

1. e.preventDefault() чтобы отменить поведение формы по умолчанию (иначе после сабмита форма перезагрузится)
2. _hideError() запускаем, чтобы опять-таки скрыть ошибки
3. url берем из конфига
4. Метод post — все по фэншую
5. data получаем через jquery-метод формы serialize() — вот и пригодились атрибуты name у инпутов. Удобнее, чем руками value собирать
6. contentType: ‘application/x-www-form-urlencoded’ — это приколы нашей админки на vue. Вы же можете отправить json
7. dataType: ‘json’ — ждем ответ от сервера в формате json
8. В success, это если получим код 200 в ответ, мы понимаем, что логин и пароль прошли, авторизация успешна. Выводим alert success для серьезности момента и перезагружаем страницу. Напомню, после перезагрузки мы будем проверять, залогинен ли пользователь и подключим нужный шаблон. То есть после success и перезагрузки юзер увидит саму админку
9. В error, это когда придет ответ 401 UNAUTHORIZED, мы вытащим сообщение об ошибке, которое вернет сервер, и выведем его в форме через _showError
10. Почему хотя у нас указан dataType в json, мы вытаскиваем данные в блоке error через хитрый response.responseJSON.message? Потому что json приходит нам вместе с 200 http-кодом, а здесь 401 код ошибки. На бекенде мы все равно вернем json для однообразия, но вытаскивать уже приходится чуть хитрее.

Это весь код клиентской части. Неважно, какой способ авторизации на сервере вы выберете, этот код на клиенте подойдет для любой авторизации.

А теперь минутка философии.

Можно ли передавать логин и пароль по сети в открытом виде?

Можно. Не запаривайтесь с шифрованием на клиенте и расшифровкой на сервере. HTTPS — его будет достаточно для шифрования трафика, если только вы не делаете сайт пентагону.

Поставьте ssl-сертификат и все. Это недорого, устанавливается несложно, а некоторые хостеры вообще предлагают бесплатные сертификаты, например, от Let’s Encrypt. Сам так делаю и голова не болит.

Едем дальше, теперь будет только серверный код.

Роутер для авторизации на бекенде

В login.js в конфиге мы задали путь, которому будем авторизовываться — /admin/api/v1/auth/login. Давайте сделаем так, чтобы он работал. Лезем в php.

Напомню, мы делаем авторизацию для админки на vue, а там уже написан REST API на чистом php. Если вы не проходили уроки vue, то рекомендую почитать хотя бы третий урок админки на vue, где мы писали серверный код, а еще лучше до этого прочитать вводную статью — REST API на нативном php. Там все рассказывается более подробно. Без этого код роутера, возможно, будет не так понятен. Хотя если вы и сами знаете, как разрулить маршруты на сервере, то можете просто читать дальше, пробежав этот раздел одним глазком.

Итак, разбираемся с роутером auth. Для начала в файле /admin/api/v1/index.php подключим файл common/auth.php, где у нас будет вся работа с авторизацией.

// Старый код, который уже был написан include_once 'lib/underscore.php'; include_once 'common/helpers.php'; // Добавляем эту строчку include_once 'common/auth.php';

Теперь создадим файл роутера routers/auth.php с таким содержимым

// Роутинг, основная функция function route($data) < // POST /auth/login if ( $data['method'] === 'POST' && count($data['urlData']) === 2 && $data['urlData'][1] === 'login' && isset($data['formData']['user']) && isset($data['formData']['password']) ) < $user = $data['formData']['user']; $password = $data['formData']['password']; $auth = \Auth\login($user, $password); if ($auth['code'] !== 'success') < header('HTTP/1.0 401 Unauthorized'); >echo json_encode($auth); exit; > // Если ни один роутер не отработал \Helpers\throwHttpError('invalid_parameters', 'invalid parameters'); >

Как я предупреждал, подробное разъяснение этих строчек было в двух указанных выше статьях. Если расшифровать, то код говорит нам такое. Если на бекенд придет post-запрос /auth/login с двумя непустыми параметрами user и password, то нужно запустить функцию login() из пространства имен Auth. Если авторизация прошла успешно (code == success), то просто отдать клиенту json с 200 http-кодом ответа (он отдается по умолчанию). Если же авторизация не успешна, то отдать тот же json, но с 401 кодом ошибки (вот его нужно явно прописывать через header)

Чтобы зарегистрировать новый роутер в системе, добавим его в белый список в файле common/helpers.php в методе проверки на валидность. Теперь он будет выглядеть так

// Проверка роутера на валидность function isValidRouter($router)

С роутером разобрались, посмотрим на саму функцию login. Идем в файл common/auth.php и добавляем заглушку для этой функции.

// Логин пользователя function login($user, $password) < if ($user === 'admin' && $password === 'password') < return array( 'code' =>'success' ); > else < return array( 'code' =>'error', 'message' => 'Неправильный логин или пароль' ); > >

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

Если совпадает, вернем массив, который роутер преобразует в json

Если не совпадает, то выходной json будет

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

Здесь еще дополнительно можно провести разные проверки, например, чтобы логин и пароль не были пустыми или слишком короткими. И по результатам валидации выводить соответствующие ошибки клиенту. Благо что для этого предусмотрено поле message. Можно, но я доверю это вам, а в примере оставлю только общую ошибку «не шмогла»

После этого кода можно уже потыкаться в форму логина и проверить связь клиента и сервера. Если все сделали правильно, то на ошибочные логин-пароль вы увидите красное сообщение. А если введете admin/password, то успешный alert(‘success’) и перезагрузку страницы в браузере. Конечно, после перезагрузки мы все еще будем видеть страницу логина. Непосредственно авторизация пользователя будет позже.

Что делать дальше?

И вот мы подбираемся к самому интересному: а где хранить пароли и в каком виде? Давайте сначала подумаем, где их хранить.

Прямо сейчас у нас есть заглушка функции логина, где пароль забит прямо в php-коде. И знаете, я не вижу ничего плохого, чтобы для интернет-магазина домашних пирожков с одним пекарем-админом и 50 посетителями в день вот прямо так и оставить. Да, прямо логин и пароль в коде. Да, при условии, если у вас маленький сайт, и возможно, даже нет базы данных. Помните, мы делали простую админку на файлах? Вполне рабочая админка, легко интегрируется на простой сайт, позволяет менять настройки. И легко закрывается от посторонних глаз авторизацией, которую мы сейчас и рассматриваем. А прикрутить к такому сайту корзину и отправку заказов на почту — и никакой базы не нужно, все прекрасно сработает и так. Главное, чтобы заказы на пирожки шли 🙂

Конечно, это годится для самых простых случаев. Если же мы делаем интернет-магазин посерьезнее, например, с личным кабинетом, или новую соцсеть для котов, то ручное вбивание логинов и паролей не катит. Пользователи сами должны заводить свои пароли, а мы — обеспечить их надежное хранение в базе данных. На этом и договоримся — храним пароли в mysql.

Разбираемся дальше, а в каком виде?

Способы хранения паролей

Первое, что приходит в голову — это табличка users в базе с полями user и password, куда запишем логин-пароль как есть. Но это плохой способ в плане безопасности. Если у нас уведут базу, то все данные пользователей будут как на ладони. Не годится.

Да, в предыдущем примере с хранением пароля прямо в коде я не упоминал про безопасность 🙂 Конечно, если злобный хакер получит доступ по ftp, то наш пароль он легко найдет. Но поэтому-то мы и выбрали способ сложнее и надежнее — держать пароли в базе.

Думаем насчет хранения пароля дальше. Нам приходит в голову, что хорошо бы пароли шифровать. Классно же — зашифровали пароль от пользователя, а когда нужно его проверить — расшифровали обратно. А когда пользователь забудет пароль, то просто вышлем его на почту — вот какой у нас хороший сервис.

Несмотря на то, что этот вариант выглядит заманчивым, не нужно так делать. Исключение — если вам поставлена четкая задача по возможности расшифровки паролей. Если вы работаете в пентагоне, то вопросов нет. А вот если возможность восстановления пароля требует директор мелкого магазинчика, то лучше объяснить все риски.

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

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

Подытожим, в тему шифрования я соваться пока не хочу и вам не советую. Есть способы надежнее и проще.

Как обойтись без шифрования. Хэширование

Фокус в том, что не нужно хранить пароли в открытом виде, но и не нужно шифровать их с возможностью расшифровки. Пароли нужно хэшировать и в базе хранить не пароль, а его хэш. Хитрым образом закодированную строку, которую нельзя расшифровать. Например, не password, а 5f4dcc3b5aa765d61d8327deb882cf99

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

Например, есть простая функция хэширования md5. Вот так она работает

echo md5('password'); > 5f4dcc3b5aa765d61d8327deb882cf99 echo md5('123456'); > e10adc3949ba59abbe56e057f20f883e

Вот такие наборы символов и будем хранить в базе. А сравнивать с этой дикой строкой будем не пароль, а хэш пароля. Примерно так

md5($password) == e10adc3949ba59abbe56e057f20f883e

Вот такая идея. Мы и не знаем пароль, даже если и получим его хэш, но можем проверить его правильность. То, что нужно.

Выбираем правильное хэширование

Идею хранения паролей нашли, то есть хранения не паролей, а их хэшей. А вот какой алгоритм хэширования выбрать?

Давайте посмотрим на то, что пробовали выше — простая функция md5. Алгоритма его расшифровки нет, но тем не менее md5 не рекомендуется для использования. Почему?

Помимо сложности самого алгоритма, есть и другой момент. Да, расшифровать пароль по хэшу нельзя, но его можно подобрать. Простым брутфорсом. К тому же существуют многочисленные базы паролей md5, где всевозможные варианты хранятся тупо списком. И подбор вашего пароля по известному хэшу займет столько времени, сколько понадобится для выполнения sql-запроса

select password from passwords where hash='e10adc3949ba59abbe56e057f20f883e';

Конечно, вы сами не будете использовать пароль 123456, но как насчет ваших пользователей? Да, 123456 можно подобрать и руками, но в таком случае никакие алгоритмы не помогут. Наша же задача максимально позаботиться о юзерах, которые выбирают пароли сложнее qwerty. Думаем дальше, гуглим.

Помимо md5 есть множество алгоритмов хэширования, sha256, sha512 и еще целая толпа. Их сложность выше, но это не отменяет опять-таки существования таблиц с готовыми паролями. Нужно что-то хитрее.

Соль

Этот кулинарный термин активно применяется в теме хэширования. В чем суть?

Чем длиннее пароль, тем сложнее его подобрать. Правильно, в таком случае тупой брутфорс будет работать дольше. Каждый лишний символ прибавляет время, затраченное на подбор пароля. И прибавляет нехило, в геометрической прогрессии. Да, мы говорили о таблицах готовых паролей, но они все-таки не бесконечные и хранить хэши всех паролей неимоверной длины — дело затратное.

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

А никак. Человек скорее убежит с сайта, чем будет сочинять пароль, удовлетворяющий десятку ваших требований. Сам так делал (сбегал). Поэтому не нужно заставлять пользователей, нужно самим добавлять к их паролям те самые странные наборы символов.

То есть вводит юзер пароль qwerty, а мы добавляем к нему какой-то дикий набор, например,

aFkh3JS88aJ;d>sdqwep!@#dfj_

То есть вместо qwerty получаем

aFkh3JS88aJ;d>sdqwep!@#dfj_qwerty

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

Когда мы будем сравнивать введенный пароль с хэшем, то нам нужно будет опять-таки прибавить эту соль к строке и уже ее хэшировать.

Здесь возникают сразу 3 вопроса:

1. откуда брать эту соль
2. как ее хранить
3. как все-таки хэшировать, раз md5 и прочие sha512 не подходят

Разбираемся. Откуда брать соль? Можно завести ее руками, набрав случайный набор символов, как это сделал я в примере выше. А можно генерить соль динамически, чтобы для каждого пользователя она была своя. Соотвественно, в первом случае можно хранить соль прямо в коде, а во втором — в таблице юзеров рядом с хэшем пароля.

Чтобы повысить надежность, можно комбинировать подходы. Например, прибавить к паролю статичную соль и динамическую, которую затем положить в таблицу. Динамическую соль можно получить, например, как md5(user). Да, сам по себе md5 небезопасен, но добавляя соли и применяя md5 несколько раз, надежность хэширования увеличивается.

Здесь уже можно извращаться как угодно. Вот варианты для хэша пароля

md5(password + salt) md5(md5(password + salt)) md5(md5(password) + md5(user))

Вариантов, как видим, множество, все зависит только от нашей фантазии и паранойи. Еще раз уточню, если будете использовать такой подход, то выбирайте вместо md5 что-нибудь понадежнее, например, sha512. Ранние версии вордпресса вообще использовали просто md5(password), но со временем сложность хэширования возрастала. Где-то читал, что некоторые cms применяли md5 к паролю аж 64 раза! Основательный подход.

Как же хэшировать нам?

Подход с формулой вроде md5(md5(password) + md5(user)) мне кажется вполне адекватным. Прямо такую брать не стоит, наверняка для нее существуют подборки паролей. Но можно ее усложнить, применив хэш еще несколько раз.

Можно, но мы пойдем другим путем. В php есть 2 стандартные функции — password_hash и password_verify. Именно их мы и будем использовать для хэширования и проверки пароля. Чем они хороши?

password_hash по умолчанию использует сильный алгорим шифрования, который меняется на еще более сильные в новых версиях php. Для каждого пароля генерируется случайная соль, которую не нужно хранить отдельно. Соль можно задать и руками, но это категорически не рекомендуется. В конце-то концов, люди, создавшие эти алгоритмы, соображают в безопасности гораздо больше меня, который разобрался только в самых азах. Зачем городить свое, если можно воспользоваться наработками специалистов?

Вы спросите, а на фига же мы тут на трех страницах рассуждали о паролях, солях и хэшах, если можно просто взять и сделать так?

password_hash($password, PASSWORD_DEFAULT);

А потом сравнить пароли так

password_verify($passwordFromUser, $passwordFromDatabase);

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

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

Но есть и другие. Те, которым интересно разобраться, какие вообще бывают подходы и почему мы пришли именно к такому. Для вас я и пишу такие простыни текста и надеюсь, что кому-то это будет полезно или интересно. Или откроете что-то новое для себя. А если сделаете еще лучше, чем реализовал я, то это вообще классно. Вот этом случае статья точно будет написана не зря.

Хорошо поговорили, но давайте завязывать с парольной философией и перейдем к коду.

Добавляем таблицу admin_users и пользователей с паролями

Как мы договорились в начале статьи, регистрацией заниматься не будем, поэтому заведем руками двух пользователей нашей админки. Сначала sql-запрос для создания самой таблицы

CREATE TABLE `admin_users` ( `id` int(10) unsigned NOT NULL, `user` varchar(255) NOT NULL, `hash` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8;

Простейшая таблица из трех полей:
1. id — первичный ключ, автоинкремент не делаем, сами проставим.
2. user — имя пользователя
3. hash — хэш пароля, тот самый

Теперь добавим в таблицу 2 строки:

INSERT INTO `admin_users` (`id`,`user`,`hash`) VALUES (1,'admin','$2y$10$uWEWei5BFR5/2sWVo9Rjp.0bHdO3l8Nofq4WE5arMj2VMxkupgzoG'); INSERT INTO `admin_users` (`id`,`user`,`hash`) VALUES (2,'developer','$2y$10$RomPT15GCJDwqr5p5gmtQ.1x.SAb2FMu1V//wBQ2nqAcvp7nVU4/C');

Первый юзер — admin с паролем password, а второй — developer с паролем qwerty. Как я получил хэши? Зашел в первый попавшийся php онлайн редактор и забил туда

echo password_hash('password', PASSWORD_DEFAULT); echo '
'; echo password_hash('qwerty', PASSWORD_DEFAULT);

Обратите внимание, у вас получатся другие значения. Я не знаю, как работает под капотом password_hash, но каждый его вызов для одной и той же строки генерирует разный хэш — и это очень хорошо. Врукопашную играя с формулами типа md5(md5(password) + md5(user)), было бы гораздо сложнее. Поэтому хорошо, что мы разобрались, как работают велосипеды для хэширования, но не стали их использовать.

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

Реализуем функцию логина

Идем в файл common/auth.php и смотрим на заглушку

// Логин пользователя function login($user, $password) < if ($user === 'admin' && $password === 'password') < return array( 'code' =>'success' ); > else < return array( 'code' =>'error', 'message' => 'Неправильный логин или пароль' ); > >

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

Я не буду описывать функции подключения к базе, это все расписано в серверном уроке админки. Да, знаю, в пятый раз уже даю ссылку, но это лучше, чем копипастить сюда все объяснение кода, совсем не маленькое. Поэтому мы воспользуемся готовым, написанным ранее кодом. Впрочем, при необходимости вы сами легко вытащите данные из базы любым удобным способом. У нас же обновленный код функции получится таким

// Логин пользователя function login($user, $password) < $pdo = \Helpers\connectDB(); $query = 'select id, hash from admin_users where user=:user'; $data = $pdo->prepare($query); $data->bindParam(':user', $user); $data->execute(); $item = $data->fetch(); if (password_verify($password, $item['hash'])) < return array( 'code' =>'success' ); > else < return array( 'code' =>'error', 'message' => 'Неправильный логин или пароль' ); > >

В первых 6-ти строках подключение к базе через PDO и выполнение sql-запроса. В 6-й строке вытаскиваем данные в ассоциативный массив $item. Этот массив будет выглядеть так

array( 'id' => , 'hash' => )

Если пользователя с таким именем в базе нет, то массив будет пустой.

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

if ($user === 'admin' && $password === 'password')

напишем честную проверку хэша через password_verify

if (password_verify($password, $item['hash']))

Вот так. Теперь функция login возвращает нам такие же данные, но идет нормальная проверка на совпадение логина и пароля. Можно попробовать в интерфейсе и убедиться, что admin/password по прежнему подходят, хоть мы и перенесли данные в базу. Но теперь еще подходит и вторая пара developer/qwerty. Все работает как надо.

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

Что такое сессии?

Попробую рассказать в паре абзацев.

Сессии — это механизм, который позволяет однозначно идентифицировать пользователя. По-человечески это значит, что для каждого посетителя сайта можно создать уникальное «хранилище», к которому будет доступ только у этого самого посетителя. Хранилище это хранится в файле на сервере. Для каждого пользователя свой уникальный файл, в который мы можем записать какие угодно данные по конкретному юзеру. Например, авторизован он или нет. Как раз то, что нам нужно.

На самом деле сессии хранятся не обязательно в файлах. Например, cms Modx хранит их базе данных. Но сейчас нам это не важно, главное, что сессии предоставляют удобный способ работать с данными, уникальными для пользователя. Для нас сессия выглядит как обычный ассоциативный массив под названием $_SESSION, в разные поля которого можно записать хоть число, хоть строку, хоть сериализованный объект. И конечно, можно записать $_SESSION[‘authorized’] = 1. Таким образом, мы запомним пользователя, ведь сессия не пропадет при перезагрузке страницы. Сессию можно удалить тремя способами:

1. Ее может сбросить сам пользователь, удалив куку PHPSESSID
2. Сессия протухнет сама через определенное время, когда мы закроем браузер. Обычно по дефолту 24 минуты
3. Мы сами сбросим сессию через php-код. Этим приемчиком мы воспользуемся, когда будем реализовывать разлогинивание

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

Давайте же пробовать на практике.

Пишем код непосредственной авторизации на сессиях

Сначала сделаем так, чтобы при успешной проверке логина-пароля в сессию записывались данные об авторизации. Смотрим опять функцию login, там, где идет проверка условия password_verify. И в блоке if перед возвратом массива с code = success дописываем такую строку

$_SESSION['userId'] = $item['id'];

То есть блок if будет выглядеть так

if (password_verify($password, $item['hash'])) < $_SESSION['userId'] = $item['id']; return array( 'code' =>'success' ); > else < return array( 'code' =>'error', 'message' => 'Неправильный логин или пароль' ); >

Именно эта новая строка и «логинит» пользователя. В сессию мы запишем id юзера. В нашей админке можно было обойтись просто булевым флагом authorized, но часто бывает, что айдишник оказывается очень нужной штукой. Например, если нам понадобится вытащить из базы email пользователя, чтобы отправить ему письмо, или имя, чтобы написать в шапке сайта что-то вроде «Привет, Вася!»

Есть соблазн закинуть все нужные данные в сессии, но не стоит увлекаться этим. Сессии не резиновые и занимают место. Лучше сохранить только id, а при необходимости доставать нужные данные из базы.

Но это мы немного отвлеклись. Данные в сессию мы сохранили, но еще нужно стартовать сам механизм сессий. Делается это с помощью функции session_start. У ней есть одна хитрость. Вызывать ее нужно до того, как мы вывели в браузер хоть что-то, хоть html, хоть json, хоть пустой пробел. То есть ставьте вызов session_start как можно выше. Часто его ставят просто в начале php-шного файла.

В нашем случае все прекрасно сработает, если мы поставим session_start прямо перед $_SESSION[‘userId’] = $item[‘id’]. Но так как у нас все запросы проходят через главный файл /admin/api/v1/index.php, то лучше стартовать сессии именно там. Прямо в самом начале файла.

Вообще без неободимости запускать сессии не стоит. То есть пока у нас сессия используется только в функции login, вроде бы и стартовать сессии нужно только там. Но я чуть забегу вперед и скажу, что нам понадобится, чтобы сессии работали при любом апишном запросе. Чуть позже разберем, почему, а пока просто зафигачим код в начало admin/api/v1/index.php

// Открываем сессию session_start();

Есть еще один момент. Сессии работают так, что запускать их нужно только один раз. Повторный запуск session_start выкинет ошибку. У нас работает все правильно, запускаем только один раз, но я предпочитаю подстраховаться и перед запуском проверить, а не была ли сессия уже запущена. Делается это с помощью функции session_id(), вот так

// Открываем сессию if (session_id() == »)

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

Теперь пользователь залогинен, осталось научиться это понимать. Вспомним функцию isLogged, которую мы написали в самом начале, поставив там заглушку return false. Давайте это исправим и напишем реальный код

function isLogged()

Вот так, ничего сложного. Функция проверяет, существует ли сессия userId и не равна ли она пустой строке. Напомню, userId у нас всегда число.

Идем дальше. Ищем файл admin/vue/index.php, тот самый главный, который разруливает, какую страницу показывать: админки или форму логина. Вот он

include_once '../api/v1/common/auth.php'; $template = \Auth\isLogged() ? 'main' : 'login'; include_once "./templates/$template.html";

Мы научили функцию \Auth\isLogged() делать нормальную проверку залогиненности, но этому index.php тоже нужно стартовать сессию, чтобы isLogged нормально отработал. Делаем это точно так же, как и выше — добавляем session_start с проверкой в начало файла. Теперь он выглядит так

// Стартуем сессию if (session_id() == '') < session_start(); >include_once '../api/v1/common/auth.php'; // Выбираем и подключаем нужный шаблон $template = \Auth\isLogged() ? 'main' : 'login'; include_once "./templates/$template.html";

Последнее, что осталось сделать перед проверкой логина — это зайти в login.js и убрать alert(‘success’) из раздела success. Alert нам больше не нужен. Если логин и пароль мы ввели правильно, то страница просто перезагрузится и сразу покажет нам админку. А если нет, то по-прежнему в форме логина выведет сообщение об ошибке.

Вот теперь можно опять проверять логин — все работает, как надо.

Разлогинивание

В любой порядочной админке нужно уметь не только входить, но и выходить из нее. После того, что мы сделали для авторизации, разлогин покажется нам очень простым. Сначала определимся с роутером. Заведем новый роут для /auth/logout в файле routers/auth.php

// GET /auth/logout if ( $data['method'] === 'GET' && count($data['urlData']) === 2 && $data['urlData'][1] === 'logout' ) < \Auth\logout(); header('Location: /admin/vue'); exit; >

То есть сначала мы вызываем \Auth\logout(), в ней собственно разлогиниваемся (удаляем сессию), а затем редиректим на /admin/vue — главную страницу админки. То есть то же самое, что и при логине на клиенте, где мы перезагружаем страницу через document.location.reload(). Здесь же тоже нужно направить на стартовую страницу, но уже кодом на бекенде.

Дальше пишем код для logout в файле common/auth.php

// Разлогин function logout()

API для выхода готово, осталось только разместить соответствующую кнопку в шапке админки. В компоненте AppHeader.vue после табов разместим ссылку

Готово, можно пробовать разлогиниться. И логиниться заново под вторым пользователем.

Закрываем API для неавторизованных пользователей

Мы молодцы и в админку теперь можно попасть только зная логин и пароль. Но апишные запросы, те самые получения списка товаров и брендов и все редактирование брендов остается открытым. Можете проверить. Сначала выйдите из админки, то есть разлогиньтесь. При заходе на http://w-shop.lc/admin/vue/ вы увидите форму логина, а вот если дернете прямо в браузере http://w-shop.lc/admin/api/v1/products, то получите полный список товаров. То же самое и с остальными запросами.

Конечно, это плохо и оставлять так нельзя. Файл admin/vue/index.php, который главная страница админки, мы закрыли проверками, а вот /admin/api/v1/index.php — входную точку для апи-запросов, еще нет. Давайте это исправим.

Напишем вот такой код в /admin/api/v1/index.php после подключения всех файлов, но перед проверкой роутера на валидность

// Закрываем API для неавторизованных пользователей if ($router !== 'auth' && !\Auth\isLogged()) < header('HTTP/1.0 403 Forbidden'); exit(); >

Две проверки в условии, если не прокатывают, то вернем клиенту 403 ошибку и завершим работу. Второе условие очевидно, мол, если пользователь не авторизован. А первое чуть хитрее, роутер не должен быть auth. Почему? А потому, что без этого условия мы закроем доступ к апишной функции логина, а она должна быть как раз открыта всегда. На самом деле она нужна только для неавторизованных юзеров, но не будем усложнять, особой роли это не играет.

Вот теперь попробуйте выйти из админки и дернуть тот самый запрос за списком товаров — http://w-shop.lc/admin/api/v1/products. Получим в ответ 403 Forbidden. Все лишнее закрыто от посторонних глаз. Отлично.

Мы сделали все, что касается авторизации, но остался один момент. У нас админка на vue, а работает ли оно все как надо?

Проверяем работоспособность vue

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

Но попробуем пересобрать админку, чтобы убедиться, что все хорошо. Сначала в production режиме

npm run build

Все собирается, открываем http://w-shop.lc/admin/vue/ (или какой у вас хост), проверяем работу админки, разлогин, попадаем на форму логина, логинимся — все в порядке. То есть наш код готов к выкладыванию в продакшен. Это хорошо.

Но при разработке нам гораздо важнее режим девелопера, который предоставляет vue-cli. Попробуем запустить его

npm run dev

Опа, открылся http://localhost:8080/, но вернул 404 ошибку с текстом «Cannot GET /». Что за фигня?

Дело в том, что vue-cli при запуске ищет входной файл index.html, который располагается в корневой папке /admin/vue, рядом с конфигом вебпака. А у нас там нет index.html, только index.php. А index.html вместе с login.html мы вынесли в отдельную папку templates.

Почему же, когда мы сбилдили приложение в production режиме и зашли через нормальный хост http://w-shop.lc/admin/vue/, то все было хорошо? Скорее всего, потому что в настройках вашего домена в nginx или apache первым читается файл index.php. Чаще всего так и делают. Например, в nginx у меня было так

server < listen 80; root /home/sn8/www/webdevkin_shop; // Вот эта строка index index.php index.html; server_name w-shop.lc; . >

То есть, в первую очередь веб-сервер должен искать index.php, если его нет, то index.html.

Так вот, первая проблема, что vue-cli работает в обход внешнего веб-сервера и ищет index.html. Ему пофиг, что в nginx указан index.php. А html-файл у нас лежит в templates.

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

Я перепробовал кучу вариантов с настройками vue и вебпака, но вынужден признать, что я все еще дурачок в сборке vue и вебпаке. Ну или по крайней мере все еще учусь. Мне казалось, что это простейшая задача — научить vue брать исходный index.html из другого места, ан нет. Адекватно это сделать так и не удалось.

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

index.html и login.html мне пришлось вытащить из папки templates наружу — в корень. Кладем их прямо рядом с index.php. До кучи вытащим из templates и login.js и переместим его в папку src — пусть login.js не будет одинок. А папку templates сносим на фиг.

Теперь у нас в корне лежат index.php, index.html и login.html. Не забудем поправить пути в index.php до html-файлов с учетом выноса их из templates , то есть вот так

// Выбираем и подключаем нужный шаблон $template = \Auth\isLogged() ? 'index' : 'login'; include_once "./$template.html";

Да, еще в login.html нужно поправить путь до login.js, мы же его переместили в src

Вот теперь будет работать. Сначала пробуем в режиме production, через хост. Мой https://w-shop.lc/admin/vue/ открывает index.php и начинает разруливать авторизацию. Запускаем в dev режиме localhost:8080 — vue сразу подхватывает index.html в обход авторизации. Отлично.

Но нас ждет еще один сюрприз. В dev режиме шаблон-то подхватывается, но админка пустая, все апишные запросы отдают 403 Forbidden. Почему? Давайте посмотрим на код в /admin/api/v1/index.php, где это может быть. А вот здесь

// Закрываем API для неавторизованных пользователей if ($router !== 'auth' && !\Auth\isLogged()) < header('HTTP/1.0 403 Forbidden'); exit(); >

Правильно, мы не проходим второе условие isLogged, проверку на залогиненность. Вот даже комментарий оставили «Закрываем API для неавторизованных пользователей». А сейчас мы в dev режиме открыли админку в обход авторизации. А чтобы заработали апи-запросы, обернем этот код еще в одно условие

// Не проверяем авторизацию в dev-режиме if ($_SERVER['HTTP_ORIGIN'] !== 'http://localhost:8080') < // Закрываем API для неавторизованных пользователей if ($router !== 'auth' && !\Auth\isLogged()) < header('HTTP/1.0 403 Forbidden'); exit(); >>

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

Вот и все. Больше кода в этом уроке не будет. Поздравляю, если дочитали до конца 🙂

Вместо заключения

Проблемы с dev режимом во vue — это неприятный момент. Мы хотим и апишечку подергать, и все удобства vue-cli использовать. И на елку влезть, и ничего не ободрать. Возможно, есть более изящный способ обойти эти проблемы в dev режиме, но я их пока не нашел. Поэтому приходится подпирать код лишними условиями.

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

Именно поэтому, разбираясь с проблемами dev режима, я начал жалеть, что вместе с vue не стал изучать, например, laravel. Это отличный php-фреймворк, который дружит с vue. Думаю, в этой связке проблем с авторизацией нет. Или они решаются более приличным способом.

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

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

Здесь исходники интернет-магазина, админки и кода авторизации — скачать.

Всем спасибо и до встречи.

Заходите в группу в контакте — https://vk.com/webdevkin

Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *