W Laravel role i uprawnienia były przez lata jednym z najbardziej mylących tematów. Przede wszystkim dlatego, że nie ma na ten temat dokumentacji: te same rzeczy „chowają się” pod innymi terminami we frameworku, np. „bramy”, „polityki”, „strażnicy” itp. W tym artykule postaram się je wszystkie wyjaśnić w „język ludzki”.
Brama jest taka sama jak Pozwolenie
Moim zdaniem jednym z największych nieporozumień jest termin „brama”. Myślę, że programiści uniknęliby wielu nieporozumień, gdyby nazywano ich tym, czym są.
Bramki to uprawnienia, nazywane po prostu innym słowem.
Jakie są typowe działania, które musimy wykonać z uprawnieniami?
- Define the permission, ex. "manage_users"
- Check the permission on the front-end, ex. show/hide the button
- Check the permission on the back-end, ex. can/can't update the data
Tak, zamień słowo „pozwolenie” na „brama”, a wszystko zrozumiesz.
Prosty przykład Laravela to:
aplikacja/Providers/AppServiceProvider.php:
use App\Models\User;
use Illuminate\Support\Facades\Gate;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Should return TRUE or FALSE
Gate::define('manage_users', function(User $user) {
return $user->is_admin == 1;
});
}
}
zasoby/widoki/navigation.blade.php:
<ul>
<li>
<a href="{{ route('projects.index') }}">Projects</a>
</li>
@can('manage_users')
<li>
<a href="{{ route('users.index') }}">Users</a>
</li>
@endcan
</ul>
trasy/web.php:
Route::resource('users', UserController::class)->middleware('can:manage_users');
Teraz wiem, że technicznie Gate może oznaczać więcej niż jedno pozwolenie.Tak więc zamiast "manage_users" możesz zdefiniować coś takiego jak "admin_area". Ale w większości przykładów, które widziałem, Gate jest synonimem zezwolenia.
Ponadto, w niektórych przypadkach uprawnienia są nazywane „zdolnościami”, jak w pakietu Bouncer. Oznacza to również to samo - zdolność/pozwolenie na jakąś akcję.Przejdziemy do pakietów w dalszej części tego artykułu.
Różne sposoby sprawdzania uprawnień bramki
Kolejnym źródłem nieporozumień jest to, jak/gdzie sprawdzić Bramę. Jest tak elastyczny, że możesz znaleźć bardzo różne przykłady. Przeanalizujmy je:
Opcja 1. Trasy: oprogramowanie pośredniczące('can:xxxxxx')
To jest przykład z góry.Bezpośrednio na trasie/grupie możesz przypisać oprogramowanie pośredniczące:
Route::post('users', [UserController::class, 'store'])
->middleware('can:create_users');
Opcja 2.
Opcja 3.Gate::allows() lub Gate::denies() Innym sposobem jest użycie fasady bramy: Lub odwrotnie: Lub krótsza droga do przerwania, z pomocnikami: Opcja 4. Kontroler: autoryzuj() Jeszcze krótszą opcją i moją ulubioną jest użycie Opcja 5. Klasa żądania formularza: Zauważyłem, że wielu programistów generuje klasy Form Request tylko w celu zdefiniowania reguły walidacji, całkowicie ignorując pierwszą metodę tej klasy, którą jest Możesz go użyć również do sprawdzenia bramek. W ten sposób uzyskujesz separację obaw, co jest dobrą praktyką w przypadku solidnego kodu, więc Kontroler nie zajmuje się walidacją, ponieważ jest skończona w dedykowanej klasie Form Request. A następnie w formularzu Żądanie: Jeśli twoje uprawnienia mogą być przypisane do modelu Eloquent, w typowym kontrolerze CRUD, możesz zbudować wokół nich klasę Policy. Jeśli uruchomimy to polecenie: Wygeneruje plik app/Policies/UserPolicy.php, z domyślnymi metodami, które mają komentarz wyjaśniający ich cel: W każdej z tych metod definiujesz warunek zwrotu prawda/fałsz. Tak więc, jeśli będziemy postępować zgodnie z tymi samymi przykładami, co wcześniej Gates, możemy to zrobić: Następnie możesz sprawdzić Politykę w bardzo podobny sposób jak w Gates: Więc określasz nazwę metody i nazwę klasy polityki. Innymi słowy, zasady to tylko kolejny sposób grupowania uprawnień, a nie Gates. Jeśli Twoje działania dotyczą głównie CRUDów lub Modeli, Zasady są prawdopodobnie wygodniejszą i lepiej ustrukturyzowaną opcją niż Gates. Omówmy kolejne zamieszanie: w dokumentacji Laravela nie znajdziesz żadnej sekcji dotyczącej ról użytkowników.
Z punktu widzenia ramowego nie ma „roli”, tylko bramki/zasady, według których można grupować w dowolny sposób. Innymi słowy, rola jest bytem POZA frameworkiem Laravel, więc musimy sami zbudować strukturę roli.Może to być częścią ogólnego zamieszania związanego z uwierzytelnianiem, ale ma to sens, ponieważ powinniśmy kontrolować sposób definiowania ról: Tak więc funkcjonalność roli to kolejna warstwa twojej aplikacji Laravel. W tym miejscu dochodzimy do pakietów Laravela, które mogą pomóc. Ale możemy również tworzyć role bez żadnego pakietu: Ostatni bit jest najważniejszy. Więc zamiast: Zrobiłbyś coś takiego: Ponownie, tutaj masz kilka opcji sprawdzenia ról.W powyższym przykładzie zakładamy, że istnieje relacja Ale jeśli wolisz być elastyczny, możesz za każdym razem przeszukiwać bazę danych: Pamiętaj jednak, aby szybko załadować relację „roli”, w przeciwnym razie możesz łatwo natknąć się na N +1 problem z zapytaniem tutaj. Z mojego osobistego doświadczenia wynika, że zwykły model budowania tego wszystkiego razem jest następujący: Innymi słowy, nie sprawdzamy dostępu według ról. Rola to tylko „sztuczna” warstwa, zestaw uprawnień, które są przekształcane w Gates podczas cyklu życia aplikacji. Och, te. Przez lata powodują tyle zamieszania.Wielu programistów uważało, że Strażnicy są rolami i zaczęli tworzyć oddzielne tabele bazy danych, takie jak „administratorzy”, a następnie przypisywać ich jako Strażników. Częściowo, ponieważ w dokumentacji można znaleźć fragmenty kodu, takie jak Tak więc strażnicy są bardziej globalną koncepcją niż role.
Looks complicated?
public function store(Request $request)
{
if (!$request->user()->can('create_users'))
abort(403);
}
}
public function store(Request $request)
{
if ($request->user()->cannot('create_users'))
abort(403);
}
}
public function create()
{
if (!auth()->user()->can('create_users'))
abort(403);
}
}
public function store(Request $request)
{
if (!Gate::allows('create_users')) {
abort(403);
}
}
authorize()
w kontrolerach.W przypadku niepowodzenia automatycznie zwróci stronę 403.public function store(Request $request)
{
if (Gate::denies('create_users')) {
abort(403);
}
}
public function store(Request $request)
{
abort_if(Gate::denies('create_users'), 403);
}
authorize()
.public function store(Request $request)
{
$this->authorize('create_users');
}
Zasady: zestaw uprawnień na podstawie modelu
public function store(StoreUserRequest $request)
{
// No check is needed in the Controller method
}
class StoreProjectRequest extends FormRequest
{
public function authorize()
{
return Gate::allows('create_users');
}
public function rules()
{
return [
// ...
];
}
}
php artisan make:policy ProductPolicy --model=Product
use App\Models\Product;
use App\Models\User;
class ProductPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user)
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Product $product)
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user)
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Product $product)
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Product $product)
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Product $product)
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Product $product)
{
//
}
}
class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
Rola: uniwersalny zestaw uprawnień
public function store(Request $request)
{
$this->authorize('create', Product::class);
}
belongsTo
od użytkownika do roli, a także istnieją stałe w modelu roli, takie jak < code>ADMIN = 1, jak EDITOR = 2
, aby uniknąć zbyt wielu zapytań do bazy danych.class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
class ProductPolicy
{
public function create(User $user)
{
return $user->role_id == Role::ADMIN;
}
Uelastycznienie: uprawnienia zapisane w DB
class ProductPolicy
{
public function create(User $user)
{
return $user->role->name == 'Administrator';
}
Auth::guard('admin') ->próba($credentials))
foreach
loop from all permissions from DB, and run a Gate::define()
statement for each of them, returning true/false based on the role;@can('permission_name')
and $this->authorize('permission_name')
, like in the examples above.$roles = Role::with('permissions')->get();
$permissionsArray = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permissions) {
$permissionsArray[$permissions->title][] = $role->id;
}
}
// Every permission may have multiple roles assigned
foreach ($permissionsArray as $title => $roles) {
Gate::define($title, function ($user) use ($roles) {
// We check if we have the needed roles among current user's roles
return count(array_intersect($user->roles->pluck('id')->toArray(), $roles)) > 0;
});
}
$user->givePermissionTo('edit articles');
$user->assignRole('writer');
$role->givePermissionTo('edit articles');
$user->can('edit articles');
Bouncer::allow($user)->to('create', Post::class);
Bouncer::allow('admin')->to('ban-users');
Bouncer::assign('admin')->to($user);