• Czas czytania ~16 min
  • 25.04.2023

Pobierz przykładowy kod: https://github.com/wnikk/-laravel-access-example

W tym artykule omówimy, jak zaimplementować kontrolę dostępu opartą na rolach (RBAC) w Laravel 10, aby skutecznie zarządzać dostępem użytkowników.
Kontrola dostępu opartego na rolach jest modelem zabezpieczeń, w którym użytkownikom przypisywane są role na podstawie ich obowiązków służbowych, a tym rolom są przyznawane prawa dostępu.
Metodologia ta zapewnia, że tylko upoważnieni użytkownicy mają dostęp do określonych funkcjonalności i danych w aplikacji.
Aby zaimplementować RBAC, użyjemy pakietu "wnikk/laravel-access-rules" z Github, który upraszcza tworzenie ról i uprawnień.
Omówimy kroki związane z tworzeniem ról i uprawnień, przypisywaniem ich użytkownikom oraz ochroną poufnych informacji przed nieautoryzowanym dostępem.

Jedną z głównych zalet wdrożenia RBAC w Laravel jest to, że umożliwia on szczegółową kontrolę dostępu.
Za pomocą RBAC można definiować role dla różnych stanowisk, które ograniczają dostęp do funkcji i danych w aplikacji.
Na przykład można utworzyć rolę "administrator" z pełnym dostępem do aplikacji, podczas gdy rola "gość" może wyświetlać tylko niektóre strony.
Możesz także tworzyć role niestandardowe, które mają dostęp do określonych funkcji, takich jak "menedżer zawartości" lub "specjalista ds. rozliczeń".
W ten sposób użytkownicy mają dostęp tylko do funkcjonalności, które są im potrzebne do wykonywania obowiązków służbowych.

This user profile page

Aby utworzyć RBAC w Laravel, użyjemy pakietu kompozytora "wnikk/laravel-access-rules", który zapewnia prosty i elastyczny sposób tworzenia ról i uprawnień.
Ten pakiet pozwala nam przypisywać role użytkownikom, przypisywać uprawnienia do ról i przypisywać uprawnienia bezpośrednio użytkownikom.
Omówimy kroki związane z konfigurowaniem pakietu, definiowaniem ról i uprawnień oraz przypisywaniem ich użytkownikom.
Postępując zgodnie z tym przewodnikiem krok po kroku, możesz łatwo zaimplementować RBAC w aplikacji Laravel i zapewnić bezpieczeństwo swoich kont użytkowników.

Od czego zaczniemy?

Aby uprościć proces implementacji uprawnień Laravel: Będziemy również korzystać z funkcji CRUD, która odnosi się do implementacji trwałej aplikacji:

  1. User Management - We create user management using Laravel 10. This allows for easier application of Laravel permission.
  2. Rules Management - Additionally, we implement rules management to limit access to content by defining a list of rules for the project.
  3. Permits and inheritance Management - Permissions management can be used to add roles to user accounts and assign Laravel permission to them.
  4. News Management - Finally, we can implement news management and apply Laravel permission with each role assigned to a user.

tworzenie, odczytywanie, aktualizowanie i usuwanie.
Zastosujemy CRUD do wszystkich modeli w naszym projekcie, aby umożliwić łatwe zarządzanie danymi.

Jeśli szukasz przykładów koncepcji omówionych w tym artykule, możesz je łatwo znaleźć w odpowiednim repozytorium GitHub. Po prostu przejdź do repozytorium i przejrzyj kod źródłowy, aby zobaczyć, jak przeprowadzono różne implementacje. Zapewni to lepsze zrozumienie, w jaki sposób koncepcje działają w praktyce i jak można je zastosować do własnych projektów.

Krok 1: Utwórz aplikację

Laravel Aby rozpocząć wdrażanie Laravel 10, pierwszym krokiem jest utworzenie nowej aplikacji Laravel. Aby to zrobić, otwórz terminal lub wiersz polecenia i zainicjuj tworzenie nowej aplikacji Laravel. Wykonując ten krok, będziesz na dobrej drodze do wdrożenia Laravel 10 i wszystkich jego funkcji w aplikacji internetowej: Krok 2:

composer create-project laravel/laravel rules-example

Zainstaluj pakiety

Następnie musimy zainstalować wymagany pakiet Wnikk dla reguł kontroli dostępu (ACR) i pakiet kontroli wizualnej. Można to łatwo zrobić, otwierając terminal i wykonując polecenia podane poniżej.

