Autentifikace uživatele

Rudolf Svátek 2016-06-28 09:34

Redakční systém by měl umožnit provádění změn v administraci pouze přihlášeným a ověřeným uživatelům. První věc, kterou tedy provedeme, je vložení uživatele. Použijeme k tomu nástroje, které nám nabízí Nette. Přihlašování uživatele bude probíhat zadáním jeho loginu a hesla.

Celou aplikaci, tak jak vypadá po dnešní kapitole, si můžeš stáhnout: kapitola3.zip. Dneškem počínaje ale budu nabízet ke stažení jen složku app. V ostatních složkách projektu se vlastně nic nemění a je zbytečné to stahovat celé.

V souboru app\model\UserManager.php je authenticator, který umí přihlásit uživatele, nebo ho i vytvořit. Verze ze sandboxu pracuje s Nettedatabase, ale já preferuji Dibi. Můj Authenticator proto vypadá po menších úpravách takto:

<?php

namespace App\Model;

use Nette;
use Nette\Security\Passwords;
use Dibi\Connection;
use Nette\Security\AuthenticationException;
use Nette\Security\IAuthenticator;
use Nette\Security\Identity;

class UserManager extends Nette\Object implements IAuthenticator
{

	const
		TABLE_NAME = 'users',
		COLUMN_ID = 'id',
		COLUMN_NAME = 'username',
		COLUMN_PASSWORD_HASH = 'password',
		COLUMN_ROLE = 'role';

	/** @var Connection */
	private $db;

	public function __construct(Connection $database) {
		$this->db = $database;
	}

	/**
	 * Performs an authentication.
	 * @param array $credentials
	 * @return Identity
	 * @throws AuthenticationException
	 */
	public function authenticate(array $credentials) {
		list($username, $password) = $credentials;
		$row = $this->db->select('*')
			->from(self::TABLE_NAME)
			->where(self::COLUMN_NAME . ' = %s', $username)
			->fetch();

		if (!$row) {
			throw new AuthenticationException('The username is incorrect.', self::IDENTITY_NOT_FOUND);
		} elseif (!Passwords::verify($password, $row[self::COLUMN_PASSWORD_HASH])) {
			throw new AuthenticationException('The password is incorrect.', self::INVALID_CREDENTIAL);
		} elseif (Passwords::needsRehash($row[self::COLUMN_PASSWORD_HASH])) {
			$row[self::COLUMN_PASSWORD_HASH] = Passwords::hash($password);
		}

		$arr = $row->toArray();
		unset($arr[self::COLUMN_PASSWORD_HASH]);
		return new Identity($row[self::COLUMN_ID], $row[self::COLUMN_ROLE], $arr);
	}

	/**
	 * Adds new user.
	 * @param  string
	 * @param  string
	 * @param  string
	 * @return void
	 * @throws DuplicateNameException
	 */
	public function add($username, $password, $role = 'guest') {
		try {
			$this->db->insert(self::TABLE_NAME, [
				self::COLUMN_NAME => $username,
				self::COLUMN_PASSWORD_HASH => Passwords::hash($password),
				self::COLUMN_ROLE => $role,
			])->execute();
		} catch (Nette\Database\UniqueConstraintViolationException $e) {
			throw new DuplicateNameException;
		}
	}

}

class DuplicateNameException extends \Exception{}

Všimni si Namespace a názvu třídy. Dohromady to dá App\Model\UserManager. Otevři soubor config.neon a najdi sekci services. Tam vidíš řádek - App\Model\UserManager. Authenticator se tedy stal službou, kterou lze volat kdekoli v aplikaci. 

Kromě metody authenticate, která ověřuje přihlášení uživatelů, je v authenticatoru ještě metoda add, která umí uživatele přidat. Funkci musíme nějak zavolat a k tomu poslouží malý skript, který vytvoř ve složce app\bin\create-user.php. On už tedy v sandboxu existuje, ale neumí pracovat s rolemi uživatelů. Trochu ho tedy uprav:

<?php

