spájame
slovenskú
IT komunitu
pridaj sa
Registrácia · Login

Matúš Koprda 10.1.2011, článok je súčasťou seriálu Ako sa stať PHP ninja s Yii Frameworkom
Hodnoť článok:
1 0

Ako sa stať PHP ninja s Yii Frameworkom (3)

V tejto časti tutoriálu si povieme ako na autentifikáciu a autorizáciu používateľov v Yii, čiže ich prihlásenie, prístup na rôzne časti stránky podľa právomocí a všetko s tým súvisiace.

Presnejšie povedané - autentifikácia je overenie identity používateľa, čo sa zvyčajne deje tak, že do prihlasovacieho formulára zadá svoje meno a heslo. K autentifikácií patrí tiež "zapamätanie si" prihlásenia (cez SESSION), aby používateľ nemusel sústavne dokazovať, že to je naozaj on a pri každej stránke zadávať svoje prihlasovacie údaje. :p

Autorizácia určuje oprávnenia používateľov robiť s určitými funkciami aplikácie - napríklad moderátor fóra môže mazať cudzie príspevky a zamykať témy kým bežný používateľ môže mazať iba vlastné témy a neprihlásený používateľ si môže témy nanajvýš pozerať. Pri autorizácií sa často stretáme s "rolami" používateľov, ktoré reprezentujú skupiny používateľov s určitými oprávneniami.

Zase raz budeme vychádzať z kódu predchádzajúceho návodu, v ktorom spravíme pár (well.. asi 6) drobných úprav a získame plne funkčné prihlasovanie a overovanie userov.

Ako funguje prihlasovanie v Yii

Celá téma auth. je v oficiálnych zdrojoch Yii zdokumentovaná dosť chabo a roztrúsene, tak sa ju pokúsim v tomto návode zhrnúť a ušetriť vás od pár hodín googlenia a študovania zdrojákov. :)

Na overenie identity sa používa trieda UserIdentity, z ktorej je zaujímavá viac menej iba jedna metóda na overenie mena a hesla. Po úspešnom prihlásení sú potom údaje o používateľovi dostupné kedykoľvek cez Yii::app()->user, čo je vlastne inštancia triedy CWebUser. Obe tieto triedy spravíme vlastné aby využívali údaje z databázy.

Vykreslenie a spracovanie login formulára zabezpečuje SiteController a triedy s ním spojené - ciže model LoginForm a view site/login.php. Proces prihlasovania vyzerá potom približne takto:

$identity = new UserIdentity($meno, $heslo);
$identity->authenticate();

if($identity->errorCode === UserIdentity::ERROR_NONE)
{
	$dlzka = $zostatPrihlaseny ? 3600*24*30 : 0;
	Yii::app()->user->login($identity, $dlzka);
	return true;
}
else
	return false;

UserIdentity

V súbore protected/components/UserIdentity.php sa skrýva trieda UserIdentity, prítomná v každej novo vygenerovanej aplikácii od začiatku. Táto trieda obsahuje metódy:
- authenticate(), ktorá má za úlohu overiť správnosť mena a hesla
- getId() a getName() - tie by mali vracať ID a meno používateľa. Pozor, v základnej implementácii vracajú obidve meno!
- a metódy na nastavovanie "perzistentných stavov" a zistenie či bol používateľ overený.

"Perzistentné stavy" sú jednoducho dáta, ktoré budú o používateľovi dostupné kým bude prihlásený - inými slovami to, čo sa má zapísať do session-u.
Čo teda potrebujeme spraviť je, že modifikujeme metódu authenticate() aby ťahala údaje z databázy (namiesto pôvodnej, ktorá prihlási všetkých s údajmi admin/admin a demo/demo) a aby ako ID vracala skutočné IDčko používateľa, nie jeho meno.

