Автентикация

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() и след това се показва приятно съобщение, потвърждаващо успешния изход.

Резюме

Имаме връзка за вход и изход на потребителя. За удостоверяване използвахме вградения автентикатор и данните за вход са в конфигурационния файл, тъй като това е просто тестово приложение. Също така защитихме формите за редактиране, така че само влезли потребители могат да добавят и редактират публикации.

Тук можете да прочетете повече за влизане на потребители и Проверка на правата.