Authentifizierung

Nette stellt Ihnen Richtlinien zur Verfügung, wie Sie die Authentifizierung auf Ihrer Seite programmieren können, zwingt Sie aber nicht dazu, es auf eine bestimmte Weise zu tun. Die Implementierung ist Ihnen überlassen. Nette hat eine Schnittstelle Nette\Security\Authenticator, die Sie zwingt, nur eine einzige Methode namens authenticate zu implementieren, die den Benutzer auf beliebige Weise findet.

Es gibt viele Möglichkeiten, wie sich ein Benutzer authentifizieren kann. Die gebräuchlichste Methode ist die passwortbasierte Authentifizierung (der Benutzer gibt seinen Namen oder seine E-Mail-Adresse und ein Passwort an), aber es gibt auch andere Möglichkeiten. Vielleicht kennen Sie die “Login mit Facebook”-Schaltflächen auf vielen Websites, oder Sie melden sich über Google/Twitter/GitHub oder eine andere Website an. Mit Nette können Sie jede beliebige Authentifizierungsmethode verwenden oder sie miteinander kombinieren. Es liegt ganz bei Ihnen.

Normalerweise würden Sie Ihren eigenen Authentifikator schreiben, aber für diesen einfachen kleinen Blog werden wir den eingebauten Authentifikator verwenden, der sich anhand eines Passworts und eines Benutzernamens authentifiziert, die in einer Konfigurationsdatei gespeichert sind. Das ist gut für Testzwecke. Wir fügen also den folgenden Sicherheits-Abschnitt in die config/common.neon Konfigurationsdatei ein:

security:
	users:
		admin: secret  # user 'admin', password 'secret'

Nette wird automatisch einen Dienst im DI-Container erstellen.

Anmeldeformular

Wir haben nun den Backend-Teil der Authentifizierung fertig und müssen eine Benutzeroberfläche bereitstellen, über die sich der Benutzer anmelden kann. Lassen Sie uns einen neuen Präsentator namens SignPresenter erstellen, der

  • ein Anmeldeformular anzeigt (und nach Benutzername und Passwort fragt)
  • den Benutzer authentifiziert, wenn das Formular abgeschickt wird
  • eine Abmeldefunktion bereitstellt

Lassen Sie uns mit dem Anmeldeformular beginnen. Sie wissen bereits, wie Formulare in einem Presenter funktionieren. Erstellen Sie die SignPresenter und die Methode createComponentSignInForm. Es sollte wie folgt aussehen:

<?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', 'Username:')
			->setRequired('Please enter your username.');

		$form->addPassword('password', 'Password:')
			->setRequired('Please enter your password.');

		$form->addSubmit('send', 'Sign in');

		$form->onSuccess[] = $this->signInFormSucceeded(...);
		return $form;
	}
}

Es gibt eine Eingabe für Benutzernamen und Passwort.

Vorlage

Das Formular wird in der Vorlage gerendert in.latte

{block content}
<h1 n:block=title>Sign in</h1>

{control signInForm}

Login-Handler

Wir fügen auch einen Form Handler für die Anmeldung des Benutzers hinzu, der direkt nach dem Absenden des Formulars aufgerufen wird.

Der Handler nimmt einfach den Benutzernamen und das Passwort, die der Benutzer eingegeben hat, und übergibt sie an den zuvor definierten Authentifikator. Nachdem sich der Benutzer angemeldet hat, leiten wir ihn auf die Homepage um.

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('Incorrect username or password.');
	}
}

Die Methode User::login() sollte eine Ausnahme auslösen, wenn der Benutzername oder das Passwort nicht mit den zuvor definierten Werten übereinstimmt. Wie wir bereits wissen, würde dies zu einem roten Tracy-Bildschirm führen, oder, im Produktionsmodus, zu einer Meldung über einen internen Serverfehler. Das würde uns nicht gefallen. Deshalb fangen wir die Ausnahme ab und fügen eine nette und freundliche Fehlermeldung in das Formular ein.

Wenn der Fehler im Formular auftritt, wird die Seite mit dem Formular erneut gerendert, und über dem Formular erscheint eine nette Meldung, die den Benutzer darüber informiert, dass er einen falschen Benutzernamen oder ein falsches Passwort eingegeben hat.

Sicherheit der Präsentatoren

Wir werden ein Formular zum Hinzufügen und Bearbeiten von Beiträgen sichern. Es ist im Präsentator EditPresenter definiert. Ziel ist es, zu verhindern, dass nicht angemeldete Benutzer auf die Seite zugreifen.

Wir erstellen eine Methode startup(), die sofort zu Beginn des Lebenszyklus des Präsentators gestartet wird. Diese leitet nicht eingeloggte Benutzer zum Anmeldeformular um.

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

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

Ein nicht authentifizierter Benutzer kann weder die Seite Erstellen noch die Seite Bearbeiten sehen, aber er kann immer noch die Links sehen, die auf sie verweisen. Diese sollten wir ebenfalls ausblenden. Ein solcher Link befindet sich in app/Presenters/templates/Home/default.latte, und er sollte nur sichtbar sein, wenn der Benutzer angemeldet ist.

Wir können ihn mit einem n:Attribut namens n:if ausblenden. Wenn die darin enthaltene Anweisung false lautet, werden der gesamte <a> Tag und sein Inhalt werden nicht angezeigt:

<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a>

Dies ist eine Abkürzung für (nicht zu verwechseln mit tag-if):

{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if}

Sie sollten den Bearbeitungslink unter app/Presenters/templates/Post/show.latte auf ähnliche Weise ausblenden.

Hey, aber wie kommen wir auf die Anmeldeseite? Es gibt keinen Link, der auf sie verweist. Fügen wir einen in die @layout.latte Vorlagendatei ein. Versuchen Sie, einen schönen Platz zu finden, es kann überall sein, wo es Ihnen am besten gefällt.

...
<ul class="navig">
	<li><a n:href="Home:">Home</a></li>
	{if $user->isLoggedIn()}
		<li><a n:href="Sign:out">Sign out</a></li>
	{else}
		<li><a n:href="Sign:in">Sign in</a></li>
	{/if}
</ul>
...

Wenn der Benutzer noch nicht eingeloggt ist, wird der Link “Anmelden” angezeigt. Andernfalls zeigen wir den Link “Abmelden” an. Wir fügen diese Aktion in SignPresenter hinzu.

Die Abmeldeaktion sieht wie folgt aus, und da wir den Benutzer sofort umleiten, ist keine Ansichtsvorlage erforderlich.

public function actionOut(): void
{
	$this->getUser()->logout();
	$this->flashMessage('You have been signed out.');
	$this->redirect('Home:');
}

Sie ruft einfach die Methode logout() auf und zeigt dann eine nette Nachricht an den Benutzer an.

Zusammenfassung

Wir haben einen Link zum Anmelden und zum Abmelden des Benutzers. Da es sich um eine einfache Testanwendung handelt, haben wir zur Authentifizierung den eingebauten Authentifikator verwendet und die Anmeldedaten in der Konfigurationsdatei hinterlegt. Wir haben auch die Bearbeitungsformulare gesichert, so dass nur angemeldete Benutzer Beiträge hinzufügen und bearbeiten können.

Hier können Sie mehr über Benutzeranmeldung und Autorisierung lesen.