composer require wnikk/laravel-access-rules
composer require wnikk/laravel-access-ui

Aby wprowadzić zmiany w pakiecie Wnikk, musimy uruchomić polecenie, które generuje pliki konfiguracyjne, pliki migracji i pliki widoku. Wykonując ten krok, będziesz mógł dostosować pakiet do specyficznych wymagań aplikacji Laravel: Krok 3:

php artisan vendor:publish --provider="Wnikk\\LaravelAccessRules\\AccessRulesServiceProvider"
php artisan vendor:publish --provider="Wnikk\\LaravelAccessUi\\AccessUiServiceProvider"

Zaktualizuj model

użytkownika Teraz zintegrujemy ACR z naszym istniejącym modelem użytkownika. Ten krok jest ważny, aby upewnić się, że nasza aplikacja Laravel ma odpowiednią kontrolę dostępu Musimy tylko dodać do niego cechę HasPermissions: Krok 4:

use Wnikk\LaravelAccessRules\Traits\HasPermissions;

class User extends Model {
    // The User model requires this trait
    use HasPermissions;

Skonfiguruj połączenie

z bazą danych Na potrzeby tego przykładu użyjemy bazy danych plików SQLite. Aby rozpocząć, utwórz pusty plik o nazwie "./database/database.sqlite" i skonfiguruj połączenie z bazą danych, jak pokazano w podanym przykładzie.
Plik .env:

DB_CONNECTION=sqlite

W tym momencie możemy uruchomić polecenie migracji. Wykonując to polecenie, będziemy mogli utworzyć niezbędne tabele w naszej bazie danych plików SQLite, co pozwoli na wydajne zarządzanie danymi.

php artisan migrate

Teraz, gdy wdrożyliśmy działający system kontroli dostępu z pakietem ACR, kolejnym krokiem jest dodanie uprawnień do modeli w naszej aplikacji. Uprawnienia określają, jakie akcje może wykonywać użytkownik z określoną rolą na określonym zasobie.

Krok 5: Utwórz migrację

wiadomości Idąc dalej, naszym następnym krokiem będzie utworzenie migracji dla tabeli wiadomości. Aby wykonać to zadanie, wykonaj następujące polecenie, które wygeneruje niezbędny plik i pozwoli nam zdefiniować schemat tabeli:

php artisan make:migration create_news_table

Spowoduje to utworzenie nowego pliku migracji w katalogu database/migrations naszej aplikacji Laravel. Poniżej znajduje się kompletny kod niezbędny do zdefiniowania struktury tabeli, w tym różne pola i odpowiadające im typy:Teraz ponownie przeprowadzimy migrację: Krok 6:

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('news', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->integer('user_id');
            $table->string('name', 70);
            $table->string('description', 320)->nullable();
            $table->text('body');
            $table->softDeletes();
        });
    }
    public function down(): void
    {
        Schema::dropIfExists('news');
    }
};

php artisan migrate

Utwórz model Teraz utworzymy model

wiadomości, aby pobrać dane z tabeli wiadomości. Aby wygenerować model wiadomości, po prostu uruchom następujące polecenie Artisan. Spowoduje to utworzenie modelu grup dyskusyjnych w katalogu app\Models.

php artisan make:model News

Przykład kodu dla modelu News: Krok 7:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Class News
 *
 * @property $id
 * @property $user_id
 * @property $name
 * @property $description
 * @property $body
 */
class News extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'user_id',
        'name',
        'description',
        'body',
    ];
}

Utwórz siewcę

Teraz, gdy mamy już wszystkie wymagane tabele w naszej bazie danych, nadszedł czas, aby wypełnić je danymi testowymi i skonfigurować dla nich reguły.

1. Utwórz kilku nowych użytkowników:Plik źródłowy database\seeders\CreateUserSeeder.php:

php artisan make:seeder CreateUserSeeder

