Аутентификация
Nette даёт способ реализовать аутентификацию на наших сайтах, но не
навязывает какого-то конкретного подхода. Реализация целиком на нас. В
Nette есть интерфейс Nette\Security\Authenticator, который требует всего одного
метода, authenticate, чтобы проверить пользователя любым выбранным
нами способом.
Способов аутентифицировать пользователя много. Самый распространённый – аутентификация по паролю (когда пользователь вводит своё имя или почту и пароль), но есть и другие. Вам, возможно, знакомы кнопки “Войти через Facebook” или вход через Google, Twitter, GitHub на некоторых сайтах. С Nette мы можем использовать любой способ входа или даже сочетать их. Решение целиком за нами.
Обычно мы бы написали собственный аутентификатор. Однако для этого
простого блога мы воспользуемся встроенным аутентификатором, который
выполняет вход по имени пользователя и паролю, хранящимся в
конфигурационном файле. Для целей проверки это подходит. Добавим в
конфигурационный файл config/common.neon такую секцию security:
security:
users:
admin: secret # пользователь 'admin', пароль 'secret'
Nette автоматически создаст для этого аутентификатора сервис в DI-контейнере.
Форма входа
Теперь, когда аутентификация настроена, нам нужно создать
пользовательский интерфейс для входа. Создадим новый презентер с
именем SignPresenter, который будет:
- показывать форму входа (с полями имени пользователя и пароля)
- аутентифицировать пользователя при отправке формы
- давать возможность выйти
Начнём с формы входа. Мы уже знаем, как работают формы в презентерах.
Создадим SignPresenter и напишем метод createComponentSignInForm.
Выглядеть он должен примерно так:
<?php
namespace App\Presentation\Sign;
use Nette;
use Nette\Application\UI\Form;
final class SignPresenter extends Nette\Application\UI\Presenter
{
protected function createComponentSignInForm(): Form
{
$form = new Form;
$form->addText('username', 'Имя пользователя:')
->setRequired('Введите, пожалуйста, имя пользователя.');
$form->addPassword('password', 'Пароль:')
->setRequired('Введите, пожалуйста, пароль.');
$form->addSubmit('send', 'Войти');
$form->onSuccess[] = $this->signInFormSucceeded(...);
return $form;
}
}
Здесь есть поля для имени пользователя и пароля.
Шаблон
Форма будет отрисована в шаблоне in.latte:
{block content}
<h1 n:block=title>Вход</h1>
{control signInForm}
Callback входа
Дальше добавим метод-callback для входа пользователя, который будет вызван сразу после успешной отправки формы.
Callback просто берёт имя пользователя и пароль, введённые пользователем, и передаёт их аутентификатору. После успешного входа мы перенаправляем на главную страницу.
private function signInFormSucceeded(Form $form, \stdClass $data): void
{
try {
$this->getUser()->login($data->username, $data->password);
$this->redirect('Home:');
} catch (Nette\Security\AuthenticationException $e) {
$form->addError('Неверное имя пользователя или пароль.');
}
}
Метод User::login() выбрасывает исключение, если имя пользователя и пароль не совпадают с учётными данными в конфигурационном файле. Как мы знаем, это привело бы к красному экрану Tracy или, в продакшн-режиме, к сообщению об ошибке сервера. Мы этого не хотим. Поэтому мы перехватываем исключение и добавляем в форму приятное, понятное пользователю сообщение об ошибке.
Когда в форме возникает ошибка, страница с формой отрисовывается заново, и над формой появляется сообщение, говорящее пользователю, что он ввёл неверное имя пользователя или пароль.
Защита презентеров
Мы защитим форму добавления и редактирования записей, определённую в
EditPresenter. Цель – не дать пользователям, которые не вошли, попасть
на эту страницу.
Создадим метод startup(), который выполняется сразу в начале жизненного цикла
презентера. Этот метод перенаправит невошедших пользователей на
форму входа.
public function startup(): void
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
Скрытие ссылок
Неавторизованный пользователь больше не видит страниц create и
edit, но по-прежнему видит ссылки, ведущие на них. Их нам тоже стоит
скрыть. Одна такая ссылка находится в шаблоне
app/Presentation/Home/default.latte и должна быть видна только вошедшим
пользователям.
Скрыть её можно с помощью n:атрибута n:if. Если условие равно
false, весь тег <a> вместе с содержимым отображён
не будет.
<a n:href="Edit:create" n:if="$user->isLoggedIn()">Создать запись</a>
что является сокращением такой записи (не путайте её с tag-if):
{if $user->isLoggedIn()}<a n:href="Edit:create">Создать запись</a>{/if}
Точно так же скройте ссылку на редактирование в шаблоне
app/Presentation/Post/show.latte.
Ссылка на вход
Как нам вообще попасть на страницу входа? Ссылки, ведущей на неё, нет.
Добавим её в шаблон @layout.latte. Попробуйте найти подходящее место,
оно может быть почти где угодно.
...
<ul class="navig">
<li><a n:href="Home:">Главная</a></li>
{if $user->isLoggedIn()}
<li><a n:href="Sign:out">Выйти</a></li>
{else}
<li><a n:href="Sign:in">Войти</a></li>
{/if}
</ul>
...
Если пользователь не вошёл, отображается ссылка “Войти”. Иначе
показывается ссылка “Выйти”. Это действие мы тоже добавим в
SignPresenter.
Поскольку сразу после выхода мы пользователя перенаправляем, шаблон не нужен. Действие выхода выглядит так:
public function actionOut(): void
{
$this->getUser()->logout();
$this->flashMessage('Вы вышли из системы.');
$this->redirect('Home:');
}
Оно просто вызывает метод logout() и затем показывает
пользователю подтверждающее сообщение.
Итоги
У нас есть ссылки для входа и выхода пользователя. Для аутентификации мы использовали встроенный аутентификатор, а учётные данные для входа лежат в конфигурационном файле, потому что это простое тестовое приложение. Мы также защитили формы редактирования, чтобы добавлять и редактировать записи могли только вошедшие пользователи.
Подробнее о входе пользователя и авторизации можно почитать здесь.