Bevezetés
Mivel a Breeze-t már használjuk, ezért "akarva/akaratlanul" van is már két felhasználói csoportunk. Az egyik a látogatók csoportja, ők azok, akik nincsenek regisztrálva, ezáltal bejelentkezve sem lehetnek, illetve a másik csoport az a regisztrált felhasználóké, akik be is tudnak jelentkezni az alkalmazásunkba. Ezt a két csoportot fogjuk most kiegészíteni egy adminisztrátor csoporttal, vagy nevezhetjük szerepkörnek is a csoportokat.
Szerepkörök beépítése
Adatbázis bővítése
Hozzunk létre egy új migrációs fájlt:
php artisan make:migration add_role_column_to_users
Az up() metóduson belül ezzel bővítsük a users tábla módosítását:
$table->enum('role', ['user','admin'])->default('user');
Megjegyzés: ha komplexebb alkalmazást építünk, akkor érdemes lehet egy roles táblát létrehozni, amiben van id, name és a timestamp-ek, majd azt összekötni a users tábla módosításával, ott létrehozva egy role_id külső kulcsot erre a roles táblára. Azonban mi most egyszerűbben oldjuk ezt meg azáltal, hogy egy sima oszlopbővítést hajtottunk végre és enumerációval (felsorolással, ami kvázi egy lista a lehetséges értékeivel) hoztuk létre az új role oszlopot a users táblában.
A down() metóduson belül ezzel bővítsük a users tábla módosítását:
$table->dropColumn('role');
Ezután következhet a migráció végrehajtása:
php artisan migrate
Megjegyzés: a MySQL adatbáziskezelőben a táblákhoz tudunk enum típusú oszlopot hozzáadni. Az SQLite ezt egy kicsit "trükkösebben" oldja meg a háttérben, mivel ott egy sima varchar (szöveges) típusú oszlop jön létre, amihez egy ellenőrzést fűz hozzá: CHECK("role" IN ('user', 'admin')) Tehát csak user és admin értékek kerülhetnek bele ebbe a mezőbe.
Nekem most két felhasználóm van a users táblában és az egyiket manuálisan beállítom admin szerepkörűre. Ugye a migrációs fájlban beállított alapértelmezett (default) érték miatt mindkét felhasználó alapból user szerepkörű lett, ezt most az egyiknél megváltoztatom admin-ra.
User Model bővítése
Szerkesszük át egy picit a User Model fájlunkat. Először is a $fillable tömbhöz adjuk hozzá az új role mezőnket.
protected $fillable = [
'name',
'email',
'password',
'username',
'role',
];
Adjunk hozzá továbbá egy új metódust, ami egy bizonyos szerepkör (user vagy admin) meglétét hivatott ellenőrizni majd:
public function hasRole(string $role): bool
{
return $this->getAttribute('role') === $role;
}
A hármas egyenlőség jel nem csak azt ellenőrzi, hogy megegyezik-e a paraméterként kapott $role változó típusa (string) megegyezik-e az alapértelmezetten létező getAttribute() metódus visszatérési értékének típusával.
Szerepkörök használata
Ahhoz, hogy tudjuk használni szerepköröket, érdemes átgondolni, átismételni, hogy hogyan is történik meg a felhasználói kérések kiszolgálása:
- A felhasználó vagy látogató beír egy URL-t a böngészőjébe, ami jobb esetben regisztrálva van útvonalként a mi Laravel rendszerünkben.
- Ha regisztrálva van az útvonal, akkor felhasználói kérés végigmegy a regisztrált middleware-eken, az alapértelmezetten létezőkön túl, az általunk regisztráltakon is végighalad, mint egy vöröshagyma héjain kintről a közepe felé haladva. A middleware-ek áttekintéséhez érdemes áttekinteni ezt a blogbejegyzésemet.
- Ha túljutott a felhasználói kérés a middleware-eken, akkor következhet az útvonalhoz tartozó Controller metóduson keresztül a feldolgozása (esetleg még a View megjelenítése és visszaadása a felhasználó böngészőjének).
Először rátérhetünk az útvonal regisztrációs fájlunkra, a web.php-ra. A user szerepkörű felhasználónknak csak egy módosítást hajtsunk végre a /dashboard útvonalában:
Route::get('/dashboard', function () {
return view('user_dashboard');
})->middleware(['auth', 'verified', 'user'])->name('dashboard');
A nézet nevét írtuk itt át dashboard-ról user_dashboard-ra, valamint a middleware-ek felsorolása közé felvettük a user middleware-t. Azért, hogy ez működjön és használható legyen, keressük meg a dashboard nézetünket és nevezzük át a fájlt erre: user_dashboard. Valamint a fájlon belül a (4. sorban lévő) címet írjuk át "Dashboard"-ról "User Dashboard"-ra. Magát az útvonal elérhetőségét (/dashboard) és nevét (dashboard) azért nem akartam megváltoztatni, mert az a rendszer több más részét is érintené, de itt most nem az a lényeg nekünk, úgyhogy emiatt ezt érintetlenül hagytam.
Következhet a másik útvonal a web.php fájlban, amelyet újként kell létrehoznunk, de lemásolhatjuk a fenti útvonalat és a megfelelő részeit módosíthatjuk így:
Route::get('/admin-dashboard', function () {
return view('admin_dashboard');
})->middleware(['auth', 'verified', 'admin'])->name('admin_dashboard');
Ez a nézet viszont még nem létezik, viszont lemásolhatjuk hozzá az előbb módosított dashboard.blade.php -t és adjuk neki az admin_dashboard.blade.php nevet. A fájlban az imént átírt címet írjuk át "User Dashboard"-ról "Admin Dashboard"-ra.
Megjegyzés: vegyük észre, hogy túl sok háttérlogika itt még nincsen az útvonalak mögött, csak egy példát mutatok arra, hogy hogyan lehet middleware-ek segítségével más-más irányba küldeni az adott szerepkörrel rendelkező felhasználókat.
Most létre fogunk hozni két middleware-t, egyet-egyet
szerepkörönként, tehát a user és az admin szerepkör is kapni fog egyet,
amelyeket utána majd az útvonalak hozzáférésénél fogunk hasznosítani.
php artisan make:middleware UserAuthenticated
php artisan make:middleware AdminAuthenticated
Kezdjük a munkát az AdminAuthenticated osztály handle() metódusának magjával, alakítsuk át így:
if( auth()->check() )
{
$user = auth()->user();
if ( $user->hasRole('admin') ) {
return $next($request);
}
}
abort(403);
A hibamentes működéshez a fájl tetején importáljuk be az App\Models\User osztályt. Megjegyzés: a php dokumentációs megjegyzésben (/** ... */) a @var, mint variable, tehát változó megjelöléssel segítjük a rendszert azzal, hogy megmondjuk ennek a $user változónak a típusát, ami így egy User osztályból példányosított objektum lesz.
Ha pedig nincs admin szerepköre a felhasználónak, akkor adjunk neki vissza egy 403-as HTTP hibakódot, ami a hozzáférés megtagadását jelenti.
Folytassuk a UserAuthenticated osztály handle() metódusának magjának átalakításával:
if( auth()->check() )
{
$user = auth()->user();
if ( $user->hasRole('admin') ) {
return redirect(route('admin_dashboard'));
}
else if ( $user->hasRole('user') ) {
return $next($request);
}
}
abort(403);
Ez egy kicsit annyival trükkösebb az előzőnél, hogy mivel a Breeze komponens részei automatikusan a felhasználói beléptetés után a /dashboard útvonalra akarja küldeni a felhasználót, itt most mi egy ellenőrzést hajtunk végre, és hogy ha az, aki bejelentkezett, nem egy sima felhasználó (user), hanem egy adminisztrátor (admin), akkor oda továbbítjuk az ő vezérlőpultjára. Ha egyik szerepkörrel sem rendelkezik, akkor itt is egy hozzáférés megtagadva üzenettel fogjuk ezt jelezni a látogatók felé.
Itt se felejtsük el importálni a User osztályunkat a fájl tetején.
Nincs más hátra, mint az, hogy beregisztráljuk a rendszerbe ezt a két útvonal middleware-t:
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'check-hour' => \App\Http\Middleware\CheckHour::class,
'user' => \App\Http\Middleware\UserAuthenticated::class,
'admin' => \App\Http\Middleware\AdminAuthenticated::class,
];
A users adatbázistáblám így néz ki:
Úgyhogy ezzel a két felhasználóval fogom tesztelni, hogy megfelelő vezérlőpultot (dashboard felületet) kapok-e meg. Elsőként itt látható, hogy Attila nevű felhasználómmal a "User Dashboard"-ot kapom meg bejelentkezés után:
Teszt Elek nevű felhasználómmal pedig az "Admin Dashboard"-ot és a hozzá tartozó útvonalat kapom meg a böngészőmben:
Ha pedig user szerepkörű felhasználóként szeretném elérni az /admin-dashboard útvonalat, akkor megkapom a 403-as hozzáférés megtagadva hibaüzenetet.
Látogatóként (nem bejelentkezett felhasználóként) sem a /dashboard sem az /admin-dashboard útvonalat nem tudom elérni, ha ezeket írnám be, akkor automatikusan a bejelentkezési űrlapra irányítana rá a rendszer.
Összegzés és kiegészítő információk az útvonalakról: csoportosítás (group) és előtag (prefix)
Ebben a bejegyzésben a felhasználói azonosításhoz kapcsolódó tudásanyagunkat kiegészítettem a szerepkör alapú lehetőségek bemutatásával. Mindehhez a felhasználói kérések (requests) feldolgozását használtuk fel, mivel a kiszolgáláshoz különböző middleware-eken kell keresztül mennie a kérésnek, itt tudtuk a felhasználók adott szerepkörének meglétét ellenőrizni. Ha több útvonalat is szeretnénk ilyen módon ellenőrizni, akkor lehetőségünk van csoportosítani az útvonalainkat, például azokat, amelyek a sima regisztrált felhasználókhoz vagy esetleg az adminisztrátorokhoz tartoznak. Ehhez példaként nézzük meg a web.php-ban már szereplő "profile" útvonalakhoz tartozó csoportosítást. A felhasználói profil szerkesztéséhez elegendő az "auth" middleware-nek való "megfelelés". Ha viszont a user és admin middleware-jeinkhez szeretnénk útvonalakat létrehozni, akkor azt így tudnánk megtenni:
Route::group(['middleware' => ['auth', 'user']], function () {
});
Route::group(['middleware' => ['auth', 'admin']], function () {
});
A megjegyzések helyére most konkrét útvonalat én nem regisztráltam, csak szemléltetni szerettem volna, hogy hogyan is kellene ennek működnie.
Még egy legutolsó kiegészítés: ha ezeket a user és admin specifikus útvonalakat szeretnénk ellátni egy előtaggal, egy prefixszel, akkor azt még a group metódus első tömb paraméterén belül jelezni kell:
Route::group(['middleware' => ['auth', 'user'], 'prefix' => 'user'], function () {
});
Route::group(['middleware' => ['auth', 'admin'], 'prefix' => 'admin'], function () {
});
Ilyenkor egy user-specifikus útvonal így nézhet ki: /user/utvonal-1
Míg egy admin-specifikus útvonal így: /admin/utvonal-1
Tehát a két útvonal regisztrációja lehet egyforma is és ez nem fog gondot okozni, mivel a prefix-ben különbözni fognak egymástól.
A bejegyzéshez kapcsolódó módosításokat tartalmazó Github commit itt érhető el.