Генератор логинов
Нужен генератор логинов, суть какая: из базы данных берётся Ф.И.О., в идеале — список Ф.И.О. к ним нужно сгенерировать логин по первым буквам Ф.И.О., например, есть следующая запись: Иванов Иван Иванович, логин к нему необходимо сгенерировать такой: iii, после проверить на задвоенность и если такой логин уже имеется, то прибавить букву к фамилии, если есть подобный, прибавить к имени, к отчеству, и повторять, пока логин не станет уникальным.
Я себе уже всю голову сломал, а как это сделать — не знаю. То ли знаний не хватает, то ли рук.
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Поиск твинк логинов
Всем привет, у кого есть мысли, как определять твинк логины по типу user, user1, user2, кроме.
Как подключить массивы логинов и паролей!
Привет, есть такая форма авторизации, как к ней подключить два разных файла, один с массивом.
форма для Массива логинов и паролей
Создать массивы логинов и паролей для доступа к секретной информации. Создать форму для ввода.
задание логин + пароль. Проверка логинов и паролей
Даны 5 логинов и 5 паролей, каждый привязан друг к другу 1 логин — 1 пароль. Нужно написать сайт.
55 / 22 / 10
Регистрация: 18.05.2016
Сообщений: 89
Храните ник в базе, и при новой генерации делайте запрос, если что то вернется, то он не уникальный, прибавляете еще символы и делаете еще запрос, и так пока не вернется null
Если вернется null то ник уникальный
Регистрация: 03.05.2016
Сообщений: 3
Чтобы хранить его в базе — его нужно сперва сгенерировать, верно? Задача в том, что создать обработчик, который сгенерирует в форме логин, а затем можно будет воткнуть этот логин в базу из той же формы, а не в том, чтобы запихать в базу данных логин, а затем проверить его на задвоенность, это уже второй кусок. Предполагается, что пользователей в базе данных будет ~500. Вносить для каждой записи логин собственноручно? Какой тогда смысл. Нет, сейчас конкретно нужен алгоритм под реализацию этой задачи. Вот что вышло у меня на данный момент:
1. Транслитерация Ф.И.О.
2. Генерация логина по первым буквам Ф.И.О.
3. Проверка на задвоенность.
4. Внесение в базу данных.
И если понятно, как реализовать транслитерацию и проверку на задвоенность, то как сгенерировать и внести — не понятно.
Генерация HTTP запросов
В последнее время я все более часто наблюдаю в основном форуме РНРClub вопросы на тему создания POST и GET запросов, а так же вопросы на тему: «Как мне посредством функции header сформировать POST запрос». Я считаю, что уже давно назрела необходимость расставить точки над «и» в использовании данной технологии, поскольку начинающие программисты просто не понимают принципов работы веба, как такового. Итак, начнем наше путешествие по миру протокола HTTP.
- 1. Протокол HTTP. Введение
- 1.1 Клиент и сервер
- 1.2 Пишем наш первый HTTP запрос
- 1.3 Структура запроса
- 3.1 Content-Type: application/x-www-form-urlencoded
- 3.2 Content-Type: multipart/form-data
1. Протокол HTTP. Введение
Сразу хочу уточнить одну маленькую вещь. Страшное слово протокол есть не что иное, как соглашение множества людей, просто в один прекрасный момент люди решили: «Давайте будем делать так, и тогда все будет в порядке». Бояться нечего, все просто до безобразия и это безобразие мы сейчас будем вскрывать. Итак, что же это такое протокол HTTP и с чем его едят?
1.1 Клиент и сервер
Чудес в мире, а тем более в мире программизма и интернета не бывает! Усвойте это как незыблемую истину. И, если программа не работает или работает не так как хочется, то, значит, скорее всего, она либо написана не правильно, либо содержит ошибки. Итак, как же все-таки браузер просит сервер прислать ему хоть что-нибудь? Да очень просто! Надо только немного расслабиться и начать получать удовольствие от процесса 🙂
1.2. Пишем наш первый HTTP запрос
Если Вы думаете, что все слишком сложно, то Вы ошибаетесь. Человек так устроен, что просто не способен создавать что-то сложное, иначе он сам в этом запутается 🙂 Итак, есть браузер и есть Web-сервер. Инициатором обмена данными всегда выступает браузер. Web-сервер никому, никогда просто так ничего не пошлет, чтобы он что-нибудь отправил браузеру надо, чтобы браузер об этом попросил. Простейший HTTP запрос моет выглядеть, например, так:
GET http://www.php.net/ HTTP/1.0\r\n\r\n
- GET (В переводе с английского означает «получить») тип запроса, тип запроса может быть разным, например POST, HEAD, PUT, DELETE (часть из них мы рассмотрим ниже).
- http://www.php.net/ URI (адрес) от которого мы хотим получить хоть какую-нибудь информацию (естественно мы надеемся подучить HTML страницу).
- HTTP/1.0 тип и версия протокола, который мы будем использовать в процессе общения с сервером.
- \r\n конец строки, который необходимо повторить два раза, зачем, станет понятно немного позднее.
1.3 Структура запроса
Рассмотрим, из чего состоит HTTP запрос. Все достаточно просто. Начнем с того, что HTTP запрос это вполне осмысленный текст. Из чего же он состоит в общем случае? Будем рассматривать протокол HTTP 1.0. Итак:
Request-Line [ General-Header | Request-Header | Entity-Header ]\r\n[ Entity-Body ]
- Request-Line строка запроса
- Формат: «Method Request-URI HTTP-Version\r\n»
- Method метод, которым будет обрабатываться ресурс Request-URI, может быть GET, POST, PUT, DELETE или HEAD.
- Request-URI относительная или абсолютная ссылка на страницу с набором параметров, например, /index.html или http://www.myhost.ru/index.html или /index.html?a=1&b=qq. В последнем случае серверу будет передан запрос с набором переменных a и b с соответствующими значениями, а знак «&» амперсант служит разделителем между параметрами.
- HTTP-Version версия HTTP протокола, в нашем случае «HTTP/1.0».
Нам крайне интересны методы обработки GET и POST. Методом GET можно просто передать параметры в скрипт, а методом POST можно эмулировать submit формы.
Для метода GET, Request-URI может выглядеть, например, так: «/index.html?param1=1¶m2=2».
-
General-Header главная часть заголовка.
Формат: [Date: value\n | Pragma: no-cache\n]
Может иметь только два параметра: Date или Pragma. Date дата по Гринвичу в формате «День недели, Число Месяц Год ЧЧ:ММ:СС GMT», например, «Tue, 15 Nov 1994 08:12:31 GMT» дата создания запроса. Pragma может иметь одно значение no-cache, которое запрещает кэширование страницы.Request-Header может иметь следующие параметры: Allow, Authorization, From, If-Modified-Since, Referer, User-Agent.
В данной главе мы не будем рассматривать параметр Autorization, так как он используется для доступа к закрытым ресурсам, что требуется не так уж часто. Вы можете самостоятельно изучить формирование заголовка для авторизованного доступа на сайте www.w3c.org.А теперь после таких страшных слов давайте попробуем немного успокоиться и понять, что же нам надо? Понимать мы естественно будем на примерах.
Давайте представим, что нам надо получить страницу с сайта, передав Cookies (Печеньки), иначе нас просто пошлют как незванных гостей, и более того, известно, что на данную страницу пускают только после того, как Вы побывали на главной странице сайта.
2 Метод GET
Напишем наш запрос.
GET http://www.site.ru/news.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
\r\nДанный запрос говорит нам о том, что мы хотим получить содержимое страницы по адресу http://www.site.ru/news.html, использую метод GET. Поле Host говорит о том, что данная страница находится на сервере www.site.ru, поле Referer говорит о том, что за новостями мы пришли с главной страницы сайта, а поле Cookie говорит о том, что нам была присвоена такая-то кука. Почему так важны поля Host, Referer и Сookie? Потому что нормальные программисты при создании динамических сайтов проверяют данные поля, которые появляются в скриптах (РНР в том числе) в виде переменных. Для чего это надо? Для того, например, чтобы сайт не грабили, т.е. не натравливали на него программу для автоматического скачивания, или для того, чтобы зашедший на сайт человек всегда попадал бы на него только с главной страницы и.т.д.
Теперь давайте представим, что нам надо заполнить поля формы на странице и отправить запрос из формы, пусть в данной форме будет два поля: login и password (логин и пароль),- и, мы естественно знаем логин и пароль.
GET http://www.site.ru/news.html?login=Petya%20Vasechkin&password=qq HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
\r\nЛогин у нас «Petya Vasechkin» Почему же мы должны писать Petya%20Vasechkin? Это из=за того, что специальные символы могут быть распознаны сервером, как признаки наличия нового параметра или конца запроса и.т.д. Поэтому существует алгоритм кодирования имен параметров и их значений, во избежание оштбочных ситуаций в запросе. Полное описание данного алгоритма можно найти здесь, а в PHP есть функции rawurlencode и rawurldecode для кодирования и декодирования соответственно. Хочу отметеить, что декодирование РНР делает сам, если в запросе были переданы закодированные параметры. На этом я закону первую главу знакомства c протоколом HTTP. В следуючей главе мы рассмотрим построение запросов типа POST (в переводе с английского «отправить»), что будет гораздо интереснее, т.к. именно данный тип запросов используется при отправке данных из HTML форм.
3. Метод POST.
В случае HTTP запроса типа POST существует два варианта передачи полей из HTML форм, а именно, используя алгоритм application/x-www-form-urlencoded и multipart/form-data. Различия между данными алгоритмами весьма существенные. Дело в том, что алгоритм первого типа создавался давным-давно, когда в языке HTML еще не предусматривали возможность передачи файлов через HTML формы. Итак, давайте рассмотрим эти алгоритмы на примерах.
3.1 Content-Type: application/x-www-form-urlencoded.
Пишем запрос, аналогичный нашему запросу GET для передачи логина и пароля, который был рассмотрен в предыдущей главе:
POST http://www.site.ru/news.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 35\r\n
\r\n
login=Petya%20Vasechkin&password=qqЗдесь мы видим пример использования Content-Type и Content-Length полей заголовка. Content-Length говорит, сколько байт будет занимать область данных, которая отделяется от заголовка еще одним переводом строки \r\n. А вот параметры, которые раньше для запроса GET помещались в Request-URI, теперь находятся в Entity-Body. Видно, что они формируются точно также, просто надо написать их после заголовка. Хочу отметить еще один важный момент, ничто не мешает, одновременно с набором параметров в Entity-Body, помещать параметры с другими именами в Request-URI, например:
POST http://www.site.ru/news.html?type=user HTTP/1.0\r\n
.
\r\n
login=Petya%20Vasechkin&password=qq3.2 Content-Type: multipart/form-data
Как только интернет мир понял, что неплохо бы было через формы отсылать еще и файлы, так W3C консорциум взялся за доработку формата POST запроса. К тому времени уже достаточно широко применялся формат MIME (Multipurpose Internet Mail Extensions многоцелевые расширения протокола для формирования Mail сообщений), поэтому, чтобы не изобретать велосипед заново, решили использовать часть данного формата формирования сообщений для создания POST запросов в протоколе HTTP.
Каковы же основные отличия этого формата от типа application/x-www-form-urlencoded?
Главное отличие в том, что Entity-Body теперь можно поделить на разделы, которые разделяются границами (boundary). Что самое интересное каждый раздел может иметь свой собственный заголовок для описания данных, которые в нем хранятся, т.е. в одном запросе можно передавать данные различных типов (как в Mail письме Вы одновременно с текстом можете передавать файлы).
Итак, приступим. Рассмотрим опять все тот же пример с передачей логина и пароля, но теперь в новом формате.
POST http://www.site.ru/news.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
Content-Type: multipart/form-data; boundary=1BEF0A57BE110FD467A\r\n
Content-Length: 209\r\n
\r\n
—1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name=»login»\r\n
\r\n
Petya Vasechkin\r\n
—1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name=»password»\r\n
\r\n
qq\r\n
—1BEF0A57BE110FD467A—\r\nТеперь давайте разбираться в том что написано. 🙂 Я специально выделил некоторые символы \r\n жирным, чтобы они не сливались с данными. Присмотревшись внимательно можно заметить поле boundary после Content-Type. Это поле задает разделитель разделов границу. В качестве границы может быть использована строка, состоящая из латинских букв и цифр, а так же из еще некоторых символов (к сожалению, не помню каких еще). В теле запроса в начало границы добавляется ‘—‘, а заканчивается запрос границей, к которой символы ‘—‘ добавляются еще и в конец. В нашем запросе два раздела, первый описывает поле login, а второй поле password. Content-Disposition (тип данных в разделе) говорит, что это будут данные из формы, а в поле name задается имя поля. На этом заголовок раздела заканчивается и далее следует область данных раздела, в котором помещается значение поля (кодировать значение не требуется!).
Хочу обратить Ваше внимание та то, что в заголовках разделов не надо использовать Content-Length, а вот в заголовке запроса надо и его значение является размером всего Entity-Body, стоящего после второго \r\n, следующего за Content-Length: 209\r\n. Т.е. Entity-Body отделяется от заголовка дополнительным переводом строки (что можно заметить и в разделах).
А теперь давайте напишем запрос для передачи файла.
POST http://www.site.ru/postnews.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/news.html\r\n
Cookie: income=1\r\n
Content-Type: multipart/form-data; boundary=1BEF0A57BE110FD467A\r\n
Content-Length: 491\r\n
\r\n
—1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name=»news_header»\r\n
\r\n
Пример новости\r\n
—1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name=»news_file»; filename=»news.txt»\r\n
Content-Type: application/octet-stream\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
А вот такая новость, которая лежит в файле news.txt\r\n
—1BEF0A57BE110FD467A—\r\nВ данном примере в первом разделе пересылается заголовок новости, а во втором разделе пересылается файл news.txt. Внимательный да увидит поля filename и Content-Type во втором разделе. Поле filename задает имя пересылаемого файла, а поле Content-Type тип данного файла. Application/octet-stream говорит о том, что это стандартный поток данных, а Content-Transfer-Encoding: binary говорит на о том, что это бинарные данные, ничем не закодированные.
Очень важный момент. Большинство CGI скриптов написано умными людьми, поэтому они любят проверять тип пришедшего файла, который стоит в Content-Type. Зачем? Чаще всего закачка файлов на сайтах используется для получения картинок от посетителя. Так вот, браузер сам пытается определить что за файл посетитель хочет отправить и вставляет соответствующий Content-Type в запрос. Скрипт его проверяет при получении, и, например, если это не gif или не jpeg игнорирует данный файл. Поэтому при «ручном» формировании запроса позаботьтесь о значении Content-Type, чтобы оно было наиболее близким к формату передаваемого файла.
image/gif для gif image/jpeg для jpeg image/png для png image/tiff для tiff (что используется крайне редко, уж больно емкий формат) В нашем примере формируется запрос, в котором передается текстовый файл. Точно так же формируется запрос для передачи бинарного файла.
4. Постскриптум.
Думаю, что о передаче запросов на сервер не стоит рассказывать подробно. Это уже дело чисто РНР техники :-). Достаточно внимательно прочитать раздел о функциях работы с сокетами, или о функциях модуля CURL в официальной документации РНР.
Из выше сказанного, надеюсь теперь понятно, почему вопрос: «Как мне сформировать POST запрос, используя функцию header?» бессмысленен. Функция header(string) добавляет запись только в заголовок запроса, но никак не в тело запроса.
Есть еще один тип запросов Content-Type: multipart/mixed, надеюсь после прочтения данной статьи Вы легко разберетесь с данным типом сами. Подробно изучить его можно Здесь
О запросах других типов можно прочитать в официальной спецификации протокола HTTP 1.0 здесь.
Авторизация на сессиях своими руками. Делаем логин в админке
Авторизация — это такая задача, для которой можно и нужно брать готовые решения. Если ваш проект посажен на популярную CMS или фреймворк, то скорее всего, там все уже придумано. Не стоит городить свое решение, лучше изучить, как внедрить готовое. Еще можно подумать об авторизации через соцсети, модно и молодежно. Или сделать авторизацию по смс, тоже хорошо. Но если вам эти варианты не подходят, то давайте думать, как сделать руками.
Сначала выберем проект, в котором будем делать авторизацию. Сначала я хотел сделать отдельный, но потом подумал, а зачем? У нас есть админка интернет-магазина, которая прекрасно подойдет для экспериментов. Админка написана на vue.js, но если вы им не интересуетесь, не беспокойтесь. Мы сделаем так, чтобы авторизацию можно было прикрутить к любому проекту. Не копипастнуть, конечно, но весь код можно будет переиспользовать.
Давайте начнем. Осторожно, это прям лонгрид.
Мы будем писать код на php, как обычно, а авторизацию сделаем на сессиях. Если описать все одной строчкой кода, то вот
$_SESSION['authorized'] = 1;
Так мы авторизовали пользователя. Серьезно, это самый важный код. Но есть и другие вопросы:
— Как выводить для неавторизованных пользователей страницу логина, а для остальных — самого сервиса?
— Как сделать форму авторизации и написать клиентский код?
— Можно ли передавать логин и пароль по сети в открытом виде?
— Какое будет серверное api?
— Логин, разлогин, какой функционал еще понадобится?
— Какие http-коды ошибок использовать?
— Как хранить пароли, в коде или в базе?
— А что если взломают ftp или уведут базу?
— В каком виде хранить пароли?
— Нужно ли их как-то шифровать?
— А как потом расшифровывать?
— Или есть еще какие-то способы?
— Хэширование? Что это вообще такое?
— Так какой все-таки способ выбрать и как его реализовать?
— Сессии, что это и как работают?
— Та строчка с $_SESSION[‘authorized’] — неужели это все, что нужно?
— Как закрыть не только страницы, но и апишные запросы для неавторизованных юзеров?
— Если ли какие-то особенности работы с vue и vue-cli (спойлер, у нас будут, но все решаемо)Если я еще вас не напугал, то это очень здорово. Устраивайтесь поудобнее, сейчас мы шаг за шагом разберемся со всеми вопросами. Разговор будет долгий.
Для начала определим в общих чертах, как это будет работать у нас.
Общая схема авторизации в админке
Прямо сейчас у нас есть админка интернет-магазина на vue, которая по адресу http:///admin/vue/ открывает список товаров, категорий и брендов.
Напомню, как она выглядит
Мы хотим, чтобы доступ к ней был только у авторизованных пользователей. А сначала юзеры должны видеть страничку логина. Вот так она будет выглядеть