<?php
namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class CreateUserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        DB::table('users')->insert([
            'id' => 1,
            'name' => 'Test user 1',
            'email' => '[email protected]',
            'password' => Hash::make('12345'),
        ]);
        DB::table('users')->insert([
            'id' => 2,
            'name' => 'Test user 2',
            'email' => '[email protected]',
            'password' => Hash::make('password'),
        ]);
        DB::table('users')->insert([
            'name' => 'Test user 3',
            'email' => Str::random(10).'@mail.com',
            'password' => Hash::make(Str::random(10)),
        ]);
        DB::table('users')->insert([
            'name' => 'Test user 4',
            'email' => Str::random(10).'@mail.com',
            'password' => Hash::make(Str::random(10)),
        ]);
        DB::table('users')->insert([
            'name' => 'Test user 5',
            'email' => Str::random(10).'@mail.com',
            'password' => Hash::make(Str::random(10)),
        ]);
    }
}

2. Tworzymy wiele wpisów newsowych.

php artisan make:seeder NewsTableSeeder

Plik źródłowy database\seeders\NewsTableSeeder.php:

<?php
namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\News;

class NewsTableSeeder extends Seeder
{
    public function run(): void
    {
        News::create([
            'user_id' => 1,
            'name' => 'First news',
            'description' => 'Description of first news',
            'body' => 'Body content 1...',
        ]);
        News::create([
            'user_id' => 1,
            'name' => 'Second news',
            'description' => 'Description of second test news',
            'body' => 'Body content 2...',
        ]);
        News::create([
            'user_id' => 2,
            'name' => 'News of test user',
            'body' => 'Body content 3...',
        ]);
    }
}

3. Wiele reguł zostanie dodanych do systemu w celach testowych.

php artisan make:seeder CreateRulesSeeder

Same zasady:
Plik źródłowy database\seeders\CreateRulesSeeder.php:

<?php
namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Wnikk\LaravelAccessRules\AccessRules;

class CreateRulesSeeder extends Seeder
{
    public function run(): void
    {
        // example #1 - route middleware
        AccessRules::newRule('example1.viewAny', 'View all users on example1');

        // example #2 - check in action
        AccessRules::newRule('example2.view', 'View data of user on example2');

        // example #3 - check on action options
        AccessRules::newRule([
            'guard_name' => 'example3.update',
            'title' => 'Changing different user data on example3',
            'options' => 'required|in:name,email,password'
        ]);

        // example #4 - global resource
        AccessRules::newRule('viewAny', 'Global rule "viewAny" for example4');
        AccessRules::newRule('view', 'Global rule "view" for example4');
        AccessRules::newRule('create', 'Global rule "create" for example4');
        AccessRules::newRule('update', 'Global rule "update" for example4');
        AccessRules::newRule('delete', 'Global rule "delete" for example4');

        // example #5 - resource for controller
        AccessRules::newRule('Examples.Example5.viewAny', 'Rule for one Controller his action "viewAny" example5');
        AccessRules::newRule('Examples.Example5.view', 'Rule for one Controller his action "view" example5');
        AccessRules::newRule('Examples.Example5.create', 'Rule for one Controller his action "create" example5');
        AccessRules::newRule('Examples.Example5.update', 'Rule for one Controller his action "update" example5');
        AccessRules::newRule('Examples.Example5.delete', 'Rule for one Controller his action "delete" example5');

        // example #6 - magic self
        AccessRules::newRule(
            'example6.update',
            'Rule that allows edit all news',
        'An example of how to use a magic suffix ".self" on example6'
        );
        AccessRules::newRule('example6.update.self', 'Rule that allows edit only where user is author');

        // example #7 - Policy
        AccessRules::newRule('Example7News.test', 'Rule event "test" example7');

        // Final example, add control to the Access user interface
        $id = AccessRules::newRule('Examples.UserRules.main', 'View all rules, permits and inheritance');
        AccessRules::newRule('Examples.UserRules.rules', 'Working with Rules', null, $id, 'nullable|in:index,store,update,destroy');
        AccessRules::newRule('Examples.UserRules.roles', 'Working with Roles', null, $id, 'nullable|in:index,store,update,destroy');
        AccessRules::newRule('Examples.UserRules.inherit', 'Working with Inherit', null, $id, 'nullable|in:index,store,destroy');
        AccessRules::newRule('Examples.UserRules.permission', 'Working with Permission', null, $id, 'nullable|in:index,update');
    }
}

4. Tworzymy teraz rolę superadministratora.

z którego będą dziedziczyć wszystkie inne role użytkowników. W tym kroku należy ustawić trzy typy modeli, które mogą mieć uprawnienia w grupach plików ustawień domyślnych (config/access.php). W przypadku superadministratora użyjemy roles:

php artisan make:seeder CreateRootAdminRoleSeeder

