Preverjanje pristnosti

Nette vam ponuja smernice za programiranje avtentikacije na vaši strani, vendar vas ne sili, da to storite na kakršen koli poseben način. Izvedba je odvisna od vas. Nette ima vmesnik Nette\Security\Authenticator, ki vas prisili, da implementirate le eno metodo, imenovano authenticate, ki poišče uporabnika, kakorkoli želite.

Obstaja veliko načinov, kako se lahko uporabnik avtentificira. Najpogostejši način je avtentikacija na podlagi gesla (uporabnik navede svoje ime ali e-pošto in geslo), vendar obstajajo tudi drugi načini. Morda poznate gumbe “Prijava s Facebookom” na številnih spletnih mestih ali prijavo prek Googla/Twitterja/GitHuba ali katerega koli drugega spletnega mesta. Z Nette lahko uporabite kateri koli način avtentikacije, ki ga želite, lahko pa jih tudi kombinirate. Vse je odvisno od vas.

Običajno bi napisali svoj avtentifikator, vendar bomo za ta preprost mali blog uporabili vgrajeni avtentifikator, ki avtentificira na podlagi gesla in uporabniškega imena, shranjenega v konfiguracijski datoteki. To je dobro za namene testiranja. Zato bomo v konfiguracijsko datoteko config/common.neon dodali naslednji razdelek security:

security:
	users:
		admin: secret  # uporabnik 'admin', geslo 'secret'

Nette bo samodejno ustvaril storitev v vsebniku DI.

Obrazec za prijavo

Zdaj imamo pripravljen zaledni del avtentikacije, zagotoviti pa moramo uporabniški vmesnik, prek katerega se bo uporabnik prijavil. Ustvarimo nov predstavnik z imenom SignPresenter, ki bo

  • prikazal obrazec za prijavo (ki bo zahteval uporabniško ime in geslo)
  • preveril pristnost uporabnika, ko bo obrazec oddan
  • zagotovil dejanje odjave

Začnimo z obrazcem za prijavo. Že veste, kako delujejo obrazci v predstavitvenem programu. Ustvarite SignPresenter in metodo createComponentSignInForm. Izgledati morata takole:

<?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', '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;
	}
}

Na voljo je vnos za uporabniško ime in geslo.

Predloga

Obrazec bo prikazan v predlogi in.latte

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

{control signInForm}

Obvladovalnik prijave

Dodamo tudi obdelovalnik obrazca za prijavo uporabnika, ki se sproži takoj po oddaji obrazca.

Obvladovalnik bo samo prevzel uporabniško ime in geslo, ki ju je vnesel uporabnik, ter ju posredoval prej opredeljenemu avtentifikatorju. Ko se uporabnik prijavi, ga bomo preusmerili na domačo stran.

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.');
	}
}

Metoda User::login() mora zavreči izjemo, če se uporabniško ime ali geslo ne ujemata s tistima, ki smo ju opredelili prej. Kot že vemo, bi to povzročilo rdeči zaslon Tracy ali, v produkcijskem načinu, sporočilo, ki obvešča o notranji napaki strežnika. To nam ne bi bilo všeč. Zato ujamemo izjemo in v obrazec dodamo lepo in prijazno sporočilo o napaki.

Ko se v obrazcu pojavi napaka, se stran z obrazcem ponovno prikaže, nad obrazcem pa je lepo sporočilo, ki uporabnika obvesti, da je vnesel napačno uporabniško ime ali geslo.

Varnost predstavnikov

Zagotovili bomo obrazec za dodajanje in urejanje objav. Opredeljen je v predstavniku EditPresenter. Cilj je preprečiti dostop do strani neprijavljenim uporabnikom.

Ustvarimo metodo startup(), ki se zažene takoj na začetku življenjskega cikla predstavnika. Ta uporabnike, ki niso prijavljeni, preusmeri na obrazec za prijavo.

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

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

Uporabnik brez avtentikacije ne more več videti strani ustvari in revidiraj, še vedno pa lahko vidi povezave, ki kažejo nanju. Skrijmo tudi te. Ena takšnih povezav je na naslovu app/UI/Home/default.latte, vidna pa mora biti le, če je uporabnik prijavljen.

Skrijemo jo lahko z uporabo n:atributa, imenovanega n:if. Če je izjava v njem false, se celotna <a> oznaka in njena vsebina ne bosta prikazani:

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

To je bližnjica za (ne zamenjujte s tag-if):

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

Na podoben način morate skriti povezavo za urejanje, ki se nahaja na naslovu app/UI/Post/show.latte.

Hej, ampak kako pridemo do prijavne strani? Ni nobene povezave, ki bi kazala nanjo. Dodajmo jo v datoteko predloge @layout.latte. Poskusi najti lepo mesto, lahko je kjerkoli, kjer ti je najbolj všeč.

...
<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>
...

Če uporabnik še ni prijavljen, bomo prikazali povezavo “Prijavite se”. V nasprotnem primeru bomo prikazali povezavo “Odjavi se”. To dejanje dodamo v SignPresenter.

Akcija odjave je videti takole, in ker uporabnika takoj preusmerimo, ne potrebujemo predloge za prikaz.

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

Samo pokliče metodo logout() in nato uporabniku prikaže lepo sporočilo.

Povzetek

Imamo povezavo za prijavo in tudi za odjavo uporabnika. Za preverjanje pristnosti smo uporabili vgrajeni avtentifikator, podatki za prijavo pa so v konfiguracijski datoteki, saj gre za preprosto testno aplikacijo. Zavarovali smo tudi obrazce za urejanje, tako da lahko samo prijavljeni uporabniki dodajajo in urejajo objave.

Tukaj lahko preberete več o prijavi in avtorizaciji uporabnikov.