Authenticator + model, rozdělení zodpovědností
- Tori
- Člen | 32
Pěkný večer.
Procházela jsem si autentikátor
v sandboxu i dokumentaci (tady nebo tady)
a moc nerozumím tomu, jakým způsobem se ověrují přihlašovací údaje.
Vadilo mi:
- Entitu uživatele a editaci jeho údajů má na starosti model (např.
UserRepository
). Proč by měl vědět něco o tom, jestli se má při úspěšném ověření přihl.údajů vracet instanceIdentity
nebo čeho? → Proč je autentikátor v sandboxu zařazený pod NSModel
? - A naopak, autentikátor patří pod NS
Nette\Security
, implementujeNette\Security\IAuthenticator
. Tedy nemá nic společného s modelem, tím méně sNette\Database
. → Proč v případech, kdy není v NS Model, dostává přesto připojení k databázi a používá konkrétníNette\Database
API?
Došla jsem teda k něčemu takovémuto:
- rozhraní
Security\IUserRepository
určuje, že třída umí- najít data uživatele podle přihlašovacího jména
- vytvořit hash hesla
Model\UserRepository implements Security\IUserRepository
- kromě věcí z rozhraní je to běžný model, vrací data na profil uživatele apod.
Security\Authenticator
- dostane třídu implementující
Security\IUserRepository
, od ní získá údaje o uživ., s její pomocí ověří heslo - vrací
Identity
nebo vyhazuje při chybě.
- dostane třídu implementující
Příklad jsem kvůli délce dala jako Gist. Budu ráda za vaši kritiku.
edit (2013–03–14 18h): Vlastně totéž už radil Hosiplan před dávnými lety… snad někdy přečtu tu tunu užitečných rad tady.
Editoval Tori (14. 3. 2013 18:25)
- Jan Tvrdík
- Nette guru | 2595
Pěkný večer Tori,
v prvé řadě – model není třída, ale jedna ze tří částí MVC architektury. Může být jako třída implementován, ale v praxi je implementován spíše jako nějaký soubor tříd.
Proč by měl (
UserRepository
) vědět něco o tom, jestli se má při úspěšném ověření přihl.údajů vracet instance Identity nebo čeho?
To záleží, jakou zodpovědnost chceš, aby třída
UserRepository
měla. Pokud má sloužit jako repositář
v takovém tom klasickém slova smyslu, tak by se o žádnou autorizaci starat
neměl. Už proto, že je to porušení Single responsibility principle.
Proč je autentikátor v sandboxu zařazený pod NS
Model
?
Protože je to součást logiky aplikace pracující s daty. Tedy z pohledu MVC ho řadíme do modelu.
A naopak, autentikátor patří pod NS
Nette\Security
.
Nechápu otázku / problém.
Pominu-li použití mizerného hashovacího algoritmu (sha1), tak to vypadá rozumně, ač možná zbytečně složitě.
- Tori
- Člen | 32
A naopak, autentikátor patří pod NS Nette\Security.
Nechápu otázku / problém.
Myšleno že v těch ostatních dvou uvedených příkladech, tj. v dokumentaci (https://doc.nette.org/…thentication) a kuchařce (https://doc.nette.org/…thentication) je zařazen pod NS Security, tak proč používá mapper místo nějaké vyšší vrstvy modelu? Ten autentikátor v sandboxu je btw stejný jako v příkladu v docs, ale každý je zařazený v jiném kontextu (namespace) – vlastně i proto jsem nad tím začala uvažovat.
Složité to asi je proto, že jsem autentikátor nechtěla jako součást
modelu. Z pohledu autentikátoru by měla implementace
IUserRepository
být spíš service než
repository (podle pěti
vrstev modelu). Tzn. pokud změním způsob ukládání uživatelů do DB,
změním úložiště, nebo budu chtít solit, zas upravím jen repository.
Zkusím to použít a uvidím, jestli se objeví nějaký zádrhel. Každopádně díky. :)
Editoval Tori (14. 3. 2013 7:41)
- pawouk
- Člen | 172
@Tori: No já s tebou zase souhlasím, také si myslím, že do Authenticator neptaří do modelu a teké to mám mimo. Rozhodně mi přijde špatně aby authenticator měl přímí přístup do DB. Tam má sahat jen model a autenticator se nemá starat o to zda vzal model data z db nebo xml. Nicméně já to mám vyřešešné tak, že do autenticatoru injectnu model a je to. Pak se metoda autenticator celkově zjednoduší:
public function authenticate(array $credentials){
list($username, $password) = $credentials;
$user = $this->model->findUser($username);
if(!$user)
throw new Security\AuthenticationException('The username is incorrect.', self::IDENTITY_NOT_FOUND);
if(!$user->checkPassword($password))
throw new Security\AuthenticationException('The password is incorrect.', self::INVALID_CREDENTIAL);
return new Security\Identity($user->id, $user->getRoles(), $user->toArray());
}
Editoval pawouk (14. 3. 2013 7:55)
- Vojtěch Dobeš
- Gold Partner | 1316
Model je business logika. Přihlášování do aplikace, pomocí uživatelského účtu, zapsaného v databázi, je jednoznačně její součástí. Není žádný důvod autentikátor někam vyčleňovat, je to prostě součást modelu. Měl by mít pouze jednu zodpovědnost (přihlašování), tudíž si nutně bude muset spoustu věcí vyžádat jako závislosti (třídu na komunikaci s databází atd.) a je mu jedno, kolik vrstev tyhle závislostí zase obalují.
Proměnná $this->model
je peklo :). Jak píše Honza, pojem
Model je to M z MVC. Když bych náhodou měl pracovat na tomhle tvém
projektu, budu se ptát: a jaký propána model? Tohle je fakt bad pattern.
Zajímalo by mě, instance jaké třídy v té proměnné je?
- Šaman
- Člen | 2659
Model mě nepřestane fascinovat tím, že 10 lidí má na něj 10 různých názorů.
Ověření, zda zadaný uživatel má na něco práva (autorizátor) je určitě věc modelu. Ověření, zda uživatel je ten, za kterého se vydává (autentizátor), tam si nejsem úplně jist, ale má to k modelu tak blízko, že je to podle mě ok.
Ale od té doby, kdy nepoužívám $this->model
(který
sloužil jako repositoryLocator a zároveň nejvyšší fasáda) mám stále
morální problém s tím nechat presentery hrabat se v jednotlivých
Repository (potažmo mapperech, protože často ta třída plní obě funkce).
Prostě mi připadá, že presenter má předat třeba data z formuláře
modelu a né přemýšlet, kterému repository to předat, případně mu to
ještě předkousat na objekt.
- duke
- Člen | 650
Tori napsala:
@pawouk:
$user->checkPassword($password)
– tohle se mi líbí, proč by měl autentikátor vědět, jakým způsobem se heslo kontroluje a jestli se hašuje nebo ne, s dovolením použiju.
Ono lze položit i jinou otázku. Proč by měl user vědět, jakým způsobem probíhá autentikace (tj. že se vůbec používá nějaké heslo a to či ono hashování). To je přece věcí autentikátoru. User by měl pouze na požádání poskytnout údaje, tj. např. heslo (ve tvaru, v jakém ho autentikátor očekává, a do tohoto tvaru ho logicky může zakódovat opět jen autentikátor).
- Tori
- Člen | 32
@duke: Aha… já jsem pawouka pochopila tak, že to ověření hesla taky dává do modelu. Ale vlastně jemu model vrací instanci (čeho?), která to ověření dělá.
Já tohle právě nemám velmi ujasněné.
Způsob počítání hashe je bezpečnostní záležitost a model by to nemuselo
zajímat (dokud se změnou algoritmu nezmění délka výsledného
řetězce).
Na druhou stranu, při registraci uživatele nebo změně
hesla taky potřebuju nějak vyrobit hash – mám někde (kde?) volat
autentikátor? Nemůžu mít ten algoritmus na dvou místech.
Anebo co když umožním i přihlašování jinými způsoby (Facebook, OpenID
nebo třeba uživatele kousne myš a ověří jeho DNA) – pro tyhle případy
by zas autentikátor potřeboval jen to rozhraní k datové vrstvě s metodami
getUserData
, getUserRoles
a
checkCredentials
a nechat případné hashování (nebo rozbor
krve) na modelu.
Takže možná takhle:
User
dostane nějaké údaje potřebné k přihlášení. Podle zvoleného způsobu ověřování (jméno+heslo nebo FB …) si požádá AuthenticatorFactory o příslušný autentikátor a tomu předhodí ty údaje.- pak jsou implementace
IAuthenticator
: každá má na starost, aby zkontaktovala třídu, která s tímhle typem údajů umí pracovat, a nechala je u ní ověřit. Případně jí nějakou předáme jako výchozí způsob ověřování. - a v modelu jsou různé implementace
ICanAuthenticate
, každá požaduje nějaký typ údajů pro přihlášení, a jakým způsobem je ověří a jestli čte z DB nebo ne, jestli hashuje, to je její věc. … protože vlastně hashování nepatří pod zabezpečení aplikace (tzn. autentikátor), ale pod zabezpečení dat samotných, takže to přeci jen patří do modelu. (a tím je vyřešená i registrace a změna hesla)
Takže při použití SimpleAuthenticator
by se nastavení
změnilo z tohoto:
authenticator:
class: Nette\Security\SimpleAuthenticator
arguments: [[
john: IJ^%4dfh54*
kathy: 12345
]]
na toto:
authenticator: Nette\Security\SimpleAuthenticator
defaultAuthModel:
class: SomeClass
arguments: [[
john: IJ^%4dfh54*
kathy: 12345
]]
class SomeClass implements ICanAuthenticate
{
private $credentials = array();
public function __construct($credentials) { $this->credentials = $credentials; }
/** @return bool */
public function checkCredentials($data)
{
return isset($this->credentials[$data['username']])
? ($this->credentials[$data['username']] === $data['password'])
: false;
}
}
Jak se vám to zdá takhle? Vim, že to je asi trochu překombinované, ale zkoušim si představit možné problémy, abych to nemusela při každém novém požadavku předělávat.
- duke
- Člen | 650
Tori napsala:
@duke: Aha… já jsem pawouka pochopila tak, že to ověření hesla taky dává do modelu. Ale vlastně jemu model vrací instanci (čeho?), která to ověření dělá.
Doporučuji si přečíst komentář od @vojtech.dobes ohledně pojmu „model“. Model nic nevrací; není to funkce, aby něco vracel. Vše, co se týká vnitřního chování aplikace (a sem patří i logika autentikace), je součástí „vrstvy“ zvané model.
Patrně matete model s vrstvou repozitářů. Ty také samozřejmě patří do modelu, ale krom nich tam také patří např. služby pracující nad nimi (včetně autentikátorů).
Já tohle právě nemám velmi ujasněné.
Způsob počítání hashe je bezpečnostní záležitost a model by to nemuselo zajímat (dokud se změnou algoritmu nezmění délka výsledného řetězce).
Bezpečnostní záležitosti aplikace spadají do modelu, neboť se týkají
vnitřního chování aplikace (model řeší, co je přístupné a co ne), a
nikoli jen vnější komunikace s uživatelem (view řeší, co z toho, co je
přístupné, se zobrazí a jak).
Způsob počítání hashe je vnitřní záležitostí autentikátoru. Je to
tedy součást modelu. Zajímat to nemusí UserRepository.
Na druhou stranu, při registraci uživatele nebo změně hesla taky potřebuju nějak vyrobit hash – mám někde (kde?) volat autentikátor? Nemůžu mít ten algoritmus na dvou místech.
Toto by nejspíš měla rešit služba, která má přístup jak k repozitáři, kde se přístupové údaje ukládají (např. UserRepository), tak ke službě, která umí heslo zahashovat (to může být buď přímo autentikátor, nebo nějaká služba použitá uvnitř autentikátoru).
Anebo co když umožním i přihlašování jinými způsoby…
Pak jen přibude více autentikátorů a případně dalších služeb spojených s režií kolem (jako výše pro registraci či změnu hesla, atp.).
Takže možná takhle:
User
dostane nějaké údaje potřebné k přihlášení. Podle zvoleného způsobu ověřování (jméno+heslo nebo FB …) si požádá AuthenticatorFactory o příslušný autentikátor a tomu předhodí ty údaje.
Pokud User
míníte Nette\Security\User
,
tak v momentu, kdy obdrží přihlašovací údaje (voláním metody login
),
požádá svůj autentikátor (pokud
nebyl nastaven, pokusí se ho nalézt pomocí service locatoru)
o autentikaci. Takže buď můžete specifický autentikátor nastavit před
voláním metody login
, nebo si můžete vytvořit univerzální
autentikátor, který podle předaných argumentů deleguje autentikaci na
konkrétnější autentikátor.
- pak jsou implementace
IAuthenticator
: každá má na starost, aby zkontaktovala třídu, která s tímhle typem údajů umí pracovat, a nechala je u ní ověřit. Případně jí nějakou předáme jako výchozí způsob ověřování.
Nejsem si jist, zda rozumím. Třídy implementující rozhraní
IAuthenticator
jsou právě těmi třídami, které mají umět
s danými údaji pracovat. Zda k tomu potřebují další služby (např.
repozitáře) už je jiná věc.
- a v modelu jsou různé implementace
ICanAuthenticate
, každá požaduje nějaký typ údajů pro přihlášení, a jakým způsobem je ověří a jestli čte z DB nebo ne, jestli hashuje, to je její věc. … protože vlastně hashování nepatří pod zabezpečení aplikace (tzn. autentikátor), ale pod zabezpečení dat samotných, takže to přeci jen patří do modelu. (a tím je vyřešená i registrace a změna hesla)
Tady už je to IMHO totálně zmatené. Jaký je rozdíl mezi
IAuthenticator
a ICanAuthenticate
? Vždyť významem
slov jde o totéž. Do modelu patří obojí ze zmíněných zabezpečení.
Jak se vám to zdá takhle? Vim, že to je asi trochu překombinované, ale zkoušim si představit možné problémy, abych to nemusela při každém novém požadavku předělávat.
Nejprve si ujasněte pojmy jako „model“ a zkuste se na věc podívat z pohledu SRP (single responsibility principle), jak už o tom psal @Jan Tvrdík.
Editoval duke (15. 3. 2013 14:15)