Это простейшая страница с двумя полями ввода: логин и пароль. И единственная кнопка «Войти в админку».
Регистрацию мы делать не будем, это отдельная эпопея. Регистрацией, отправкой на почту писем с подтверждением и восстановлением пароля мы займемся, когда дойдем до личного кабинета. Разграничивать права пользователей тоже не будем. Это интересная задача, но мы сначала хотим разобраться с самой авторизацией, а уже потом копать дальше.
Если мы не будем динамически заводить пользователей, то значит, забьем их заранее. Это сильно упростит задачу, а функционал не пострадает. В конце концов, сколько там будет админов самописного интернет-магазина? Скорее всего один или два. Без разграничения прав заводить больше пользователей особого смысла нет. Вот мы и заведем всего двоих 🙂
Вернемся к форме логина. При нажатии на кнопку мы отправим логин и пароль на сервер, сделаем специальный запрос для этого. На сервере функция логина проверит, есть ли у нас юзер с такими данными. Если есть, то мы авторизуем этого юзера и пропишем его в сессию, чтобы при перезагрузке страницы не пришлось вводить логин-пароль заново. Если такого юзера нет, то есть логин или пароль неверные, то вернем в браузер ошибку и выведем в форме логина соответствующее сообщение. А еще реализуем разлогинивание, чтобы все как у людей.
Вот такой план. По идее, задача авторизации серверная, но мы будем писать так, чтобы с каждым новым куском кода был виден прогресс. Чтобы можно было тыкать кнопочки и проверять, что все идет как задумано. Поэтому начнем работу с клиентской части и по мере необходимости будем писать нужные куски на 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, гита.
Истории из жизни айти и обсуждение кода.Как бы вы реализовали форму аутентификации на сайте? Вопрос для собеседования на Junior/Middle/Senior?
В свете исследования «Веб-разработчики пишут небезопасный код по умолчанию« мне подумалось, что именно так может звучать один из базовых вопросов на собеседовании с точки зрения проверки знания web-разработчика от уровня Junior до Senior.
Тема с одной стороны в общем-то простая, а с другой — многогранная. Можно сделать “на коленке”, а можно и “по-взрослому” — зависит от знаний конкретного девелопера и технического задания. Ну и не привязывается к конкретному языку. Что nodejs, что .net, что PHP — на ответы это не влияет. Ну и отлично же! Давайте попробуем.
Я попытался разбить вопросы на три уровня. Каждый следующий уровень обязан включать все вопросы выше, т.е. уровни и вопросы отсортированы от простых к более сложным.
Как бы вы ответили на конкретный вопрос? Попробуйте проверить себя и потратить пару минут на обдумывание прежде чем читать ответ.
Восклицательным знаком ⚠ помечены вопросы, на которых можно «засыпаться» и оставить плохое впечатление о себе у интервьюера. Так же я позволил себе добавить еще пункты, которые подразумевают «Регистрацию», но по касательной. Многие ответы обрамил ссылками, которые помогут разобраться чуть глубже в конкретном вопросе, думаю будет полезно.
Junior level
⚠ Нужно ли скрывать вводимый пароль на странице?
Конечно. Никто не хочет, чтобы кто-то подсматривал пароли из-за плеча, верно? У input есть специальный атрибут password для этого. Это очевидно, но это и первый пункт. Дальше будет сложнее, обещаю 🙂
Должны ли валидироваться поля для ввода на клиентской стороне?
Должны. Как минимум на пустые значения. Отправлять пустую форму на сервер плохая идея. Во-первых, зря нагружает бэкенд, во-вторых страдает юзабилити — вы заставляете пользователя ждать там, где можно обойтись и без запросов на сервер.
Аутентификация? Авторизация? Идентификация? В двух словах расскажите что за что отвечает.
Часто эти понятия путают, поэтому давайте кратко:
- Идентификация — это проверка что такой пользователь/логин/email существует в системе.
- Аутентификация — проверка связки логина и пароля, то есть проверка на то, не выдаёт ли пользователь себя за другого человека.
- Авторизация — проверка прав доступа пользователя к внутренним ресурсам.
Запрос с данными формы должен идти через GET или POST?
Правильнее делать используя метод POST. Ничего вам не мешает сделать это любым другим методом, однако правильнее всего данные формы слать именно через POST. Изначально этот тип запроса был спроектирован не идемпотентным. Это значит, что отсылая его вы не можете гарантировать, что последствия его выполнения будут одинаковыми, если вызвать его несколько раз подряд. Поэтому в случае обрыва соединения браузер переспросит вас хотите ли вы заново отослать эту форму (вы наверняка видели такую хотя бы раз). Все GET запросы же перепосылаются браузерами без подтверждения.
Так же тут стоит упомянуть, что все данные запаковываются в body, который прикрепляется к запросу, что может помочь от утечек как в системе логирования на стороне сервера так и по пути к нему.
⚠ Сохраните ли вы в пароль при регистрации «как есть» в текстовом виде (plain text)?
Если да — это непрофессионально и даже преступно. Пароли должны быть зашифрованы стойким алгоритмом по ключу, идеально — если они хэшированы.
Хэширование — это преобразование данных без возможности их обратного восстановления. И кодирование Base64 тут не подойдёт, как почему-то решили некоторые ребята из исследования в начале статьи. В случае, если вашу базу выкрадут злоумышленники — у них окажутся все пароли ваших пользователей. В случае же шифрования или хэширования — им придётся изрядно дополнительно попотеть чтобы завладеть паролями даже с учётом слитых данных.
Данные статистики говорят о том, что 30-40% организаций хранят пароли в plain text. И в 2018-ом даже Twitter с его 330+млн пользователей смог. Ух.
Нужно ли проверять вводимые данные не только на UI стороне, но и на сервере? Является ли это ненужной/двойной работой?
Главное правило веб разработчика — не доверяй клиенту (браузеру). Все данные/поля могут быть отосланы/подменены другими инструментами, поэтому вам нужно обязательно делать полный цикл проверки на сервере.
Нужна ли валидация на сложность пароля или можно позволять пользователю иметь пароль любой сложности?
Заставлять пользователя придумать пароль в соответствии с правилами — безусловно здравая идея, т.к. слишком простые пароли легко подбираются путём перебора по словарю. Но тут важна грань, т.к. знаю реальный пример, когда правила пароля настолько сложные, что пользователи потом хранят эти пароли в текстовых файлах на рабочих столах, т.к. запомнить их нереально. А это еще хуже 🙂 Получается, заботились о безопасности, а по факту родили новый вектор атаки.

