Laravel Tutorial Teil 2 zum Thema Authentifizierung und Autorisierung mit Rollen.
Authentifizierung mit Laravel
Um mit Laravel schnell Authentifizierung zu ermöglichen, kann man mit folgendem Befehl entsprechende Datenbank, Templates, Routes und Controller erstellen:
1 |
php artisan make:auth |
Die entstehende Migration führen wir zum jetzigen Zeitpunkt noch nicht aus. Wir wollen die Authentifizierung gleich noch erweitern.
Mit der Laravel-Authentifizierung können sich nun Nutzer registrieren und ein Passwort vergeben und sich dann anmelden. Über entsprechende Methoden können dann Aktionen z.B. im Controller geschützt werden und auf angmeldete Benutzer (also authentifizierte Benutzer) eingeschränkt werden.
Erweiterung der Authentifizierung mit Rollen-Autorisierung
Nachdem wir nun die Authentifizierung über php artisan make:auth
implementiert haben, werden wir das Ganze noch etwas erweitern. Laravel bringt selbst zwar die Möglichkeiten von Gates und Policies mit. Ich möchte jedoch das Ganze an ein klassisches Rollensystem knüpfen. Zur Autorisierung mit Laravel-Bordmitteln komme ich später in einem weiteren Teil des Tutorials noch.
Dazu gibt es auch bereits „fertige“ Pakete, aus didaktischen Gründen macht jedoch der „Nachbau“ eines solchen Systems sinn, um die Arbeitsweise und das Vorgehen besser zu verstehen. Außerdem folgt es dem KISS-Prinzip und man verhindert unnötigen Overhead ;-).
Die folgenden Ausführungen sind an einige Tutorials angelehnt und für meine Zwecke angepasst.
- Laravel 5.4 (and 5.5) native User Authentication + Role Authorization
- https://laravel-news.com/authorization-gates
- https://code.tutsplus.com/tutorials/gates-and-policies-in-laravel–cms-29780
- https://scotch.io/tutorials/user-authorization-in-laravel-54-with-spatie-laravel-permission
Vorüberlegungen
Für meine Rollen-Autorisierung möchte ich vier Rollen einfügen:
- Administratoren (admin)
- Bearbeiter (editor)
- Autoren (author)
- Benutzer (user)
Die Reihenfolge gibt eine gewisse „Rechtehierarchie“ vor. Soll heißen: Administratoren haben mehr rechte also Bearbeiter etc. Wir könnten die Gruppen aber auch ganz anders benennen. Vorstellbar wäre „Arbeitsgruppe 1“, „Arbeitsgruppe 2“ etc. Dann ist keine klassische Hierarchie im engeren Sinne vorhanden. Auch dies ist möglich. Zu den Beispielen wie man das Rechtesystem nun nutzen kann, komme ich zu einem späteren Zeitpunkt. Jetzt erstmal zur Einrichtung.
Das Rollen-Modell
Zunächst benötigten für unsere Rollen natürlich ein Modell samt Datenbanktabellen und Inhalt. Zum einen werden die Rollen selbst benötigt und zum anderen eine Zuordnung von Nutzern und Rollen. Beginnen wir mit dem Rollen-Modell.
Dazu weisen wir Laravel an uns zunächst ein Modell namens „Role“ zu generieren:
1 |
php artisan make:model Role -m |
Der Parameter -m
erzeugt uns dabei gleich die notwendige Migrations-Datei mit der Bezeichnung create_roles_table.php
im Ordner /database/migrations
. Diese werden wir auch so gleich bearbeiten. Das erzeugte Grundgerüst für die Erstellung der roles
-Tabelle erweitern wir um zwei Felder:
- Rollenbezeichnung (name)
- Rollenbeschreibung (description)
dazu fügen wir die hervorgehobenen Zeilen in unsere up()
-Methode ein:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateRolesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('roles', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('description'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('roles'); } } |
Als nächstes erstellen wir nun die Migration für die Benutzer-Rollen-Zuweisung, also eine role_user
Pivot-Tabelle:
1 |
php artisan make:migration create_role_user_table |
Die dadurch erstelle Migrations-Datei wird ebenfalls angepasst. Wir finden Sie im Migrations-Ordner mit einem Zeitstempel gefolgt von der Bezeichnung create_role_user_table.php
. Wir fügen hier ebenfalls zwei Felder in der up()
-Methode ein, denn wir müssen einen Benutzer (user_id
) zu einer Rolle (role_id
) zuordnen. In diesem Fall verzichten wir auf die Laravel-Zeitstempel und entfernen die Zeile.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateRoleUserTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('role_user', function (Blueprint $table) { $table->increments('id'); $table->integer('role_id')->unsigned(); $table->integer('user_id')->unsigned(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('role_user'); } } |
Damit haben wir nun die notwendigen Datenbanktabellen und Model-Dateien erstellt. Damit man später mit den Rollen arbeiten kann, fügen wir ins User- und ins Role-Modell noch die n:m-Beziehung (in Laravelsprache auch many-to-many relationship) ein. Dazu fügen wir jeweils eine Methode ein:
User-Modell
1 2 3 4 |
public function roles() { return $this->belongsToMany(Role::class); } |
Role-Modell
1 2 3 4 |
public function users() { return $this->belongsToMany(User::class); } |
Wer genau hingeschaut hat, dem wird etwas auffallen: Der ein oder andere ist davon ausgegangen, dass einem Benutzer jeweils nur eine Rolle zugewiesen werden kann. Das ist nicht der Fall. Ein Benutzer kann über beliebig viele Rollen verfügen. Wir ergänzen im User-Modell noch ein paar weitere Methoden, damit wir später auch überprüfen können, ob ein Nutzer eine bestimmte Rolle besitzt:
- authorizeRoles
- hasAnyRole
- hasRole
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * @param string|array $roles */ public function authorizeRoles($roles) { if (is_array($roles)) { return $this->hasAnyRole($roles) || abort(401, 'This action is unauthorized.'); } return $this->hasRole($roles) || abort(401, 'This action is unauthorized.'); } /** * Check multiple roles * @param array $roles */ public function hasAnyRole($roles) { return null !== $this->roles()->whereIn('name', $roles)->first(); } /** * Check one role * @param string $role */ public function hasRole($role) { return null !== $this->roles()->where('name', $role)->first(); } |
unser User-Modell sollte nun also so aussehen (gekürzt):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; protected $fillable = [ 'name', 'email', 'password', ]; protected $hidden = [ 'password', 'remember_token', ]; public function roles() { // ... } public function authorizeRoles($roles) { // ... } public function hasAnyRole($roles) { // ... } public function hasRole($role) { // ... } } |
Anpassung der Laravel-Registrierung
Nachdem wir nun alle Vorbereitungen getroffen haben sollten wir zunächst die Registrierung von Laravel anpassen. Aktuell würde dies nicht funktionieren. Wir müssen nämlich dem Nutzer nach der Registrierung noch automatisch eine Rolle zuweisen. Soll ja nicht gleich jeder Admin weden ;-).
Dazu passen wir die create()
-Methode im Register-Controller unter app/Http/Controllers/Auth/RegisterController.php
an:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
use App\Role; class RegisterController ... protected function create(array $data) { $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); $user ->roles() ->attach(Role::where('name', 'user')->first()); return $user; } |
Damit haben wir nun fasst alles geschafft. Jetzt müssen noch ein paar Dummy-Benutzer sowie deren Rollenzuordung in die Datenbank. Dies kann man einfach mit Laravels seeding-Funktion machen. Dazu rufen wir folgende Befehle auf:
1 2 |
php artisan make:seeder RoleTableSeeder php artisan make:seeder UserTableSeeder |
Dadurch werden zwei Seed-Klassen namens RoleTableSeeder
und UserTableSeeder
im Verzeichnis database/seeds/
erstellt. Diese passen wir dann entsprechend an.
RoleTableSeeder-Klasse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
use Illuminate\Database\Seeder; use App\Role; class RoleTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $role_admin = new Role(); $role_admin->name = 'admin'; $role_admin->description = 'Administrator'; $role_admin->save(); $role_user = new Role(); $role_user->name = 'user'; $role_user->description = 'Benutzer'; $role_user->save(); $role_author = new Role(); $role_author->name = 'author'; $role_author->description = 'Autor'; $role_author->save(); $role_editor = new Role(); $role_editor->name = 'editor'; $role_editor->description = 'Bearbeiter'; $role_editor->save(); } } |
UserTableSeeder-Klasse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
use Illuminate\Database\Seeder; use App\User; use App\Role; class UserTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $role_admin = Role::where('name', 'admin')->first(); $role_editor = Role::where('name', 'editor')->first(); $role_author = Role::where('name', 'author')->first(); $role_user = Role::where('name', 'user')->first(); $admin = new User(); $admin->name = 'admin'; $admin->email = 'admin@example.com'; $admin->password = bcrypt('pass'); $admin->save(); $admin->roles()->attach($role_admin); $editor = new User(); $editor->name = 'editor'; $editor->email = 'editor@example.com'; $editor->password = bcrypt('pass'); $editor->save(); $editor->roles()->attach($role_editor); $author = new User(); $author->name = 'author'; $author->email = 'author@example.com'; $author->password = bcrypt('pass'); $author->save(); $author->roles()->attach($role_author); $user = new User(); $user->name = 'user'; $user->email = 'user@example.com'; $user->password = bcrypt('pass'); $user->save(); $user->roles()->attach($role_user); } } |
Damit diese beiden Klassen auch entsprechend ausgeführt werden, muss jetzt noch eine letzte Anpassung in der DatabaseSeeder-Klasse
im gleichen Verzeichnis durchgeführt werden.:
1 2 3 4 5 6 7 |
public function run() { // zuerst die Role-Tabelle füttern ... $this->call(RoleTableSeeder::class); // ... denn der User benötigt diese Roles $this->call(UserTableSeeder::class); } |
Puh geschafft … ein letzter Befehl setzt nun alles in die Tat:
1 |
php artisan migrate:fresh --seed |
Wer nun in die Datenbank blickt findet nun vier Nutzer mit entsprechenden Rechten. Der Einfachheit halber wurden die Nutzer entsprechend ihrer Rollen benannt und besitzen jeweils das Passwort „pass“.
Nun haben wir also alles fertig vorbereitet um in unserer Anwendung diese Rollen zur Autorisierung zu verwenden. Wie das funktioniert erfahrt ihr im nächsten Teil.