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

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

Тепер у нас є введення імені користувача та пароля.

Шаблон

Форма відображатиметься в шаблоні app/UI/Sign/in.latte.

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

{control signInForm}

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

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

Обробник просто приймає ім'я користувача та пароль, які користувач ввів, і передає їх аутентифікатору. Після входу в систему ми перенаправимо вас на головну сторінку:

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 або, у виробничому режимі, повідомлення про внутрішню помилку сервера. Але ми цього не хочемо. Тому ми перехоплюємо виняток і додаємо у форму гарне і доброзичливе повідомлення про помилку.

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

Безпека презентерів

Убезпечимо форму для додавання та редагування постів. Мета – запобігти доступу до сторінки неавторизованих користувачів.

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

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

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

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

Гей, але як нам потрапити на сторінку входу в систему? Немає посилання, що вказує на неї. Давайте додамо його у файл шаблону app/UI/@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() і потім показує користувачеві гарне повідомлення.

Підіб'ємо підсумок

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

Тут ви можете прочитати більше про вхід користувача та авторизація.