Zezwolenia dla każdego modelu mogą być kontrolowane przez powiązanego z nim właściciela.
Plik źródłowy database\seeders\CreateRootAdminRoleSeeder.php:

<?php
namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Wnikk\LaravelAccessRules\AccessRules;

class CreateRootAdminRoleSeeder extends Seeder
{
    public function run(): void
    {
        $acr = new AccessRules;
        $acr->newOwner('Role', 'root', 'RootAdmin role');

        // For example #1
        $acr->addPermission('example1.viewAny');

        // For example #2
        $acr->addPermission('example2.view');

        // For example #3
        $acr->addPermission('example3.update', 'name');
        $acr->addPermission('example3.update', 'email');
        $acr->addPermission('example3.update', 'password');

        // For example #4
        $acr->addPermission('viewAny');
        $acr->addPermission('view');
        $acr->addPermission('create');
        $acr->addPermission('update');
        $acr->addPermission('delete');

        // For example #5
        $acr->addPermission('Examples.Example5.viewAny');
        $acr->addPermission('Examples.Example5.view');
        $acr->addPermission('Examples.Example5.create');
        $acr->addPermission('Examples.Example5.update');
        $acr->addPermission('Examples.Example5.delete');

        // For example #6
        //For all - $acr->addPermission('example6.update');
        $acr->addPermission('example6.update.self');

        // For example #7
        $acr->addPermission('Example7News.test');

        // For final example
        $acr->addPermission('Examples.UserRules.index');
        $acr->addPermission('Examples.UserRules.rules');
        $acr->addPermission('Examples.UserRules.rules', 'index');
        $acr->addPermission('Examples.UserRules.rules', 'store');
        $acr->addPermission('Examples.UserRules.rules', 'update');
        $acr->addPermission('Examples.UserRules.rules', 'destroy');
        $acr->addPermission('Examples.UserRules.roles');
        $acr->addPermission('Examples.UserRules.roles', 'index');
        $acr->addPermission('Examples.UserRules.roles', 'store');
        $acr->addPermission('Examples.UserRules.roles', 'update');
        $acr->addPermission('Examples.UserRules.roles', 'destroy');
        $acr->addPermission('Examples.UserRules.inherit');
        $acr->addPermission('Examples.UserRules.inherit', 'index');
        $acr->addPermission('Examples.UserRules.inherit', 'store');
        $acr->addPermission('Examples.UserRules.inherit', 'destroy');
        $acr->addPermission('Examples.UserRules.permission');
        $acr->addPermission('Examples.UserRules.permission', 'index');
        $acr->addPermission('Examples.UserRules.permission', 'update');
    }
}

5. Na koniec dodamy dziedziczenie uprawnień od superadministratora do wszystkich użytkowników.

php artisan make:seeder AddRoleToAllUserSeeder

Plik źródłowy database\seeders\AddRoleToAllUserSeeder.php: Przejdźmy teraz do importowania wszystkich instrukcji utworzonych w tym kroku:

<?php
namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\User;

class AddRoleToAllUserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $all = User::all();
        foreach ($all as $one) $one->inheritPermissionFrom('Role', 'root');

        // or
        // $acr = new AccessRules;
        // $acr->setOwner('Role', 'root');
        // foreach ($all as $one) $one->inheritPermissionFrom($acr);

        // or
        // $mainUser = User::find(1);
        // foreach ($all as $one) $one->inheritPermissionFrom($mainUser);
    }
}

php artisan db:seed --class=CreateUserSeeder
php artisan db:seed --class=NewsTableSeeder
php artisan db:seed --class=CreateRulesSeeder
php artisan db:seed --class=CreateRootAdminRoleSeeder
php artisan db:seed --class=AddRoleToAllUserSeeder

Aby wykonać te same manipulacje kontrolą dostępu i dziedziczeniem, o których mowa powyżej, alternatywną metodą jest użycie interfejsu, który został dodany na początku tego artykułu. Możesz uzyskać dostęp do interfejsu, otwierając adres "/accessui/" w swoim projekcie:Lista wszystkich ról, grup i użytkowników:


List all roles, group and user

Wymień wszystkie reguły:
List all rules

Przejdźmy do najciekawszej części ☕

: Różne metody weryfikacji dostępu i reguły z nimi związane.
Idąc dalej, kontrolery użyte w przykładach będą służyć jako źródła danych JSON dla SPA Frontend, eliminując tym samym potrzebę tworzenia szablonów.
W każdym razie zawsze możemy zobaczyć wynik weryfikacji reguły.