class UserIdentity extends CUserIdentity {

private $_id;
const ERROR_STATUS_BANNED = 4;

public function getId()
{
	return $this->_id;
}
Prepíšeme metódu na získanie IDčka tak, že bude vracať ID používateľa. Tiež pridávame nový chybový stav ERROR_STATUS_BANNED, keďže používatelia evidovaní v našej DB môžu byť aj zablokovaní. Číslo 4 som vybral zrovna preto, že v CUserIdentity je ako posledný definovaný stav s číslom 2, tak aby sme mali rezervu, keby sa do Yii náhodou doplnili stav č.3.. ehm. :-)
public function authenticate() { $u = User::model()->findByAttributes( array('email'=>$this->username) );
Prihlasovanie cez email je praktickejšie (email sa ťažšie zabúda:) takže ho budeme používať namiesto username/nicku. Ak nenájdeme používateľa s daným emailom, v $u bude namiesto modelu hodnota NULL.
if ( !$u ) $this->errorCode = self::ERROR_USERNAME_INVALID; else if( !$u->matchesPassword($this->password) ) $this->errorCode = self::ERROR_PASSWORD_INVALID;
Metódu matchesPassword(), ktorá zistí, či heslo zodpovedá hashu hesla z databázy, za chvíľu do modelu doplníme.
else if( $u->status == 'blocked') $this->errorCode = self::ERROR_STATUS_BANNED; else { $this->_id = $user->model->id; $this->username = $user->model->name;
Premennú $this->username už nastavil konštruktor, akurát že momentálne je v nej email (emailom sa prihlasujeme), tým pádom hodnotu zmeníme na meno používateľa. V prípade, že chceme k userovi na tomto mieste priradiť perzistentné údaje, dá sa to metódou setState() - napríklad $this->setState('role', $user->model->role->name);. Môžeme ich však nastaviť aj hocikedy neskôr.. alebo vôbec.
$this->errorCode = self::ERROR_NONE; } return !$this->errorCode; } }

User Model

Do modelu (protected/models/User.php) teraz dopíšeme jednoduchú metódu, ktorá vracia zhodu hesla s hashom. Potrebujeme ju kvôli predchádzajúcej triede UserIdentity.

public function matchesPassword($password)
{
	return ($this->getHash($password) == $this->_oldPassword);
}
getHash() sme definovali minule, je to chránená metóda a vracia hash hesla.

WebUser

WebUser (protected/components/WebUser.php) je špeciálna trieda, ktorej inštancia je globálne dostupná v aplikácii cez Yii::App()->user. V podstate by nám stačila aj jej základná implementácia, ale pre zvýšenie užitočnosti budeme chcieť jednoducho pristupovať k ďalším údajom používateľa, takže do nej vložíme aj jeho model. Ten sa hneď využije v metóde checkAccess() na zisťovanie, či má používateľ určitú rolu. Vysvetlíme si tiež čo je lazy loading a ako je riešený v Yii.

class WebUser extends CWebUser {

protected $_model = null;

public function getModel()
{
	if (!$this->_model)
	{
		if ($this->id) $this->_model = User::model()->findByPk($this->id);
		else $this->_model = User::model();
	}

	return $this->_model;
}
Pokiaľ nie je načítaný model, loadne ho a vráti. V $this->id je vždy ID prihláseného používateľa, ktoré keď chýba, metóda vracia prázdny model.
public function checkAccess($operation, $params = array(), $allowCaching = true) { return ($this->model->role->name == $operation); }
Na tomto mieste sa prejavuje asi najväčšia podivnosť, čo som zatiaľ našiel v Yii. Pri obmedzení akcie v controlleri na určité role (ako si ukážeme ďalej) sa volá práve táto metóda, avšak nesprávnym spôsobom, kde sa do parametra $operation nastaví meno role. Podľa správnosti by malo byť v $operation niečo ako "role" a v $params zoznam rolí.. bohužiaľ to tak neni.. oh well :p
}

CWebUser je odvodený od triedy CComponent. Tá poskytuje okrem iného properties (vlastnosti? eh.. bohvie ako sa to vlastne prekladá:). Property sa navonok používa ako obyčajná premenná objektu, ale v skutočnosti sa jej obsah vracia a nastavuje cez metódy getPremenna() a setPremenna(). Využitie vidíte v našej triede WebUser - môžete hocikedy napísať Yii::app()->user->model a získate model, ktorý sa načíta až v danom momente.

Výraz "lazy loading" sa používa na čokoľvek, čo sa navonok tvári, že obsahuje údaje/je kompletné/použiteľné, ale v skutočnosti sa načíta až na poslednú chvíľu keď ich naozaj treba. Na tomto princípe funguje dosť veľa vecí v Yii, čo je aj jedným z dôvodov prečo je také rýchle. Mimochodom na to, aby sa dali v PHP robiť properties, sa využívaju magické metódy PHPčka.

