Аутентификация
Nette предоставляет способ программирования аутентификации на наших
страницах, но ни к чему нас не принуждает. Реализация зависит только от
нас. Nette содержит интерфейс Nette\Security\Authenticator
, который требует
только одного метода authenticate
, который проверяет пользователя
любым способом, каким мы захотим.
Существует много возможностей, как пользователь может быть аутентифицирован. Самый частый способ аутентификации — с помощью пароля (пользователь предоставляет свое имя или e-mail и пароль), но есть и другие способы. Возможно, вы знакомы с кнопками типа “Войти через Facebook” или входом через Google/Twitter/GitHub на некоторых сайтах. С Nette мы можем иметь любой метод входа, или мы можем их комбинировать. Это зависит только от нас.
Обычно мы бы написали собственный аутентификатор, но для этого
простого небольшого блога мы используем встроенный аутентификатор,
который входит в систему на основе пароля и имени пользователя,
сохраненных в конфигурационном файле. Он подходит для тестовых целей.
Добавим следующую секцию security
в конфигурационный файл
config/common.neon
:
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() выбросит исключение, если имя пользователя и пароль не совпадают с данными в конфигурационном файле. Как мы уже знаем, это может привести к красной странице ошибки или, в производственном режиме, к сообщению об ошибке сервера. Этого мы не хотим. Поэтому мы перехватим это исключение и передадим красивое, дружественное к пользователю сообщение об ошибке в форму.
Как только в форме произойдет ошибка, страница с формой перерисуется, и над формой отобразится красивое сообщение, информирующее пользователя о том, что он ввел неправильное имя пользователя или пароль.
Защита презентеров
Защитим форму для добавления и редактирования постов. Она определена
в презентере 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()
, а затем отображается красивое
сообщение, подтверждающее успешный выход.
Итог
У нас есть ссылка для входа и также для выхода пользователя. Для проверки мы использовали встроенный аутентификатор, а данные для входа хранятся в конфигурационном файле, так как это простое тестовое приложение. Мы также защитили формы редактирования, так что добавлять и редактировать посты могут только вошедшие пользователи.
Здесь вы можете прочитать больше о входе пользователей и проверке прав.