Przykład 1

W tym przykładzie używamy oprogramowania pośredniczącego do routingu, aby ograniczyć dostęp do kontrolera.

Apped to file routes\web.php:

Route::get('/example1', [Example1Controller::class, 'index'])->middleware('can:example1.viewAny');

W tym przykładzie kontroler nie jest używany
Źródło do pliku ... Przykład1Kontroler.php:

<?php
namespace App\Http\Controllers\Examples;

use Illuminate\Support\Facades\Response;
use App\Http\Controllers\Controller;
use App\Models\User;

class Example1Controller extends Controller
{
    public function index()
    {
        return Response::json(User::all(), 200);
    }
}

Sprawdź, co się stało:
Example1 Response

Przykład 2

Let's try to check the permission in the action itself
Apped to file routes\web.php:

Route::get('/example2', [Example2Controller::class, 'show']);

Source file ... Example2Controller.php:

<?php
namespace App\Http\Controllers\Examples;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Gate;
use App\Http\Controllers\Controller;

class Example2Controller extends Controller
{
    public function show()
    {
        Gate::authorize('example2.view');

        return Response::json(Auth::user()->toArray(), 200);
    }
}

Tak jak w przykładzie, możesz użyć wszystkich możliwości fasady Laravel Gate.

Przykład 3

This is quite similar to the previous example, but with a separate list of options.
Apped to file routes\web.php:

Route::any('/example3/{frm}', [Example3Controller::class, 'update']);

Source file ... Example3Controller.php:

<?php
namespace App\Http\Controllers\Examples;

use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Hash;
use App\Http\Controllers\Controller;
use App\Enum\UserProfileFormEnum;

class Example3Controller extends Controller
{
    public function update(UserProfileFormEnum $frm, Request $request)
    {
        // Add the check by indicating after the point of the [Option] field
        Gate::authorize('example3.update.'.$frm->value);

        $user = Auth::user();
        switch ($frm)
        {
            case(UserProfileFormEnum::Name):

                if($request->name) $user->fill( $request->only(['name']) );

            break;
            case(UserProfileFormEnum::Password):

                if($request->password) $user->password = Hash::make($request->password);

            break;
            case(UserProfileFormEnum::Email):

                $validator = Validator::make($request->all(), [
                    'email' => 'required|email',
                ]);
                if ($validator->fails()) abort('403', $validator->messages());

                $user->email = $request->email;

            break;
        }

        return Response::json($user->save(), 200);
    }
}

Dlaczego występuje to zachowanie i czym " Opcja" różni się od standardowej definicji reguły ?

Warto zauważyć, że pole "Opcja" nie jest powiązane z regułą, ale z samym uprawnieniem.
Ma to na celu umożliwienie tworzenia wielu uprawnień w ramach jednej reguły. Na przykład
możliwe jest pobranie wszystkich rekordów według identyfikatorów, które mają dostęp, bez konieczności tworzenia oddzielnych tabel lub pól.

Przykład 4

In this example, we will utilize the built-in $this->authorizeResource() function which comes with the resource feature. This function is very convenient as it automatically creates checks for each action against the following rules: "viewAny", "view", "create", "update" and "delete".
Apped to file routes\web.php:

Route::apiResource('example4', Example4Controller::class)->parameters([
    'example4' => 'news'
]);

Source file ... Example4Controller.php:

<?php
namespace App\Http\Controllers\Examples;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use App\Http\Controllers\Controller;
use App\Models\News;

class Example4Controller extends Controller
{
    /**
     * Create the controller instance.
     */
    public function __construct()
    {
        $this->authorizeResource(News::class, 'News');
    }

    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        return Response::json(News::all());
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $news = News::create($request->toArray());

        return Response::json($news->id, 201);
    }

    /**
     * Display the specified resource.
     */
    public function show(News $news)
    {
        return Response::json($news->toArray());
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, News $news)
    {
        $news->fill($request->toArray());

        return Response::json($news->save);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(News $news)
    {
        return Response::json($news->delete());
    }
}

Przykład 5

W poprzednim przykładzie użyliśmy reguł globalnych, co nie jest zbyt wygodne. Aby temu zaradzić, możemy wprowadzić funkcjonalność tworzenia reguł dla każdego kontrolera osobno.

