Перейти к содержимому

Как проверить залогинен ли пользователь

  • автор:

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

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

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

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

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

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

— Как выводить для неавторизованных пользователей страницу логина, а для остальных — самого сервиса?
— Как сделать форму авторизации и написать клиентский код?
— Можно ли передавать логин и пароль по сети в открытом виде?
— Какое будет серверное 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 с таким содержимым

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

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

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

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

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

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

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

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

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

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

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

Мы подключили модуль авторизации и вывели шаблон в зависимости от результата функции 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

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

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

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

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

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

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

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

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

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, где у нас будет вся работа с авторизацией.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Именно эта новая строка и «логинит» пользователя. В сессию мы запишем 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_id(), вот так

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

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

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

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

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

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

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

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

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

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

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

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 после подключения всех файлов, но перед проверкой роутера на валидность

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

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

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

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

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

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

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

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

Опа, открылся 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 у меня было так

То есть, в первую очередь веб-сервер должен искать 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 , то есть вот так

Да, еще в 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, где это может быть. А вот здесь

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

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

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

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

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

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

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

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

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

Руководство Django Часть 8: Аутентификация и авторизация пользователя

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

Требования: Завершить изучение предыдущих тем руководства, включая Руководство Django Часть 7: Работа с сессиями.
Цель: Понимать как настроить и использовать механизм аутентификации пользователя и разграничений прав доступа.

Обзор

Django предоставляет систему аутентификации и авторизации («permission») пользователя, реализованную на основе фреймворка работы с сессиями, который мы рассматривали в предыдущей части. Система аутентификации и авторизации позволяет вам проверять учётные данные пользователей и определять какие действия какой пользователь может выполнять. Данный фреймворк включает в себя встроенные модели для Пользователей и Групп (основной способ применения прав доступа для более чем одного пользователя), непосредственно саму систему прав доступа (permissions)/флаги, которые определяют может ли пользователь выполнить задачу, с какой формой и отображением для авторизованных пользователей, а так же получить доступ к контенту с ограниченным доступом.

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

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

Система аутентификации является очень гибкой и позволяет вам формировать свои собственные URL-адреса, формы, отображения, а также шаблоны страниц, если вы пожелаете, с нуля, через простой вызов функций соответствующего API для авторизации пользователя. Тем не менее, в данной статье мы будем использовать «встроенные» в Django методы отображений и форм аутентификации, а также методы построения страниц входа и выхода. Нам все ещё необходимо создавать шаблоны страниц, но это будет достаточно несложно.

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

Подключение аутентификации

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

Примечание: Необходимые настройки были выполнены для нас, когда мы создали приложение при помощи команды django-admin startproject . Таблицы базы данных для пользователей и модели авторизации были созданы, когда в первый раз выполнили команду python manage.py migrate .

Соответствующие настройки сделаны в параметрах INSTALLED_APPS и MIDDLEWARE файла проекта (locallibrary/locallibrary/settings.py), как показано ниже:

Создание пользователей и групп

Вы уже создали своего первого пользователя когда мы рассматривали Административная панель сайта Django в части 4 (это был суперпользователь, созданный при помощи команды python manage.py createsuperuser ). Наш суперпользователь уже авторизован и имеет все необходимые уровни доступа к данным и функциям, таким образом нам необходимо создать тестового пользователя для отработки соответствующей работы сайта. В качестве наиболее быстрого способа, мы будем использовать административную панель сайта для создания соответствующих групп и аккаунтов locallibrary.

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

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