Anyway... :), do konfigurácie protected/config/main.php musíme ešte pridať jeden riadok, aby sa používal náš WebUser namiesto defaultej implementácie (triedu si samozrejme môžete nazvať aj inak ako 'WebUser'):

...
'components'=>array(
	'user'=>array(
		'allowAutoLogin'=>true,
		'class' => 'WebUser', // tento riadok treba pridat
	),
...

Tweaks

Aby sme prihlasovanie dotiahli do konca, patrilo by sa zmeniť menovku "name" na "email", čo sa robí v modeli protected/models/LoginForm.php. Všimnite si, že toto je model formulára, nie tabuľky, ako to býva vo frameworkoch bežné. Na to, aby sa všade ukazoval popis email stačí v metóde attributeLabels() zmeniť popis menovky username na "Email".

// class LoginForm
public function attributeLabels()
{
	return array(
		'username'=>'Email',
		'rememberMe'=>'Remember me next time',
	);
}

Ďalším nepovinným ale odporúčaným krokom je pridať chybové hlášky. Metóda authenticate() v originále vypíše iba "Incorrect username or password." a keď už máme UserIdentity poskytujúci viac podrobností, prečo to nevyužiť, right?

// class LoginForm
public function authenticate($attribute,$params)
{
	$this->_identity=new UserIdentity($this->username,$this->password);

	if(!$this->_identity->authenticate())
	{
		switch($this->_identity->errorCode)
		{
		case UserIdentity::ERROR_USERNAME_INVALID:
			$this->addError('username','Invalid email.'); break;
		case UserIdentity::ERROR_PASSWORD_INVALID:
			$this->addError('password','Invalid password.'); break;
		case UserIdentity::ERROR_STATUS_BANNED:
			$this->addError('username','Your account was blocked.'); break;
		}
	}
}

Pro tip: ajaxová validácia sa vypína vo view-e protected/views/site/login.php.

Test autorizácie

Keď už máme prihlasovanie vybavené, nebolo by od veci ho vyskúšať. Vytvoríme si teda jednoduchý controller tak, aby na každú akciu pustil len vybrané druhy používateľov. Kostru controllera odporúčam generovať cez Gii, vzhľadom na to, že ten hneď vygeneruje aj potrebný view.

class AuthtestController extends Controller {

public function actionIndex()
{
	$this->render('index');
}

public function actionAdminonly()
{
	$this->render('message', array('msg'=>'Only admin can view this!'));
}

public function actionTrainersonly()
{
	$this->render('message', array('msg'=>'Only trainers (and admin) can view this!'));
}

public function actionAuthonly()
{
	$this->render('message', array('msg'=>'Only authentificated users can view this!'));
}

public function filters()
{
	return array(
		'accessControl',
	);
}

public function accessRules()
{
	return array(
		array('allow',
			'actions'=>array('index'),
			'users'=>array('*'), // vsetci
		),
		array('allow',
			'actions'=>array('adminonly'),
			'roles'=>array('administrator'), // iba admin
		),
		array('allow',
			'actions'=>array('trainersonly'),
			'roles'=>array('administrator', 'trainer'), // admin a treneri
		),
		array('allow',
			'actions'=>array('authonly'),
			'users'=>array('@'), // vsetci prihlaseni
		),
		array('deny',
			'users'=>array('*'),
		),
	);
}

}

Pre úplnú úplnosť ešte spomeniem aj kód view-ov, pri ktorom si aspoň zopakujete ako sa vytvárajú linky a omrvinková navigácia! ;)

protected/views/authtest/index.php

<?php
$this->breadcrumbs=array(
	'Authtest',
);?>
<h1>Authorization test</h1>

<p><?php echo CHtml::link('Admin only', array('/authtest/adminonly')); ?></p>
<p><?php echo CHtml::link('Trainers (+ admin) only', array('/authtest/trainersonly')); ?></p>
<p><?php echo CHtml::link('Authentificated only', array('/authtest/authonly')); ?></p>

protected/views/authtest/message.php

