Автентифікація
Nette надає спосіб програмування автентифікації на наших сторінках,
але ні до чого нас не змушує. Реалізація залежить тільки від нас. Nette
містить інтерфейс Nette\Security\Authenticator
, який вимагає лише одного
методу authenticate
, що перевіряє користувача будь-яким способом,
який ми захочемо.
Існує багато способів перевірки користувача. Найпоширеніший спосіб – за допомогою пароля (користувач надає своє ім'я або електронну пошту та пароль), але є й інші способи. Можливо, ви знаєте кнопки типу “Увійти через Facebook” або вхід через Google/Twitter/GitHub на деяких сайтах. З Nette ми можемо мати будь-який метод входу, або навіть комбінувати їх. Це залежить тільки від нас.
Зазвичай ми б написали власний автентифікатор, але для цього
простого маленького блогу ми використаємо вбудований автентифікатор,
який здійснює вхід на основі пароля та імені користувача, збережених у
конфігураційному файлі. Він підходить для тестових цілей. Додамо
наступну секцію security до конфігураційного файлу
config/common.neon
:
security:
users:
admin: secret # користувач 'admin', пароль 'secret'
Nette автоматично створить сервіс у DI-контейнері.
Форма входу
Тепер у нас готова автентифікація, і нам потрібно підготувати користувацький інтерфейс для входу. Створимо новий presenter з назвою SignPresenter, який:
- відобразить форму входу (з логіном та паролем)
- після відправки форми перевірить користувача
- надасть можливість виходу
Почнемо з форми входу. Ми вже знаємо, як працюють форми в presenter'ах.
Створимо presenter 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() викине виняток, якщо ім'я користувача та пароль не відповідають даним у конфігураційному файлі. Як ми вже знаємо, це може призвести до червоної сторінки помилки або, в робочому режимі, до повідомлення про помилку сервера. Цього ми не хочемо. Тому перехопимо цей виняток і передамо гарне, зрозуміле користувачеві повідомлення про помилку до форми.
Як тільки у формі виникне помилка, сторінка з формою перемалюється, і над формою з'явиться гарне повідомлення, що інформує користувача про те, що він ввів неправильне ім'я користувача або пароль.
Захист presenter'ів
Захистимо форму для додавання та редагування дописів. Вона визначена
в presenter'і EditPresenter
. Мета – заборонити доступ до сторінки
користувачам, які не увійшли в систему.
Створимо метод startup()
, який запускається одразу на початку життєвого циклу
presenter'а. Він перенаправить неавторизованих користувачів на
форму входу.
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()
, а потім відображається гарне
повідомлення, що підтверджує успішний вихід.
Підсумок
У нас є посилання для входу та виходу користувача. Для перевірки ми використали вбудований автентифікатор, а дані для входу зберігаються в конфігураційному файлі, оскільки це простий тестовий додаток. Також ми захистили форми редагування, тому додавати та редагувати дописи можуть лише авторизовані користувачі.
Тут ви можете прочитати більше про вхід користувачів та авторизацію.