Хорошая статья про границы политики паролей есть на Хабре.
Middle level
Соединение защищено по https?
Если нет, то у меня для вас плохие новости — ваш логин и пароль может быть легко перехвачен по пути от вашего браузера к серверу. Это может быть как целенаправленная атака, так и “пассивный сниффинг данных“ публичными Wi-Fi, на уровне провайдера или даже на уровне уязвимого роутера, которым вы пользуетесь. Еще лет пять назад встречались сайты, где форма оплаты могла быть на https, а весь сайт — по http и в целом это работало безопасно (т.к. во время платежа на клиент не сохраняется никаких данных, которые впоследствии могут быть перехвачены по http каналу), однако сайты без https сегодня — это уже моветон и первый признак того, что такому сайту свои данные доверять нельзя.
Кстати, буквально на днях выпустил пост о «SSL/TLS/Асимметричном шифровании на пальцах» у себя на тг канале, поэтому если интересна тема — вэлкам!
На тему вопроса есть хороший ответ на стеке
⚠ Нужно ли при неудачной попытке регистрации писать, что такой пароль уже существует в системе?
Конечно нет. Вы должны выдавать МИНИМУМ информации всего, что касается безопасности. Есть шуточная версия в виде сообщения «такой пароль уже используется пользователем %username%». Шутки шутками, но возможно где-то и внедрили, за более чем тринадцать лет опыта работы и не такое встречал.
На aws к слову максимальный уровень — после ввода логина, сразу же запрашивают пароль + второй фактор (2fa), т.е. злоумышленник даже не сможет узнать какой именно параметр не подошёл.
Если попытка входа была неудачная — нужно ли логировать данные формы для дальнейшего изучения проблемы?
Нельзя. Максимум — это логин (а лучше хэш логина). Пароль — нельзя. Было уже множество громких инцидентов когда у крупных сервисов ломали системы логов и вытаскивали пароли, которые логировались в plain text (логировался весь реквест).
Если хэшировать в базе пароль, то как? Каким алгоритмом?
Хорошо, если мидл назовёт парочку, допустим md5 или sha-1/sha-256. Вопрос чем они плохи — уже скорее сеньорский левел, обсудим это ниже. Однако считаю, что для мидла и такой ответ пойдёт. Также плюсом для собеседующегося будет рассказ о том как именно нужно хэшировать — т.е. с солью. SALT — это что-то, что добавляется к паролю чтобы уменьшить риск его обратного преобразования в случае утечки данных. Многие популярные пароли уже находятся в базах (так называемые радужные таблицы или Rainbow tables) и хеши к ним подобраны, поэтому реверснуть какой-нибудь md5 без соли — дело несложное.
Вот тут хорошая статья с Хабра о хешировании паролей.
Senior level
Каким еще способом можно обезопасить форму? Что такое CSRF токен и имеет ли смысл его добавлять?
Это вопрос на самом деле в целом на понимание вида уязвимости CSRF (Cross-Site Request Forgery). Сеньор должен знать и понимать как это работает как со стороны имплементации, так и со стороны взламывающей стороны (хакера).
Безусловно, запрос должен посылаться с CSRF токеном. Это гарантирует отправку формы тем же клиентом, что её и отобразил.
update : Меня в комментариях резонно тыкнули в то, что уже лет пять проблема как фактически не актуальна, т.к. существует и поддерживается аттрибут SameSite. Резонно. Остаётся лишь вопрос к старым браузерам, где этой поддержки нет. Тут и тут есть описание проблемы и её решения.
update2 : Так же CSRF уязвимость не имеет силы при использовании JWT токенов, т.к. сам app ставит их при осуществлении запросов.
⚠ Что такое двухфакторная аутентификация (2fa) и нужна ли?
Вопрос для сеньора, однако в целом мидловский, т.к. 2fa уже прочно вошла в широкие массы и будет плохим признаком, если вы ответите в духе wtf.
Для серьёзных сервисов — 2fa is a must. Существенно снижает риск зайти в вашу учётку злоумышленником даже зная ваши логин и пароль. 2fa — это «второй фактор», обычно некий токен, который выдаётся через ваш телефон (приложение или же смс), но не ограничивается им. Могут быть и более безопасные варианты в виде usb флешек, блютус девайсов, биометрических сканеров и тп.
Как бы вы хранили пароль в базе? Хешированным? Солёным? Поподробнее пожалуйста!
Пароль должен быть хеширован, притом важен алгоритм. md5 и sha-1 — уже не подходят в текущих реалиях и помечены как небезопасные (RFC 6151,RFC5246). Используйте bcrypt, PBKDF2 или Argon2i, но не SHA-*. Семейство SHA-* не были задизайнены для хэширования паролей, они слишком быстрые. bcrypt/scrypt и PBKDF2 — медленные.
Если взять ПК с AMD R9 290X, то он сможет сгенерить 172 миллиарда md5/сек, 11 миллиардов SHA-256/сек, 797 миллиона SHA-512/сек и 1.3 миллиона PBKDF2 (8192 итераций и соль, 20 байт на выходе).
Что по соли — она может быть одна для всех пользователей, но в идеале — уникальная для каждого пользователя, т.е генерируется из данных самого пользователя. Плюсом будет, если упомянете тут о радужных таблицах.
Так же, кроме соли еще существует и понятие перца. Если соль может храниться рядом с данным пользователей, то перец должен храниться где-то в другом месте (об этом ниже).
⚠ Сможете ли сами написать алгоритм для хранения пароля?
Правильный ответ тут — не нужно изобретать криптовелосипеды, если вы не бородатый математик! Слишком просто допустить критическую ошибку в алгоритме и снизить стойкость. Хорошо, если вы сможете привести пример несложного шифрования через XOR или расскажете про AES256 и для чего нужен вектор инициализации (IV).
Ну и немного вопросов для Senior+
Что делать в случае коллизий паролей при хэшировании?
Коллизии существуют для большинства хеш-функций, но для «хороших» функций частота их возникновения близка к теоретическому минимуму. В целом — чем длиннее хэш, тем меньше шанс этой коллизии. Именно md5 и sha-1 не обеспечивают этой длины в сегодняшнем мире. Так же можно упомянуть про двойное хеширование и метод цепочек — внутрянку знать не обязательно, просто знать, что методы есть.
Если принято решение шифровать пароль, то какой тип шифрования выберете? Почему?
Вопрос на знание отличий симметричного vs асимметричного типа. Хорошо бы понимать разницу. Ответом тут на самом деле будет “it depends”, зависит от системы взаимодействия. Обычно, используют симметричное, т.к. асимметричное подразумевает валидацию второй стороной, которой скорее всего не будет. Плюс симметричное шифрование быстрее. Неплохо бы так же дополнить, что шифрование не замена хэшированию и должно применяться только в крайних случаях для кейсов подобно обсуждаемому нами.
Рекомендации по теме «Hashing vs Encryption» можно найти прямо в cheetsheets OWASP’a.
В связи с предыдущим вопросом — одинаков ли по стойкости 128-битный ключ для симметричного и асимметричного способа?
Нет, не одинаков. Природа асимметричного шифрования подразумевает, что ключ должен быть намного длиннее. Эквивалент 128 битного симметричного ключа равен 2048 асимметричного. Хорошо бы тут еще затронуть про плюсы и минусы каждого из подходов и паттерны применения (расскажите про SSL/TLS и как он устроен)
В дополнение — хороший ответ на qna Хабра тут.
Хорошая ли идея вынести аутентификацию на openid?
Безусловно да, если на сайте множество поддоменов и сервисов — это позволит упростить коммуникацию групп бэкендов/сервисов, а так же фрагментирует точки отказа в случае их возникновения. Т.е. если ляжет авторизация, все остальные узлы сервиса скорее всего будет работать, т.к. логически вынесены. Получится, что модуль аутентификации не размазан по множеству бэкендов. Плюс это более расширяемое решение и добавляет изолированности этой важной функции.
Если на аутентификацию будет один из векторов атаки — это не затронет остальные компоненты системы. К слову это частая точка для ддоса, т.к. обычно грамотная аутентификация построена на «долгих» алгоритмах и кушает и память/cpu, а значит может легко положить всю систему, если та гвоздями приколочена к основной группе сервисов. Хорошо бы еще упомянуть и про Oauth 2.0 — это второй по популярности протокол.
Что есть Rate Limiter и каким боком оно нам к форме аутентификации?
Это уже немного вне темы сабжа, но и уровень всё же со звёздочкой — добавим в тред 🙂 Rate Limit (или Throttling) — это механизм ограничения запросов по какому-то сценарию. В случае брутфорса или же ддоса — позволит отсекать вредных клиентов и не аффектать «хороших». Все большие ребята следят за этим. Часто авторизацию прячут (как и весь сайт) за тем же Cloudflare, который берёт на себя эту функцию. Rate Limiter может быть и кастомным и быть реализован на уровне самого приложения. К примеру в виде “не принимать больше n запросов per period от такого-то пользователя с такой-то ролью”. Есть такие опции и у тех же Lambda functions от AWS, там прямо в интерфейсе можно прописать нужные цифры.
Тут можно найти общая инфу и про алгоритмы. Ну и классная статья от Яндекса об их пути рейт лимитера.
Где хранить соль и как её формировать?
Обычно, соль хранится где-то рядом с паролем. Однако в случае, если злоумышленник сможет слить базу — соль станет ему доступна. Поэтому к соли еще хорошо бы добавлять либо какую-то константу, которой нет в бд, либо проводить данные через какой-то “black-box” алгоритм, который так же не будет известен хакеру — этим вы ещё больше снижаете риск взлома хэшей, т.к. соль будет храниться в двух местах. Усложняя — можно (и нужно) комбинировать эти два метода и переменную брать, допустим из переменных окружения.
Так же важно добавлять соль после пароля, а не до. Причиной этому является одно из свойств хэширования — поточное преобразование. В случае, если соль добавлется перед паролем — злоумышленник скорее всего сможет найти соль, учесть её и вычислять уже хэши без учёта этой самой соли.
Дополнительные материалы
1) Годные советы по функционалу окна аутентификации
Выводы
Как мы видим — одно и то же задание может быть сделано как джуниором, так и высококлассным специалистом и результаты будут весьма разные. Позволю себе вставить пять копеек к статье об исследовании в начале сабжа : глупо ожидать серьёзного уровня реализации за 200 евро (а уж тем более за 100) на фрилансе, однако и хранить в Base64 определенно точно не стоит, если вас попросили обеспечить безопасность паролей. Нужно объяснять заказчику тезис трёх основополагающих свойств результата работы : Быстро/Качественно/Дёшево — выбрать можно только два из них.
Пожалуй, это основные моменты, которые пришли на ум. Если что-то пропустил или где-то не прав, вэлкам в комментарии 😉
ps. Если вы так же любите айтишечку как и я — буду рад видеть вас на своём Telegram канале.
- аутентификация
- собеседование вопросы
- computer science
- криптография
- разработка web-сайтов