Aby to osiągnąć, musimy zmodyfikować główny kontroler, W tym celu stworzymy cechę.
Plik źródłowy App\Http\Traits\GuardedController.php:

<?php
namespace App\Http\Traits;

use App\Http\Controllers\Controller;

trait GuardedController
{
    /**
     * Map of resource methods to ability names
     * @example ['index' => 'viewAny']
     *
     * @var string[]
     */
    //abstract protected $guardedMethods = [];

    /**
     * Do not automatically scan all available methods.
     *
     * @var bool
     */
    //abstract protected $disableAutoScanGuard = true;

    /**
     * List of resource methods which do not have model parameters.
     * @example ['index']
     *
     * @var string[]
     */
    //abstract protected $methodsWithoutModels = ['index'];

    /**
     * Get the map of resource methods to ability names.
     *
     * @return array
     */
    protected function resourceAbilityMap()
    {
        if (empty($this->disableAutoScanGuard)) {
            $methods = array_diff(
                get_class_methods($this),
                get_class_methods(Controller::class)
            );
            $map = array_combine($methods, $methods);
        } else {
            $map = [];
        }

        $map = array_merge($map, parent::resourceAbilityMap());
        $map = array_merge($map, $this->guardedMethods??[]);

        // Replace name for class App\Http\Controllers\Examples\Example1Controller
        // to guard prefix "Examples.Example1."
        $name = $this->getClassNameGate();

        // Replace standard rule "viewAny" to "Examples.Example1.viewAny"
        foreach ($map as &$item) {$item = $name.$item;}
        unset($item);

        return $map;
    }

    /**
     * Get the list of resource methods which do not have model parameters.
     *
     * @return array
     */
    protected function resourceMethodsWithoutModels()
    {
        $base = parent::resourceMethodsWithoutModels();

        return array_merge($base, $this->methodsWithoutModels??[]);
    }

    /**
     * Get name off class witch namespace for guard
     *
     * @param string|null $action
     * @return string
     */
    protected static function getClassNameGate(?string $action = null): string
    {
        // Replace name for class App\Http\Controllers\Examples\Example1Controller
        // to guard prefix "Examples.Example1."
        $name = str_replace([
            dirname(__TRAIT__, 2).DIRECTORY_SEPARATOR.'Controllers'.DIRECTORY_SEPARATOR,
            DIRECTORY_SEPARATOR,
            'Controller'
        ], [
            '', '.', '.'
        ], static::class);

        return $name.$action;
    }
}

Controller i jego przykład pozostają dokładnie takie same jak w poprzednim.
Dodano tylko cechę (Plik ... Example5Controller.php) :Ale błąd jest już inny:

<?php
namespace App\Http\Controllers\Examples;
...
use App\Http\Traits\GuardedController;

class Example5Controller extends Controller
{
    use GuardedController;