<?php
$this->breadcrumbs=array(
	'Authtest'=>array('index'),
	'Message'
);?>
<h1>Authorized!</h1>

<img style="display: block; float: right; padding: 0 0 8px 8px;" src="<?php echo Yii::app()->request->hostInfo ?>/images/thumbs_up.jpg">
hostInfo poskytuje absolútnu URL kde sa nachádza naša aplikácia (bez lomítka na konci). Vďaka tomu sa zobrazí obrázok bezohľadu na to, či budeme mať aplikáciu v roote webservera (http://stranka.sk/) alebo v nejakom podadresári (http://stranka.sk/blabla/gule/tisic/).
<em><?php echo $msg ?></em>

Hotovo

Download aplikácie

Tak a to je naozaj všetko čo potrebujeme na sprevádzkovanie a otestovanie vlastného, plne funkčného auth modulu. Spustiteľný kód vrátane Yii Frameworku a schémy pre databázu si môžete stiahnuť tu [RAR].

Ďalšie časti seriálu Ako sa stať PHP ninja s Yii Frameworkom nájdeš tu:
dev > Yii

P.S.: Ak sa ti tento framework zdá veľmi zložitý, pozri si naše video lekcie k frameworku CodeIgniter na Zajtra.sk.
Matúš Koprda Matúš Koprda

Momentálne robím internety a keď budem veľký, snáď sa dostanem aj k hrám alebo niečomu zmysluplnému. :) Mám rád minimalizmus, obskúrnu hudbu, všetko geeky a rýpanie sa v detailoch. Ďalšie kecy odo mňa nájdeš na brm.sk a na Twitteri @blade_sk.


Hodnoť článok:
1 0

3 komentáre k článku:

Komentovať môžu iba prihlásení

Zaregistruj sa cez bezplatnú registráciu alebo použi login cez Facebook (FB Connect)

Prihlás sa tu, ak už máš profil na Zajtra.sk:


Zabudol som heslo

0 0 Jana Mičeková 5.2.2012 12:14:00
Ďakujem za článok. Keďže zatiaľ nepotrebujem nič komplikované, ostanem pri tomto riešení prihlasovania. Prešla som a skúsila aj ten postup v linku nižšie, snáď pochopila RBAC, vyskúšala aj rozšírenia Rights a UserGroups. Rights nemali správu užívateľov, u UserGroups tie linky (napriek úprave UrlManagera a skrytiu index.php) nefungovali ako mali. Keďže ešte netuším, či sa mi podarí rozbehať slovenčinu (diakritiku, Yii podľa všetkého nepodporuje ISO-8859-2), nechcela som sa tým zdržovať. O to viacej oceňujem toto riešenie a pokiaľ sa mi to podarí úspešne nanovo zopakovať, pôjdem ďalej. Fakt skvelá práca.
0 0 Michal Biroš (fb) 7.6.2011 00:00:00
k tej casti, kde rozoberas WebUser-a... ta originalna funkcia checkAccess nie je nespravna, iba pocita s inym sposobom pridelovania prav.

Zatial co ty pocitas s tym, ze pouzivatel ma nejaku rolu a podla nej ma pristup niekam...."Yii sposob" umoznuje jednotlivym rolam pridavat biznis pravidla.

Ma to vela vyhod...mozes pridavat nove roly bez toho, aby si musel nieco menit v kode. Dalej napr. pri mazani komentaru mozes okrem toho ci je pouzivatel prihlaseny overit aj to, ci maze vlastny komentar. atd.

Aby som to zhrnul...podla mna "Yii sposob" kontrolovania pristupu/prav je lepsi a prepisanim metody checkAccess sa pripravujes o urcity komfort.
Odporucam tento tutorial: http://danaluther.blogspot.com/2010/03/yii-authentication-via-database.html
0 0 Juraj Bencúr (fb) 13.1.2011 00:00:00
Moc, moc dobré! Ďakujem za článok. :-)
Zajtra.sk > Programovanie > PHP > Ako sa stať PHP ninja s Yii Frameworkom (3)


Kritika

Vieš ako robiť veci lepšie? Pomôž našim odvážnejším členom a skritizuj im projekty!

Reklama

Seriály zo Zajtra.sk

Reklama