Implementação de Permissões no Laravel
Introdução
O Laravel fornece políticas para definir a lógica de autorização em sua aplicação. Essas políticas estabelecem as regras que determinam quais usuários estão autorizados a realizar ações específicas nos modelos de sua aplicação. Neste guia, discutiremos as etapas necessárias para implementar permissões detalhadas em uma aplicação Laravel usando um esquema de banco de dados, um trait e uma política.
Pré-requisitos
Antes de continuar, verifique se você tem uma aplicação Laravel configurada e funcionando. Você também deve estar familiarizado com migrações, modelos e políticas do Laravel.
Esquema do Banco de Dados
Para implementar permissões em uma aplicação Laravel, crie um esquema de banco de dados que inclua tabelas para funções, permissões e seus relacionamentos. O exemplo a seguir mostra como criar essas tabelas usando migrações do Laravel:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRolesTable extends Migration
{
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('roles');
}
}
class CreatePermissionsTable extends Migration
{
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('permissions');
}
}
class CreateRolePermissionsTable extends Migration
{
public function up()
{
Schema::create('role_permissions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('role_id');
$table->unsignedBigInteger('permission_id');
$table->timestamps();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('role_permissions');
}
}
Neste exemplo, criamos tabelas para funções, permissões e seus relacionamentos. A tabela roles
inclui um ID, um nome, uma descrição e timestamps. A tabela permissions
consiste em um ID, um nome, uma descrição e timestamps. A tabela role_permissions
contém um ID, um role_id
, um permission_id
e timestamps. As colunas role_id
e permission_id
são chaves estrangeiras que fazem referência às colunas id
nas tabelas roles
e permissions
, respectivamente.
Esquemas de Modelos
Para fornecer dicas de tipo e melhorar a legibilidade do código ao interagir com os modelos Role
, Permission
e User
, criamos classes <Model>Schema
correspondentes. Essas classes definem constantes que representam os nomes das colunas nas tabelas de banco de dados correspondentes.
Por exemplo, aqui está a classe PermissionSchema
:
<?php
namespace App\Schemas;
class PermissionSchema
{
const id = 'id';
const name = 'name';
const description = 'description';
const created_at = 'created_at';
const updated_at = 'updated_at';
const deleted_at = 'deleted_at';
}
No exemplo acima, estamos definindo constantes para cada uma das colunas na tabela de permissões. Então, podemos usar essas constantes para fornecer dicas de tipo e melhorar a legibilidade do código ao interagir com o modelo Permission
e seus dados associados.
Posteriormente, usaremos esses esquemas para definir o nome das colunas ao interagir com os modelos. Por exemplo, no trait HasPermissions
que criaremos mais adiante, usamos a constante PermissionSchema::name
para fazer referência à coluna name
na tabela de permissões:
use App\Schemas\PermissionSchema;
// ...
public function hasPermission(string $permission): bool
{
return $this->roles->some(function ($role) use ($permission) {
return $role->permissions->contains(PermissionSchema::name, $permission);
});
}
Ao usar PermissionSchema::name
em vez da string crua "name", estamos tornando nosso código mais legível e menos propenso a erros. Além disso, se alguma vez mudássemos o nome da coluna name
na tabela de permissões, poderíamos simplesmente atualizar a constante PermissionSchema::name
para refletir o novo nome, e todas as referências a essa constante em nosso código também seriam atualizadas automaticamente.
Ao usar esquemas de modelos dessa maneira, podemos facilitar a manutenção e o trabalho com nosso código ao longo do tempo.
Modelos
Para implementar permissões detalhadas em uma aplicação Laravel, crie três modelos: Role
, Permission
e User
. Esses modelos interagem com as tabelas de banco de dados que armazenam informações sobre funções, permissões e relacionamentos usuário/função.
Exemplo do modelo Role
:
namespace App\Models;
use App\Schemas\RolePermissionSchema;
use App\Schemas\UserRoleSchema;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
use HasFactory;
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, UserRoleSchema::table)->withTimestamps();
}
public function permissions(): BelongsToMany
{
return $this->belongsToMany(Permission::class, RolePermissionSchema::table)->withTimestamps();
}
}
No exemplo do modelo Role
acima, usamos o relacionamento BelongsToMany
para definir o relacionamento entre os modelos Role
e User
, bem como o relacionamento entre os modelos Role
e Permission
.
Exemplo do modelo Permission
:
namespace App\Models;
use App\Schemas\RolePermissionSchema;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Permission extends Model
{
use HasFactory;
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, RolePermissionSchema::table)->withTimestamps();
}
}
No exemplo do modelo Permission
acima, usamos o relacionamento BelongsToMany
para definir o relacionamento entre os modelos Permission
e Role
.
Finalmente, exemplo do modelo User
:
namespace App\Models;
use App\Schemas\UserRoleSchema;
use App\Traits\HasPermissions;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, HasPermissions;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, UserRoleSchema::table)->withTimestamps();
}
}
No exemplo do modelo User
acima, usamos o relacionamento BelongsToMany
para definir o relacionamento entre os modelos User
e Role
. Além disso, usamos vários traits e propriedades para lidar com autenticação, notificações e permissões.
Trait de Verificação de Permissões
Para simplificar a verificação de permissões em uma política, crie um trait que adicione um método hasPermission()
à classe que o usa. O exemplo a seguir mostra como criar esse trait:
<?php
namespace App\Traits;
use App\Schemas\PermissionSchema;
trait HasPermissions
{
public function hasPermission(string $permission): bool
{
return $this->roles->some(function ($role) use ($permission) {
return $role->permissions->contains(PermissionSchema::name, $permission);
});
}
}
Este método simplifica o processo de verificar se um usuário tem uma permissão específica.
O método hasPermission
recebe um parâmetro de string, $permission
, e retorna um valor booleano. Ele verifica se o usuário atual tem a permissão especificada examinando todas as funções associadas ao usuário.
Se nenhuma das funções possuir a $permission
necessária, a função some
retorna false
, indicando que o usuário não tem a permissão necessária.
Ao incorporar esse trait em seu modelo User
, você pode verificar facilmente se um usuário tem uma permissão específica.
Implementando Permissões Detalhadas com Políticas
Para aplicar permissões detalhadas usando o modelo User
, crie uma classe de política para cada modelo ao qual você deseja controlar o acesso. Neste exemplo, criaremos uma PostPolicy
para gerenciar o acesso ao modelo Post
.
Primeiro, crie a classe PostPolicy
:
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return $user->hasPermission('viewAny-posts');
}
public function view(User $user, Post $post)
{
return $user->hasPermission('view-posts') && $post->user_id === $user->id;
}
public function create(User $user)
{
return $user->hasPermission('create-posts');
}
public function update(User $user, Post $post)
{
return $user->hasPermission('update-posts') && $post->user_id === $user->id;
}
public function delete(User $user, Post $post)
{
return $user->hasPermission('delete-posts') && $post->user_id === $user->id;
}
}
Observe que usamos o trait hasPermission
adicionado ao modelo User
.
Em seguida, registre a política no AuthServiceProvider
. Aqui está um exemplo do registro da PostPolicy
:
namespace App\Providers;
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Post::class => PostPolicy::class,
];
public function boot()
{
$this->registerPolicies();
}
}
Neste exemplo, registramos a PostPolicy
para o modelo Post
. Isso diz ao Laravel para usar a PostPolicy
para controlar o acesso às instâncias do modelo Post
.
Agora, você pode usar essa política em seus controladores para autorizar ações. Aqui está um exemplo de uso da PostPolicy
em um controlador:
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function edit(Post $post)
{
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// Update the post...
}
}
Neste exemplo, usamos o método authorize()
para verificar se o usuário autenticado tem permissão para atualizar a postagem fornecida, usando o método update
da PostPolicy
. Se o usuário estiver autorizado, permitimos que ele edite ou atualize a postagem. Caso contrário, retornamos um erro HTTP 403 Forbidden.
Ao usar políticas para controlar o acesso aos seus modelos, você garante que apenas usuários autorizados possam executar ações confidenciais em sua aplicação.