Skip to content

Laravel 权限实现

简介

Laravel提供策略用于为应用程序定义授权逻辑。这些策略确立了管理哪些用户被授权在应用程序模型上执行特定操作的规则。本指南将讨论使用数据库架构、trait和策略在Laravel应用程序中实现细粒度权限所需的步骤。

前提条件

在开始之前,请确保已经设置并运行了Laravel应用程序。您还应该熟悉Laravel迁移、模型和策略。

数据库架构

为在Laravel应用程序中实现权限,请创建包含角色、权限及其关系表的数据库架构。以下示例演示如何使用Laravel迁移创建这些表:

php
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');
    }
}

在此示例中,我们为角色、权限及其关系创建表。roles表包含ID、名称、描述和时间戳。permissions表包含ID、名称、描述和时间戳。role_permissions表包含ID、role_idpermission_id和时间戳。role_idpermission_id列是外键,分别引用rolespermissions表中的id列。

模型架构

为了在与RolePermissionUser模型交互时提供类型提示并提高代码可读性,我们创建了相应的<Model>Schema类。这些类定义了代表相应数据库表中列名称的常量。

例如,以下是PermissionSchema类:

php
<?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';
}

在上面的例子中,我们为permissions表中的每一列定义常量。然后,我们可以在与Permission模型及其关联数据交互时使用这些常量来提供类型提示并提高代码可读性。

稍后,我们将在与模型交互时使用这些模式来定义列的名称。例如,在我们稍后将创建的HasPermissions trait中,我们使用了PermissionSchema::name常量来引用permissions表中的name列:

php
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",我们使代码更具可读性并减少错误发生。此外,如果我们要更改permissions表中name列的名称,我们可以简单地更新PermissionSchema::name常量以反映新名称,并且代码中对该常量的所有引用都将自动更新。

通过以这种方式使用模型架构,我们可以使代码随着时间的推移更易于维护和使用。

模型

要在Laravel应用程序中实现细粒度权限,请创建三个模型:RolePermissionUser。这些模型与存储有关角色、权限和用户/角色关系的信息的数据库表交互。

以下是Role模型的示例:

php
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();
    }
}

在上面的Role模型示例中,我们使用BelongsToMany关系定义RoleUser模型之间的关系,以及RolePermission模型之间的关系。

以下是Permission模型的示例:

php
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();
    }
}

在上面的Permission模型示例中,我们使用BelongsToMany关系定义PermissionRole模型之间的关系。

最后,以下是User模型的示例:

php
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();
    }
}

在上面的User模型示例中,我们使用BelongsToMany关系定义UserRole模型之间的关系。此外,我们使用各种trait和属性来处理身份验证、通知和权限。

权限检查Trait

为了简化策略中的权限检查,创建一个为使用它的类添加hasPermission()方法的trait。以下示例演示如何创建此trait:

php
<?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,并返回一个布尔值。它通过检查与用户关联的所有角色来检查当前用户是否具有指定的权限。

如果没有角色拥有所需的$permissionsome函数返回false,表示用户没有必要的权限。

通过将此trait合并到User模型中,您可以轻松验证用户是否具有特定权限。

使用策略实现细粒度权限

要使用User模型应用细粒度权限,请为要控制访问权限的每个模型创建策略类。在此示例中,我们将创建一个PostPolicy来管理对Post模型的访问。

首先,创建PostPolicy类:

php
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 trait。

接下来,在AuthServiceProvider中注册策略。以下是注册PostPolicy的示例:

php
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的示例:

php
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...
    }
}

在此示例中,我们使用authorize()方法验证经过身份验证的用户是否有权限使用PostPolicyupdate方法更新给定的帖子。如果用户被授权,我们允许他们编辑或更新帖子。如果没有,我们将返回HTTP 403 Forbidden错误。

通过使用策略来控制对模型的访问,您可以确保只有授权用户才能在应用程序中执行敏感操作。