    public function __construct()
    {
        $this->authorizeResource(News::class, 'News');
    }
...


Example5 Response

Tak więc, przy minimalnych modyfikacjach istniejącego kodu,
Można łatwo włączyć funkcje dynamicznej kontroli dostępu.

Przykład 6

Here is a fairly simple example that is similar to the second one.
Through a few subtle differences, there is a magical behavior present here.
Apped to file routes\web.php:

Route::any('/example6/{news}', [Example6Controller::class, 'update']);

Source file ... Example6Controller.php:

<?php
namespace App\Http\Controllers\Examples;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Gate;
use App\Http\Controllers\Controller;
use App\Models\News;

class Example6Controller extends Controller
{
    public function update(Request $request, News $news)
    {
        Gate::authorize('example6.update', $news);

        $news->fill($request->toArray());

        return Response::json($news->save?1:0);
    }
}

Przyjrzyjmy się temu bliżej. Jeśli mamy regułę "example6.update.self", musimy określić systemowi, aby sprawdzić regułę "example6.update" i dodać ".self" przekazać obiekt rekordu do sprawdzenia pod maską ACR.

Praca ACR będzie wyglądała tak:Dodatkowo warto zaznaczyć, że jeśli nie sprawdzamy użytkownika, a inny podmiot, np. moderator, *ACR* to śledzi, a sprawdzenie będzie wyglądało tak pod maską:

if (
    Gate::allows('example6.update.self')
    && $user->id === $news->user_id
) {
    return true;
}

$moderator = App\Models\Moderator::find('...');
if (
    Gate::forUser($moderator)->allows('example6.update.self')
    && $moderator->uuid === $news->moderator_uuid
) {
    return true;
}

Nie ma już potrzeby wykonywania takich kontroli, gdyż magiczna metoda obsłuży je automatycznie.

Przykład 7

Chociaż nie jest to ABAC, niezbędną funkcjonalność kontroli dostępu opartą na atrybutach można osiągnąć, dodając wbudowany mechanizm polityki Laravel.

Wszystkie poprzednie przykłady koncentrowały się na sprawdzaniu dostępu do kontrolera, ale możemy użyć tego samego podejścia w "Polityce" do zaimplementowania kontroli dostępu dla atrybutów ze wszystkimi ich odmianami.
W tym celu konieczne jest wygenerowanie polityki dla naszego pliku model:

php artisan make:policy NewsPolicy –model=News

Source ... NewsPolicy.php:

<?php
namespace App\Policies;

use App\Models\News;
use App\Models\User;
use Illuminate\Auth\Access\Response;

class NewsPolicy
{
    public function availableUpdateOnSomeTime(User $user, News $news): ?bool
    {
        if(
            $user->can('Example7News.allowedEditLast24Hours', $news)
            && stripos($user->name, 'author') !== false
            && ($news->created_at->isToday() || $news->created_at->isYesterday())
        ) {
            return true;
        }
        return null;
    }
}

Następnie konieczna będzie aktualizacja w klasie AuthServiceProvider $policies, jak napisano poniżej:Teraz sprawdź politykę w kontrolerze:

protected $policies = [
    News::class => NewsPolicy::class,
];


Plik źródłowy ... Example7Controller.php:

<?php
namespace App\Http\Controllers\Examples;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use App\Http\Controllers\Controller;
use App\Models\News;

class Example7Controller extends Controller
{

    public function index(News $news)
    {
        $this->authorize('availableUpdateOnSomeTime', $news);

        return Response::json($news->toArray());
    }
}

Dlatego mamy teraz możliwość sprawdzania nie tylko reguł indywidualnie, ale także weryfikowania atrybutów modelu.

Jest to ważne - jeśli dodasz regułę "availableUpdateOnSomeTime" i uprawnienia do użytkownika, polityka nie zostanie sprawdzona.

Ostatni przykład ⚡

W stanie domyślnym interfejs AccessUi nie zawiera żadnych kontroli dotyczących poziomu udzielonego dostępu.
Aby rozwiązać ten problem, możemy utworzyć kontroler proxy w tym przykładzie, który zweryfikuje wszystkie uprawnienia przed wykonaniem jakiejkolwiek manipulacji danymi.
Najpierw wyłącz standardowe trasy AccessUI,
Plik config/accessUi.php:Następnie utworzymy 2 kontrolery:

/**
     * Panel Register
     *
     * This manages if routes used for the admin panel should be registered.
     * Turn this value to false if you don't want to use admin panel
     */
    'register' => false,

"UserRulesController" i "UserProfileController", który używa cechy "RunsAnotherController" do uruchamiania innych kontrolerów AccessUi.
Sama implementacja jest wyświetlana w plikach "user-rules.blade.php" i "user-profile.blade.php".
Pliki mogą być nieco długie, ale można je przeglądać osobno w repozytorium.

W rezultacie będziemy mieli indywidualne strony w naszym stylu z weryfikacją praw dostępu:Strona profilu autoryzowanego użytkownika:


This user profile page

Lista reguł i tylko role (ukryty użytkownik na stronie właściciela):
Rules list and only roles list

Podsumowując, wdrożenie "wnikk/laravel-access-rules" (ACR, ACL, RBAC) w Laravel jest potężnym sposobem na zapewnienie, że użytkownicy otrzymają dostęp tylko do zasobów, do których są upoważnieni. Z pomocą wbudowanego oprogramowania pośredniczącego i funkcji autoryzacji Laravel możliwe jest łatwe tworzenie i zarządzanie złożonymi zasadami kontroli dostępu zarówno na poziomie globalnym, jak i specyficznym dla kontrolera. Korzystając z Access-Control-Rules, programiści mogą dodawać dynamiczne funkcje kontroli dostępu do swoich aplikacji Laravel przy minimalnych zmianach kodu, zapewniając jednocześnie, że aplikacja pozostaje bezpieczna i łatwa w utrzymaniu.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297