Аутентификация

Nette предоставляет способ программирования аутентификации на нашем сайте, но не заставляет нас ничего делать. Реализация зависит от нас. Nette включает интерфейс Nette\Security\Authenticator, который требует только один метод authenticate для аутентификации пользователя, как мы хотим.

Существует множество способов, с помощью которых пользователь может аутентифицировать себя. Наиболее распространенным является аутентификация на основе пароля (пользователь указывает свое имя или электронную почту и пароль), но существуют и другие способы. Возможно, вы видели кнопки типа «Войти с помощью Facebook/Google/Twitter/GitHub и т. д.» на многих сайтах. С Nette мы можем использовать любой метод входа в систему, а также комбинировать их. Это зависит от нас.

Обычно мы пишем собственный аутентификатор, но для этого простого блога мы воспользуемся встроенным аутентификатором, который осуществляет вход на основе пароля и имени пользователя, хранящихся в конфигурационном файле. Это хорошо подходит для тестирования. Добавьте секцию security в конфигурационный файл config/common.neon:

security:
	users:
		admin: secret  # логин 'admin', пароль 'secret'

Nette автоматически создаст службу в контейнере DI.

Форма регистрации

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

  • отображать форму входа в систему (запрашивается имя пользователя и пароль)
  • аутентифицировать пользователя при отправке формы
  • обеспечивать выход из системы

Давайте начнем с формы входа в систему. Вы уже знаете, как работают формы в презентере. Создайте SignPresenter и метод createComponentSignInForm. Это должно выглядеть следующим образом:

<?php
namespace App\Presenters;

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;
	}
}

Теперь у нас есть ввод имени пользователя и пароля.

Шаблон

Форма будет отображаться в шаблоне app/Presenters/templates/Sign/in.latte.

{block content}
<h1 n:block=title>Войти</h1>

{control signInForm}

Обработчик входа в систему

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

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

public function signInFormSucceeded(Form $form, \stdClass $data): void
{
	try {
		$this->getUser()->login($data->username, $data->password);
		$this->redirect('Homepage:');

	} catch (Nette\Security\AuthenticationException $e) {
		$form->addError('Неправильные логин или пароль.');
	}
}

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

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

Безопасность презентеров

Обезопасим форму для добавления и редактирования постов. Цель — предотвратить доступ к странице неавторизованным пользователям.

Создадим метод startup(), который запускается сразу в начале жизненного цикла презентера. Это перенаправляет незарегистрированных пользователей на форму входа в систему.

public function startup(): void
{
	parent::startup();

	if (!$this->getUser()->isLoggedIn()) {
		$this->redirect('Sign:in');
	}
}

Неавторизованный пользователь больше не может видеть страницы создания и редактирования, но он всё ещё может видеть ссылки, указывающие на них. Давайте спрячем и их. Одна из таких ссылок находится в app/Presenters/templates/Homepage/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/Presenters/templates/Post/show.latte.

Эй, но как нам попасть на страницу входа в систему? Нет ссылки, указывающей на нее. Давайте добавим её в файл шаблона app/Presenters/templates/@layout.latte. Попробуйте найти хорошее место, это может быть любое место, которое вам больше всего нравится.

...
<ul class="navig">
	<li><a n:href="Homepage:">Главная</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('Homepage:');
}

Он просто вызывает метод logout() и затем показывает пользователю красивое сообщение.

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

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