if (!isset($_SERVER['argv'][3])) {
	echo '
Add new user to database.

Usage: create-user.php <name> <password> <role>
';
	exit(1);
}

list(, $name, $password, $role) = $_SERVER['argv'];

$container = require __DIR__ . '/../app/bootstrap.php';
$manager = $container->getByType('App\Model\UserManager');

try {
	$manager->add($name, $password, $role);
	echo "User $name was added.\n";
} catch (App\Model\DuplicateNameException $e) {
	echo "Error: duplicate name.\n";
	exit(1);
}

Teď už jen stačí spustit příkazový řádek a zadat:

cd redakcni-system\bin
php create-user.php root password admin

Argument root a password si můžeš změnit jak chceš. Jde o uživatelské jméno a heslo. Argument admin je role uživatele a tu bys v tomto případě měnit neměl. Mrkni do databázové tabulky users a měl bys vidět jeden záznam.

Autorizace uživatele

Zatímco autentizace má na starosti určit, jestli je přihlášený uživatel opravdu tím, za koho se vydává, tak autorizace zjišťuje, jestli má potřebná práva k různým činnostem. Například administrátor může provádět změny v administraci, ale obyčejnému uživateli to dovolit nechceme.

Nette má přichystánu techniku ACL. Tačí si definovat uživatelské role, zdroje, se kterými můžeme pracovat a operace, které se zdroji plánujeme provádět. Takový jednoduchý autorizator mi leží v souboru app\model\Authorizator.php a vypadá takto:

<?php

namespace App\Model;

use Nette\Object;
use Nette\Security as NS;

/**
 * Users Authorizator.
 */
class MyAuthorizator extends Object implements NS\IAuthorizator 
{

	/**
	 * @var NS\Permission
	 */
	private $acl;

	public function __construct() {
		$this->acl = new NS\Permission();
		$this->acl->addRole('guest');
		$this->acl->addRole('user', 'registered');
		$this->acl->addRole('admin', 'user');

		$this->acl->addResource('backend');
		$this->acl->addResource('users');

		$this->acl->allow('user', array('backend'), array('view'));
		$this->acl->allow('admin');
	}

	function isAllowed($role, $resource, $privilege) {
		return $this->acl->isAllowed($role, $resource, $privilege);
	}

}

Vidíš 3 různé role a 2 zdroje. Guest nemůže nic, jen koukat normálně na web. User může kromě toho v administraci prohlížet záznamy kromě záznamů o uživatelích. No a admin může všechno. Je to jen uázka. Klidně popřemýšlej a nastavi si práva podle sebe. Přiznám se, že já zatím v RS::RS řeším jen roli admin. Ale chystám se na to.

V presenterech se pak můžeš ptát, jestli je nějaký uživatel v požadované roli, nebo má povolenou akci se zdrojem.

Přihlášení uživatele

Máme tabulku uživatelů, umíme ověřit uživatele a přidělit mu oprávnění, tak ještě mu dovolíme se přihlásit. K tomu je zapotřebí vyrobit, zobrazit a po odeslání zpracovat přihlašovací formulář.

Třída app\forms\FormFactory.php bude zodpovědná za vznik objektu Form. Budeme ji volat při jakémkoli vzniku nějakého formuláře. Prvním takovým formulářem bude přihlašovací formulář. Bude jednoduchý - přihlašovací jméno, heslo a možnost neodhlašovat uživatele. Sandbox už má přihlašování vyřešeno v app\forms\SignInFormFactory.php

Budeme potřebovat ještě presenter a šablonu. Presentery často mají spousty společných metod, které řeší stejnou věc. Třeba vykreslení menu, nebo ankety. Proto vznikne nějaký BasePresenter, který tyto společné věci bude dělat a ostatní presentery od něj dědí. V sandboxu už je v souboru app\FrontModule\presenters\BasePresenter.php.

Takže přihlašování, registraci a odhlašování zařídí presenter app\FrontModule\presenters\SignPresenter.php.Vidíš, že dědí právě od BasePresenteru V presenteru bude nakonec 5 metod - pro přihlášení, registraci, odhlášení, žádost o obnovu hesla a samotná obnova hesla. Pro přihlášení je teď vše připraveno a můžeš do prohlížeče zadat:

