Laravel 权限实现
简介
Laravel 提供策略来定义应用程序的授权逻辑。这些策略建立了规则,规定哪些用户有权对应用程序的模型执行特定操作。在本指南中,我们将讨论使用数据库架构、特征和策略在 Laravel 应用程序中实现细粒度权限所需的步骤。
先决条件
在继续之前,请确保您已设置并运行了 Laravel 应用程序。您还应该熟悉 Laravel 迁移、模型和策略。
数据库架构
要在 Laravel 应用程序中实现权限,请创建一个包含角色、权限及其关系的数据库架构。以下示例演示如何使用 Laravel 迁移创建这些表:
php artisan make:model Role -m
<?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('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* 回滚迁移。
*/
public function down(): void
{
Schema::dropIfExists('roles');
}
};
php artisan make:model Permission -m
<?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('permissions', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* 回滚迁移。
*/
public function down(): void
{
Schema::dropIfExists('permissions');
}
};
php artisan make:model RolePermission -m
<?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('role_permissions', function (Blueprint $table) {
$table->id();
$table->foreignId('role_id')->constrained('roles')->onDelete('cascade');
$table->foreignId('permission_id')->constrained('permissions')->onDelete('cascade');
$table->softDeletes();
$table->timestamps();
});
}
/**
* 回滚迁移。
*/
public function down(): void
{
Schema::dropIfExists('role_permissions');
}
};
php artisan make:model UserRole -m
<?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('user_roles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->foreignId('role_id')->constrained('roles')->onDelete('cascade');
$table->timestamps();
$table->softDeletes();
});
}
/**
* 回滚迁移。
*/
public function down(): void
{
Schema::dropIfExists('user_roles');
}
};
在这个例子中,我们创建了角色、权限及其关系的表。roles
表包括 ID、名称、描述和时间戳。permissions
表包括 ID、名称、描述和时间戳。role_permissions
表包含 ID、role_id
、permission_id
和时间戳。role_id
和 permission_id
列是外键,分别引用 roles
和 permissions
表中的 id
列。
模型架构
为了在与 Role
、Permission
和 User
模型交互时提供类型提示并提高代码可读性,我们创建了相应的 <Model>Schema
类。这些类定义了表示相应数据库表中列名的常量。
例如,这是 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';
}
在上面的例子中,我们为权限表中的每一列定义了常量。然后,我们可以使用这些常量来提供类型提示,并在与 Permission
模型及其相关数据交互时提高代码可读性。
我们稍后将使用这些架构来定义与模型交互时列的名称。例如,在我们稍后创建的 HasPermissions
特征中,我们使用 PermissionSchema::name
常量来引用权限表中的 name
列:
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);
});
}
通过使用 PermissionSchema::name
而不是原始字符串 "name",我们使我们的代码更具可读性,并且更不容易出错。此外,如果我们 ever 需要更改权限表中 name
列的名称,我们只需更新 PermissionSchema::name
常量以反映新名称,我们代码中对该常量的所有引用都会自动更新。
通过这种方式使用模型架构,我们可以使我们的代码更易于维护,并且随着时间的推移更容易使用。
模型
要在 Laravel 应用程序中实现细粒度权限,请创建四个模型:Permission
、Role
、RolePermission
和 UserRole
。这些模型与存储角色、权限和用户/角色关系信息的数据库表交互。
这是 Role
模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* @property int $id
* @property string $name
* @property string|null $description
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
*/
class Role extends Model
{
use HasFactory, SoftDeletes;
protected $guarded = ['id'];
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'user_roles')->withTimestamps();
}
public function permissions(): BelongsToMany
{
return $this->belongsToMany(Permission::class, 'role_permissions')->withTimestamps();
}
}
在上面的 Role
模型示例中,我们使用 BelongsToMany
关系来定义 Role
和 User
模型之间的关系,以及 Role
和 Permission
模型之间的关系。
这是 Permission
模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* @property int $id
* @property string $name
* @property string|null $description
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
*/
class Permission extends Model
{
use HasFactory, SoftDeletes;
protected $guarded = ['id'];
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, 'role_permissions')->withTimestamps();
}
}
在上面的 Permission
模型示例中,我们使用 BelongsToMany
关系来定义 Permission
和 Role
模型之间的关系。
这是 RolePermission
模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* @property int $id
* @property int $role_id
* @property int $permission_id
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
*/
class RolePermission extends Model
{
use HasFactory, SoftDeletes;
protected $guarded = ['id'];
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}
public function permission(): BelongsTo
{
return $this->belongsTo(Permission::class);
}
}
在上面的 RolePermission
模型示例中,我们使用 BelongsTo
关系来定义 RolePermission
和 Role
模型之间的关系,以及 RolePermission
和 Permission
模型之间的关系。
这是 UserRole
模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* @property int $id
* @property int $user_id
* @property int $role_id
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property \Illuminate\Support\Carbon $deleted_at
*/
class UserRole extends Model
{
use HasFactory, SoftDeletes;
protected $guarded = ['id'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}
}
在上面的 UserRole
模型示例中,我们使用 BelongsTo
关系来定义 UserRole
和 User
模型之间的关系,以及 UserRole
和 Role
模型之间的关系。
最后,这是 User
模型:
<?php
namespace App\Models;
use App\Traits\HasPermissions;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
/**
* 代表系统中用户的用户模型。
*
* @property int $id
* @property string $name
* @property string $email
* @property \DateTime|null $email_verified_at
* @property string $password
* @property string|null $remember_token
* @property int|null $contact_id
* @property \DateTime $created_at
* @property \DateTime $updated_at
*/
class User extends Authenticatable
{
use HasFactory, HasPermissions, Notifiable;
/**
* 可批量赋值的属性。
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* 应该为序列化隐藏的属性。
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* 应该被转换的属性。
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, 'user_roles')->withTimestamps();
}
}
在上面的 User
模型示例中,我们使用 BelongsToMany
关系来定义 User
和 Role
模型之间的关系。此外,我们使用各种特征和属性来处理身份验证、通知和权限。
权限检查特征
为了简化策略中的权限检查,创建一个特征,为使用它的类添加 hasPermission()
方法。以下示例演示如何创建此特征:
<?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);
});
}
}
此方法简化了检查用户是否具有特定权限的过程。
hasPermission
方法接受一个字符串参数 $permission
,并返回一个布尔值。它通过检查与用户关联的所有角色来检查当前用户是否具有指定的权限。
如果没有角色具有所需的 $permission
,some
函数返回 false
,表示用户没有必要的权限。
通过将此特征合并到您的 User
模型中,您可以轻松验证用户是否具有特定权限。
使用策略实现细粒度权限
要使用 User
模型应用细粒度权限,请为您想要控制访问的每个模型创建一个策略类。在此示例中,我们将创建一个 PostPolicy
来管理对 Post
模型的访问。
首先,创建 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;
}
}
请注意,我们利用了添加到 User
模型中的 hasPermission
特征。
接下来,在 AuthServiceProvider
中注册策略。以下是注册 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();
}
}
在此示例中,我们为 Post
模型注册了 PostPolicy
。这指示 Laravel 使用 PostPolicy
来控制对 Post
模型实例的访问。
现在,您可以在控制器中使用此策略来授权操作。以下是在控制器中使用 PostPolicy
的示例:
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);
// 更新帖子...
}
}
在此示例中,我们使用 authorize()
方法来验证经过身份验证的用户是否有权更新给定的帖子,使用 PostPolicy
的 update
方法。如果用户获得授权,我们允许他们编辑或更新帖子。如果没有,我们返回 HTTP 403 Forbidden 错误。
通过使用策略来控制对模型的访问,您可以确保只有授权用户才能在您的应用程序中执行敏感操作。