Запустите сервер разработки и перейдите к административной панели вашего сайта ( http://127.0.0.1:8000/admin/ ). Залогиньтесь на сайте при помощи параметров (имя пользователя и пароля) аккаунта суперпользователя. Самая «верхняя» страница панели Администратора показывает все наши модели. Для того, чтобы увидеть записи в разделе Authentication and Authorisation вы можете нажать на ссылку Users, или Groups.

Admin site - add groups or users

В первую очередь, в качестве нового члена нашего сайта, давайте создадим новую группу.

Admin site - add group

  1. Нажмите на кнопку Add(Добавить) (рядом с Group) и создайте новую группу; для данной группы введите Name (Имя) «Library Members».
  2. Для данной группы не нужны какие-либо разрешения, поэтому мы просто нажимаем кнопку SAVE (Сохранить) (вы перейдёте к списку групп).

Теперь давайте создадим пользователя:

  1. Перейдите обратно на домашнюю страницу административной панели
  2. Для перехода к диалогу добавления пользователя нажмите на кнопку Add, соответствующую строке Users (Пользователи). Admin site - add user pt1
  3. Введите соответствующие Username (имя пользователя) и Password/Password confirmation (пароль/подтверждение пароля) для вашего тестового пользователя
  4. Нажмите SAVE для завершения процесса создания пользователя. Административная часть сайта создаст нового пользователя и немедленно перенаправит вас на страницу Change user (Изменение параметров пользователя) где вы можете, соответственно, изменить ваш username, а кроме того добавить информацию для дополнительных полей модели User. Эти поля включают в себя имя пользователя, фамилию, адрес электронной почты, статус пользователя, а также соответствующие параметры доступа (может быть установлен только флаг Active). Ниже вы можете определить группу для пользователя и необходимые параметры доступа, а кроме того, вы можете увидеть важные даты, относящиеся к пользователю (дату подключения к сайту и дату последнего входа). Admin site - add user pt2
  5. В разделе Groups, из списка Доступные группы выберите группу Library Member, а затем переместите её в блок «Выбранные группы» (нажмите стрелку-«направо», находящуюся между блоками). Admin site - add user to group
  6. Больше нам не нужно здесь нечего делать, просто нажмите «Save»(Сохранить), и вы вернётесь к списку созданных пользователей.

Вот и все! Теперь у вас есть учётная запись «обычного члена библиотеки», которую вы сможете использовать для тестирования (как только добавим страницы, чтобы пользователи могли войти в систему).

Примечание: Попробуйте создать другого пользователя, например «Библиотекаря». Так же создайте группу «Библиотекарей» и добавьте туда своего только что созданного библиотекаря

Настройка представлений проверки

Django предоставляет почти все, что нужно для создания страниц аутентификации входа, выхода из системы и управления паролями из коробки. Это включает в себя url-адреса, представления (views) и формы,но не включает шаблоны — мы должны создать свой собственный шаблон!

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

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

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

Проектирование URLs

Добавьте следующее в нижней части проекта urls.py файл (locallibrary/locallibrary/urls.py) файл:

Перейдите по http://127.0.0.1:8000/accounts/ URL (обратите внимание на косую черту!), Django покажет ошибку, что он не смог найти этот URL, и перечислить все URL, которые он пытался открыть. Из этого вы можете увидеть URL-адреса, которые будут работать, например:

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

Примечание:

Теперь попробуйте перейти к URL-адресу входа ( http://127.0.0.1:8000/accounts/login/ ). Это приведёт к сбою снова, но с ошибкой, сообщающей вам, что нам не хватает требуемого шаблона (registration / login.html) в пути поиска шаблона. Вы увидите следующие строки, перечисленные в жёлтом разделе вверху:

Следующий шаг — создать каталог регистрации в пути поиска, а затем добавить файл login.html.

Каталог шаблонов

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

Для этого сайта мы разместим наши HTML-страницы в каталоге templates / registration /. Этот каталог должен находиться в корневом каталоге проекта, то есть в том же каталоге, что и в каталоге и папках locallibrary). Создайте эти папки сейчас.

Примечание: ваша структура папок теперь должна выглядеть как показано внизу: locallibrary (django project folder) |_catalog |_locallibrary |_templates (new) |_registration

Чтобы сделать эти директории видимыми для загрузчика шаблонов (т. е. помещать этот каталог в путь поиска шаблона) откройте настройки проекта (/locallibrary/locallibrary/settings.py), и обновите в секции TEMPLATES строку ‘DIRS’ как показано.

Шаблон аутентификации

Предупреждение: Важно: Шаблоны аутентификации, представленные в этой статье, являются очень простой / слегка изменённой версией шаблонов логина демонстрации Django. Возможно, вам придётся настроить их для собственного использования!

Создайте новый HTML файл, названный /locallibrary/templates/registration/login.html. дайте ему следующее содержание:

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

Перейдите на страницу входа ( http://127.0.0.1:8000/accounts/login/ ) когда вы сохраните свой шаблон, и вы должны увидеть что-то наподобие этого:

Library login page v1

Если ваша попытка войти в систему будет успешной, вы будете перенаправлены на другую страницу (по умолчанию это будет http://127.0.0.1:8000/accounts/profile/ ). Проблема здесь в том, что по умолчанию Django ожидает, что после входа в систему вы захотите перейти на страницу профиля, что может быть или не быть. Поскольку вы ещё не определили эту страницу, вы получите ещё одну ошибку!

Откройте настройки проекта (/locallibrary/locallibrary/settings.py) и добавьте текст ниже. Теперь, когда вы входите в систему, вы по умолчанию должны перенаправляться на домашнюю страницу сайта.

Шаблон выхода

Если вы перейдёте по URL-адресу выхода ( http://127.0.0.1:8000/accounts/logout/ ), то увидите странное поведение — ваш пользователь наверняка выйдет из системы, но вы попадёте на страницу выхода администратора. Это не то, что вам нужно, хотя бы потому, что ссылка для входа на этой странице приведёт вас к экрану входа в систему администратора. (и это доступно только для пользователей, у которых есть разрешение is_staff ).

Создайте и откройте /locallibrary/templates/registration/logged_out.html. Скопируйте текст ниже:

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

Шаблон сброса пароля

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

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

Форма сброса пароля

Это форма, используемая для получения адреса электронной почты пользователя (для отправки пароля для сброса пароля). Создайте /locallibrary/templates/registration/password_reset_form.html и дайте ему следующее содержание:

Сброс пароля

Эта форма отображается после того, как ваш адрес электронной почты будет собран. Создайте /locallibrary/templates/registration/password_reset_done.html, и дайте ему следующее содержание:

Сброс пароля по email

Этот шаблон предоставляет текст электронной почты HTML, содержащий ссылку на сброс, которую мы отправим пользователям. Создайте /locallibrary/templates/registration/password_reset_email.html и дайте ему следующее содержание:

Подтверждение на сброс пароля

На этой странице вы вводите новый пароль после нажатия ссылки в электронном письме с возвратом пароля. Создайте /locallibrary/templates/registration/password_reset_confirm.html и дайте ему следующее содержание:

Сброс пароля завершён

Это последний шаблон сброса пароля, который отображается, чтобы уведомить вас о завершении сброса пароля. Создайте /locallibrary/templates/registration/password_reset_complete.html и дайте ему следующее содержание:

Тестирование новых страниц аутентификации

Теперь, когда вы добавили конфигурацию URL и создали все эти шаблоны, теперь страницы аутентификации должны работать! Вы можете протестировать новые страницы аутентификации, попытавшись войти в систему, а затем выйдите из учётной записи суперпользователя, используя эти URL-адреса:

  • http://127.0.0.1:8000/accounts/login/
  • http://127.0.0.1:8000/accounts/logout/

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

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

Для получения дополнительной информации см. Отправка email (Django docs).

Тестирование проверки подлинности пользователей

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

Тестирование в шаблонах

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

Обычно вы сначала проверяете переменную шаблона <>, чтобы определить, имеет ли пользователь право видеть конкретный контент. Чтобы продемонстрировать это, мы обновим нашу боковую панель, чтобы отобразить ссылку «Вход», если пользователь вышел из системы, и ссылку «Выход», если он вошёл в систему.

Откройте базовый шаблон (/locallibrary/catalog/templates/base_generic.html) и скопируйте следующий текст в sidebar блок непосредственно перед тегом шаблона endblock.

Как вы можете видеть, мы используем теги шаблона if-else-endif для условного отображения текста на основе того, является ли <> истинным. Если пользователь аутентифицирован, мы знаем, что у нас есть действительный пользователь, поэтому мы вызываем <>, чтобы отобразить их имя.

Мы создаём URL-адрес для входа и выхода из системы, используя тег шаблона URL-адреса и имена соответствующих конфигураций URLs. Также обратите внимание на то, как мы добавили ?next=<> в конец URLs. Это означает, что следующий URL-адрес содержит адрес (URL) текущей страницы, в конце связанного URL-адреса. После того, как пользователь успешно выполнил вход в систему, представления будут использовать значение » next » чтобы перенаправить пользователя обратно на страницу, где они сначала нажали ссылку входа / выхода из системы.

Примечание: Попробуйте! Если вы находитесь на главной странице и вы нажимаете «Вход / Выход» на боковой панели, то после завершения операции вы должны вернуться на ту же страницу.

Тестирование в представлениях

Если вы используете функциональные представления, самым простым способом ограничить доступ к вашим функциям является применение login_required декоратор к вашей функции просмотра, как показано ниже. Если пользователь вошёл в систему, ваш код просмотра будет выполняться как обычно. Если пользователь не вошёл в систему, это перенаправит URL-адрес входа, определённый в настройках проекта. ( settings.LOGIN_URL ), передав текущий абсолютный путь в качестве next параметра URL. Если пользователю удастся войти в систему, они будут возвращены на эту страницу, но на этот раз аутентифицированы.

Примечание: Вы можете сделать то же самое вручную, путём тестирования request.user.is_authenticated , но декоратор намного удобнее!

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

Это имеет такое же поведение при переадресации, что и login_required декоратор. Вы также можете указать альтернативное местоположение для перенаправления пользователя, если он не аутентифицирован ( login_url ), и имя параметра URL вместо » next » , чтобы вставить текущий абсолютный путь ( redirect_field_name ).

Для получения дополнительной информации ознакомьтесь с Django docs here.

Пример — перечисление книг текущего пользователя

Теперь, когда мы знаем, как ограничить страницу определённому пользователю, создайте представление о книгах, которые заимствовал текущий пользователь.

К сожалению, у нас пока нет возможности пользователям использовать книги! Поэтому, прежде чем мы сможем создать список книг, мы сначала расширим BookInstance модель для поддержки концепции заимствования и использования приложения Django Admin для заимствования ряда книг нашему тестовому пользователю.

Модели

Прежде всего, мы должны предоставить пользователям возможность кредита на BookInstance (у нас уже есть status и due_back дата, но у нас пока нет связи между этой моделью и пользователем. Мы создадим его с помощью поля ForeignKey (один ко многим). Нам также нужен простой механизм для проверки того, просрочена ли заёмная книга.

Откройте catalog/models.py, и импортируйте модель User из django.contrib.auth.models (добавьте это чуть ниже предыдущей строки импорта в верхней части файла, так User доступен для последующего кода, что позволяет использовать его):

Затем добавьте поле borrower в модель BookInstance :

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

Теперь добавьте следующее определение свойства внутри класса BookInstance:

Примечание: Сначала мы проверим, является ли due_back пустым, прежде чем проводить сравнение. Пустое поле due_back заставило Django выкидывать ошибку, а не показывать страницу: пустые значения не сопоставимы. Это не то, что мы хотели бы, чтобы наши пользователи испытывали!

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

Admin

Теперь откройте каталог catalog/admin.py, и добавьте поле borrower в класс BookInstanceAdmin , как в list_display , так и в полях fieldsets , как показано ниже. Это сделает поле видимым в разделе Admin, так что мы можем при необходимости назначить User в BookInstance .

Займите несколько книг

Теперь, когда возможно кредитовать книги конкретному пользователю, зайдите и заработайте на нескольких записей в BookInstance . Установите borrowed поле вашему тестовому пользователю, сделайте status «В займе» и установите сроки оплаты как в будущем, так и в прошлом.

Примечание: Мы не будем описывать процесс, так как вы уже знаете, как использовать Admin сайт!

Займ в представлении

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

Добавьте следующее в catalog/views.py:

Чтобы ограничить наш запрос только объектами BookInstance для текущего пользователя, мы повторно реализуем get_queryset() , как показано выше. Обратите внимание, что «o» это сохранённый код для «on loan» и мы сортируем по дате due_back , чтобы сначала отображались самые старые элементы.

URL-адрес для заёмных книг

Теперь откройте /catalog/urls.py и добавьте url() , указывая на приведённое выше представление (вы можете просто скопировать текст ниже в конец файла).

Шаблон для заёмных книг

Теперь все, что нам нужно сделать для этой страницы, — это добавить шаблон. Сначала создайте файл шаблона /catalog/templates/catalog/bookinstance_list_borrowed_user.html и дайте ему следующее содержание:

Этот шаблон очень похож на тот, который мы создали ранее для объектов Book и Author . Единственное, что «новое» здесь, это то, что мы проверяем метод, который мы добавили в модель (bookinst.is_overdue ) с целью использовать его для изменения цвета просроченных предметов.

Когда сервер разработки запущен, вы должны теперь иметь возможность просматривать список для зарегистрированного пользователя в своём браузере по адресу http://127.0.0.1:8000/catalog/mybooks/ . Попробуйте это, когда ваш пользователь войдёт в систему и выйдет из системы (во втором случае вы должны быть перенаправлены на страницу входа в систему).

Добавить список на боковую панель

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

Откройте базовый шаблон (/locallibrary/catalog/templates/base_generic.html) и добавьте выделенную строку из sidebar, как показано на рисунке.

На что это похоже?

Когда любой пользователь войдёт в систему, он будет видеть ссылку «Мной позаимствовано (My Borrowed)» в боковой колонке, и список книг, показанных ниже (первая книга не имеет установленной даты, что является ошибкой, которую мы надеемся исправить в более позднем уроке!).

Library - borrowed books by user

Права доступа

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

Модели

Определение разрешений выполняется в разделе моделей » class Meta » , используется permissions поле. Вы можете указать столько разрешений, сколько необходимо в кортеже, причём каждое разрешение определяется во вложенном кортеже, содержащем имя разрешения и отображаемое значение разрешения. Например, мы можем определить разрешение, позволяющее пользователю отметить, что книга была возвращена, как показано здесь:

Затем мы могли бы назначить разрешение группе «Библиотекарь» (Librarian) на сайте администратора.

Откройте catalog/models.py, и добавьте разрешение, как показано выше. Вам нужно будет повторно выполнить миграцию (вызвав python3 manage.py makemigrations и python3 manage.py migrate ) для надлежащего обновления базы данных.

Шаблоны

Разрешения текущего пользователя хранятся в переменной шаблона, называемой << perms >> . Вы можете проверить, имеет ли текущий пользователь определённое разрешение, используя конкретное имя переменной в соответствующем приложении «Django» — например, << perms.catalog.can_mark_returned >> будет True если у пользователя есть это разрешение, а False — в противном случае. Обычно мы проверяем разрешение с использованием шаблона <% if %>, как показано в:

Представления

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

Функция в представлении с декоратором:

Требуется разрешение mixin для представлений на основе классов.

Пример

Мы не будем обновлять LocalLibrary здесь; возможно, в следующем уроке!

Испытайте себя

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

Вы должны следовать той же схеме, что и для другого представления. Главное отличие состоит в том, что вам нужно ограничить представление только библиотекарями. Вы можете сделать это на основе того, является ли пользователь сотрудником (декоратор функции: staff_member_required , переменная шаблона: user.is_staff ) но мы рекомендуем вам вместо этого использовать can_mark_returned разрешения и PermissionRequiredMixin , как описано в предыдущем разделе.

Предупреждение: Важно: Не забудьте использовать вашего суперпользователя для тестирования на основе разрешений (проверки разрешений всегда возвращают true для суперпользователей, даже если разрешение ещё не определено!). Вместо этого создайте пользователя-библиотекаря и добавьте необходимые возможности.

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

All borrowed books, restricted to librarian

Подводим итоги

Отличная работа — теперь вы создали веб-сайт, на котором участники библиотеки могут входить в систему и просматривать собственный контент, и библиотекари (с правом доступа) могут просматривать все заёмные книги с их читателями. На данный момент мы все ещё просто просматриваем контент, но те же принципы и методы используются, когда вы хотите начать изменять и добавлять данные.

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

Как проверить, залогинен ли пользователь на другом сайте?

У меня есть два отдельных приложения (в PHP) — A и B с разными доменами. В приложении A я хочу проверить, вошел ли пользователь (прямо сейчас) в приложение B. Ajax сделает эту работу, но CORS: / Curl также не будет работать. Есть другие варианты?

Решение

На двух серверах должно быть какое-то общее хранилище зарегистрированных пользователей.

Если два веб-сайта находятся в разных бизнес-доменах (например, Google и Amazon), вам необходимо создать API-интерфейсы на обоих веб-сайтах, чтобы проверить, вошел ли один и тот же пользователь на другом веб-сайте.
Еще одна вещь, которую следует рассмотреть здесь, это то, что именно определяет вашего пользователя. Один сайт может использовать электронную почту, другие могут также использовать имя пользователя, токен oauth (google / facebook / github / etc .. auth), а другие могут использовать номер телефона (особенно в азиатских странах). Поэтому это может стать довольно сложной задачей, чтобы отслеживать все.

Если веб-сайты разделяют бизнес-домен (например, Gmail и Google Drive, ИЛИ мобильный и веб-сайт), у вас будет единая система входа для всех веб-сайтов и запросите ее для пользовательских данных (не забудьте проверить, совпадают ли файлы cookie !!)

Есть еще много деталей для обсуждения, но это должно дать вам основную идею. Ура!

Как проверить залогинен ли пользователь

Проверьте зарегистрированного пользователя в Django

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

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

Проверьте вошедшего пользователя в представлениях в Django

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

См. Следующий код

Мы можем использовать request.user.is_authenticated , чтобы проверить, вошел ли пользователь в систему или нет. Если пользователь авторизован, он вернет True . В противном случае вернет False .

Проверьте зарегистрированного пользователя в шаблонах в Django

Как и в представлениях, мы также можем использовать request внутри шаблонов для проверки авторизованного пользователя. Синтаксис точно такой же. В шаблонах мы будем использовать теги шаблонов Django для создания оператора if-else .

Vaibhav is an artificial intelligence and cloud computing stan. He likes to build end-to-end full-stack web and mobile applications. Besides computer science and technology, he loves playing cricket and badminton, going on bike rides, and doodling.

Как проверить авторизирован ли пользователь? Django

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

0xdb's user avatar

Site design / logo © 2022 Stack Exchange Inc; user contributions licensed under CC BY-SA . rev 2022.12.9.43105

Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

Как проверить, авторизован ли пользователь на сайте в CMS 1с-Битрикс?

Проблемы, связанные с проверкой авторизован пользователь в 1-с битрикс или нет решаются следующим кодом:

Функция CUser::IsAuthorized() проверяет авторизован ли посетитель сайта, если да, то возвращает true, иначе false. Может принимать аргумент — ID пользователя.

Решение задачи 1 будет следующим:

Задача 2: показать текст только для авторизованных пользователей на сайте в CMS 1с-Битрикс.

Решение второй задачи будет следующим:

Задача 3: если пользователь не авторизован, показывать ему рекламу, если авторизован, то без рекламы.
Задача 4: показать блок конкретному пользователю.

Показываем для Васи(Его ID — 21), специальное предложение. Будем использовать функцию CUser::GetID(), которая возвращает ID авторизованного пользователя.

Задача 5: показать блок пользователям группы администратор.

Очень полезная функция для разработчиков, если нужно вывести содержимое массива на рабочем сайте: CUser::IsAdmin(). Возвращает true, если вы принадлежите группе админов, иначе false.

Как проверить авторизован ли пользователь в приложении на Vue?

Делаю приложение и столкнулся с следующей задачей:
Скажем есть пункт меню «Профиль»
есть два представления данной страницы:

LoginPage — страница авторизации(регистрации)
ProfilePage — страница авторизованного пользователя с его данными

Как проверить авторизован ли пользователь в приложении или нет?
и в зависимости от ответа выдавать то или иное представление.
подозреваю что это нужно делать средством beforeEnter в path: ‘/profile’, component: ProfileViewsPage

  • Вопрос задан более трёх лет назад
  • 4480 просмотров
  • Facebook
  • Вконтакте
  • Twitter

kulakoff

Мне кажется у вас не совсем верный подход, что если у вас будет множество путей, которые требуют авторизации? Один из вариантов добавить в роутер информацию требует данная страница авторизации или нет:

Информация о том, что авторизован пользователь или нет должна сохраняться например в localStorage.

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

  • Facebook
  • Вконтакте
  • Twitter

nickola105

Сделал вот такую реализацию:
на странице профиля

на странице логин

не совсем понял зачем в этой реализации

все работает вроде без нее
Вроде все работает, но просьба к вам оценить профессионально

kulakoff

Так прокатит, если у вас только страница, которая для захода требует прав доступа, иначе в каждой придется писать вот это кусок:

Нужно использовать глобальный хук beforeRouteEnter, и в нем каждый маршрут смотреть можно ли на него зайти без авторизации/с авторизацией

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

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

Luxury Car Rental in Miami | Reserve a Premium Vehicle with RealCar