http://localhost:8000/sign/in

Měl by se zobrazit formulář pro přihlášení. Zadej tedy jako Username "root" a jako Password "password". Samozřejmě, pokud jsi použil jiné údaje při vytváření uživatele, použij je. Po odeslání jsi přihlášen.

Prozatím to poznáš jen tak, že uvidíš informaci v Tracy. To je ten ladící nástroj vpravo dole:

Informace o přihlášeném uživateli

Registrace uživatele

Opět potřebujeme pár věcí - službu v configu, vykreslení registračního formuláře, akci presenteru, zpracování odeslaného formuláře. V sanboxu už je všechno připraveno. Jen v souboru SignUpFactory.php proveď pár změn, aby soubor vypadal takto:

<?php
 
namespace App\Forms;
 
use Nette;
use Nette\Application\UI\Form;
use App\Model;
 
class SignUpFormFactory 
{
 
    const PASSWORD_MIN_LENGTH = 7;
 
    /** @var FormFactory */
    private $factory;
 
    /** @var Model\UserManager */
    private $userManager;
 
    public function __construct(FormFactory $factory, Model\UserManager $userManager) {
        $this->factory = $factory;
        $this->userManager = $userManager;
    }
 
    /**
     * @return Form
     */
    public function create(callable $onSuccess) {
        $form = $this->factory->create();
        $form->addText('username', 'Pick a username:')
                ->setRequired('Please pick a username.');
 
        $form->addPassword('password', 'Create a password:')
                ->setOption('description', sprintf('at least %d characters', self::PASSWORD_MIN_LENGTH))
                ->setRequired('Please create a password.')
                ->addRule($form::MIN_LENGTH, NULL, self::PASSWORD_MIN_LENGTH);
 
        $form->addSubmit('send', 'Sign up');
 
        $form->onSuccess[] = function (Form $form, $values) use ($onSuccess) {
            try {
                $this->userManager->add($values->username, $values->password, 'guest');
            } catch (Model\DuplicateNameException $e) {
                $form->addError('Username is already taken.');
                return;
            }
            $onSuccess();
        };
        return $form;
    }
 
}

Pro možnost registrace stačí zadat adresu:

http://localhost:8000/sign/up

Jakmile vyplníš a odešleš formulář, do databáze uživatelů se přidá další záznam (tedy pokud nezadáš už použitý username).

Odhlášení uživatele

Jelikož odhlašování nevyžaduje žádný formulář či další akci uživatele, jde zařídit jednoduchým voláním akce v SignPresenter.php. Pro odhlášení zadej do prohlížeče adresu:

http://localhost:8000/sign/out

Uživatelé také občas řeší zapomenuté heslo. To je ale celkem závažná problematika i z hlediska bezpečnosti. Budu se tomu tedy věnovat v nějaké další kapitole úplně zvlášť.

Celou aplikaci, tak jak vypadá po dnešní kapitole, si můžeš stáhnout: kapitola3.zip

Příště si připravíme databázový model, abychom mohli pracovat s daty.

Příště si zkusíme vložit nějakého uživatele, který bude mít práva administrátora.

Redakční systém RS::RS Předchozí kapitola

Instalace Nette Framework

Redakční systém RS::RS Celý seriál

Vývoj redakčního systému v PHP

Redakční systém RS::RS Následující kapitola

Databázový model

Komentáře (0)

Přidej svůj komentář

O mně

Jmenuji se Rudolf Svátek. Jsem lektor výpočetní techniky a PHP programátor. Stavím firemní stránky a eshopy. Aby se mi to dělalo pohodlně, vytvořil jsem redakční systém RS::RS, který ti tu nabízím k použití.

Rychlý kontakt na mně

  • Rudolf Svátek
  • Telefon:
    +420 777 828 353
  • Email:
  • Adresa:
    Josefa Hory 1097/5
    736 01 Havířov
    ČR



Tyto stránky používají Cookies. Používáním stránek s tím souhlasíte Další informace