mstfkhazaal/defender

Laravel 的角色和权限

V0.13.0 2022-10-03 16:29 UTC

README

Defender 是 Laravel 5 / 6 / 7 / 8 / 9(单一认证)的访问控制列表(ACL)解决方案。(不兼容多认证)
考虑到安全性和可用性,此项目旨在为您提供一种安全的方式来控制应用程序访问,同时不失编码的乐趣。

当前构建状态

Build Status Code Climate StyleCI

统计信息

Latest Stable Version Latest Unstable Version License Total Downloads Monthly Downloads Daily Downloads

欢迎贡献

Defender 正在寻找维护者和贡献者。

安装

1. 依赖关系

使用 composer,执行以下命令以自动更新您的 composer.json,使用相应的包版本

composer require artesaos/defender

或者手动更新您的 composer.json 文件

{
    "require": {
        "artesaos/defender": "~0.10.0"
    }
}

2. 提供者

如果您使用 Laravel >= 5.5,则可以跳过此部分,因为我们的包支持自动发现。

您需要更新应用程序配置以注册包,以便它可以由 Laravel 加载。只需更新您的 config/app.php 文件,在 'providers' 部分的末尾添加以下代码即可

// file START ommited
    'providers' => [
        // other providers ommited
        \Artesaos\Defender\Providers\DefenderServiceProvider::class,
    ],
// file END ommited

3. 用户类

在您的用户类中,添加 trait Artesaos\Defender\Traits\HasDefender 以启用权限和角色的创建

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Artesaos\Defender\Traits\HasDefender;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
    use Authenticatable, CanResetPassword, HasDefender;
...

如果您使用 laravel 5.2+,有一些小的区别

<?php

namespace App;

use Artesaos\Defender\Traits\HasDefender;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasDefender;
...

4. 发布配置文件和迁移

要发布默认配置文件和数据库迁移,请执行以下命令

php artisan vendor:publish

执行迁移,以便在您的数据库中创建表

php artisan migrate

您还可以仅发布配置文件或迁移

php artisan vendor:publish --tag=config

或者

php artisan vendor:publish --tag=migrations

如果您已经发布了 defender 文件,但由于某些原因想要覆盖之前发布的文件,请添加 --force 标志。

5. Facade(可选)

为了使用 Defender facade,您需要在 config/app.php 文件中注册它,您可以按照以下方式操作

// config.php file
// file START ommited
    'aliases' => [
        // other Facades ommited
        'Defender' => \Artesaos\Defender\Facades\Defender::class,
    ],
// file END ommited

6. Defender 中间件(可选)

如果您必须控制 Defender 提供的访问,Defender 提供中间件来保护您的路由。如果您必须通过 Laravel 路由控制访问,Defender 为一些简单任务提供了一些内置中间件。要使用它们,只需将它们放入您的 app/Http/Kernel.php 文件中。

protected $routeMiddleware = [
    'auth'            => \App\Http\Middleware\Authenticate::class,
    'auth.basic'      => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'guest'           => \App\Http\Middleware\RedirectIfAuthenticated::class,

    // Access control using permissions
    'needsPermission' => \Artesaos\Defender\Middlewares\NeedsPermissionMiddleware::class,

    // Simpler access control, uses only the groups
    'needsRole' => \Artesaos\Defender\Middlewares\NeedsRoleMiddleware::class
];

下面将介绍如何使用中间件。

6.1 - 创建自己的中间件

如果内置中间件不符合您的需求,您可以使用 Defender 的 API 来控制访问。

使用方法

Defender 仅处理访问控制。身份验证仍然由 Laravel 的 Auth 完成。

注意:如果您使用不同的用户模型或已更改命名空间,请更新 defender 配置文件中的 user_model 键

创建角色和权限

使用命令

您可以使用这些命令为您应用程序创建角色和权限。

php artisan defender:make:role admin  # creates the role admin
php artisan defender:make:role admin --user=1 # creates the role admin and attaches this role to the user where id=1
php artisan defender:make:permission users.index "List all the users" # creates the permission
php artisan defender:make:permission users.create "Create user" --user=1 # creates the permission and attaches it to user where id=1
php artisan defender:make:permission users.destroy "Delete user" --role=admin # creates the permission and attaches it to the role admin

使用 seeder 或 artisan tinker

您还可以使用 Defender 的 API。您可以创建一个 Laravel Seeder 或使用 php artisan tinker

use App\User;

$roleAdmin = Defender::createRole('admin');

// The first parameter is the permission name
// The second is the "friendly" version of the name. (usually for you to show it in your application).
$permission =  Defender::createPermission('user.create', 'Create Users');

// You can assign permission directly to a user.
$user = User::find(1);
$user->attachPermission($permission);

// or you can add the user to a group and that group has the power to rule create users.
$roleAdmin->attachPermission($permission);

// Now this user is in the Administrators group.
$user->attachRole($roleAdmin);

使用中间件

要保护您的路由,您可以使用内置中间件。

Defender 需要 Laravel 的 Auth,因此,在使用您打算使用的 Defender 中间件之前,请使用 auth 中间件。

检查权限:needsPermissionMiddleware

Route::get('foo', ['middleware' => ['auth', 'needsPermission'], 'shield' => 'user.create', function()
{
    return 'Yes I can!';
}]);

如果您使用 Laravel 5.1+,则可以使用 Middleware 参数。

Route::get('foo', ['middleware' => ['auth', 'needsPermission:user.index'], function() {
    return 'Yes I can!';
}]);

使用此语法,您也可以在控制器中使用中间件。

$this->middleware('needsPermission:user.index');

您可以将要检查的权限数组传递过去。

Route::get('foo', ['middleware' => ['auth', 'needsPermission'], 'shield' => ['user.index', 'user.create'], function()
{
    return 'Yes I can!';
}]);

当使用中间件参数时,使用 | 来分隔多个权限。

Route::get('foo', ['middleware' => ['auth', 'needsPermission:user.index|user.create'], function() {
    return 'Yes I can!';
}]);

或者是在控制器中

$this->middleware('needsPermission:user.index|user.create');

当你传递一个权限数组时,只有当用户拥有所有权限时,才会触发路由。然而,如果你想在用户至少拥有一个权限时允许访问该路由,只需添加 'any' => true

Route::get('foo', ['middleware' => ['auth', 'needsPermission'], 'shield' => ['user.index', 'user.create'], 'any' => true, function()
{
    return 'Yes I can!';
}]);

或者,使用中间件参数,将其作为第二个参数传递

Route::get('foo', ['middleware' => ['auth', 'needsPermission:user.index|user.create,true'], function() {
    return 'Yes I can!';
}]);

或者是在控制器中

$this->middleware('needsPermission:user.index|user.create,true');

检查角色:needsRoleMiddleware

这与之前的中间件类似,但只检查角色,这意味着它不会检查权限。

Route::get('foo', ['middleware' => ['auth', 'needsRole'], 'is' => 'admin', function()
{
    return 'Yes I am!';
}]);

如果你使用 Laravel 5.1,则可以使用中间件参数。

Route::get('foo', ['middleware' => ['auth', 'needsRole:admin'], function() {
    return 'Yes I am!';
}]);

使用此语法,您也可以在控制器中使用中间件。

$this->middleware('needsRole:admin');

您可以将要检查的权限数组传递过去。

Route::get('foo', ['middleware' => ['auth', 'needsRole'], 'shield' => ['admin', 'member'], function()
{
    return 'Yes I am!';
}]);

当使用中间件参数时,使用 | 来分隔多个角色。

Route::get('foo', ['middleware' => ['auth', 'needsRole:admin|editor'], function() {
    return 'Yes I am!';
}]);

或者是在控制器中

$this->middleware('needsRole:admin|editor');

当你传递一个权限数组时,只有当用户拥有所有权限时,才会触发路由。然而,如果你想在用户至少拥有一个权限时允许访问该路由,只需添加 'any' => true

Route::get('foo', ['middleware' => ['auth', 'needsRole'], 'is' => ['admin', 'member'], 'any' => true, function()
{
    return 'Yes I am!';
}]);

或者,使用中间件参数,将其作为第二个参数传递

Route::get('foo', ['middleware' => ['auth', 'needsRole:admin|editor,true'], function() {
    return 'Yes I am!';
}]);

或者是在控制器中

$this->middleware('needsRole:admin|editor,true');

在视图中使用

Laravel 的 Defender 扩展用于使用 Defender。

@shield

@shield('user.index')
    shows your protected stuff
@endshield
@shield('user.index')
    shows your protected stuff
@else
    shows the data for those who doesn't have the user.index permission
@endshield

你还可以使用通配符(*)

@shield('user.*')
    shows your protected stuff
@else
    shows the data for those who doesn't have the any permission with 'user' prefix
@endshield

@is

@is('admin')
    Shows data for the logged user and that belongs to the admin role
@endis
@is('admin')
    Shows data for the logged user and that belongs to the admin role
@else
    shows the data for those who doesn't have the admin permission
@endis
@is(['role1', 'role2'])
    Shows data for the logged user and that belongs to the admin role
@else
    shows the data for those who doesn't have the admin permission
@endis

使用 JavaScript 辅助函数

该插件提供辅助函数,以便在需要与前端用户权限交互时使用。

echo Defender::javascript()->render();
// or
echo app('defender')->javascript()->render();
// or
echo app('defender.javascript')->render();

此辅助函数注入一段包含当前用户所有权限和角色的 JavaScript 代码。

使用门面

使用 Defender 的门面,您可以在应用程序的任何部分访问 API 并使用它。

Defender::hasPermission($permission):

检查登录用户是否有 $permission

Defender::canDo($permission):

检查登录用户是否有 $permission。如果返回 superuser 角色为 true

Defender::roleHasPermission($permission):

检查登录用户是否有 $permission,只检查角色权限。

Defender::hasRole($roleName):

检查登录用户是否属于 $roleName 角色。

Defender::roleExists($roleName):

检查角色 $roleName 是否存在于数据库中。

Defender::permissionExists($permissionName):

检查权限 $permissionName 是否存在于数据库中。

Defender::findRole($roleName):

通过名称 $roleName 在数据库中查找角色。

Defender::findRoleById($roleId):

通过角色 ID roleId 在数据库中查找角色。

Defender::findPermission($permissionName):

通过名称 $permissionName 在数据库中查找权限。

Defender::findPermissionById($permissionId):

通过 ID $permissionId 在数据库中查找权限。

Defender::createRole($roleName):

在数据库中创建新的角色。

Defender::createPermission($permissionName):

在数据库中创建新的权限。

Defender::is($roleName):

检查当前用户是否属于该角色。

Defender::javascript()->render():

返回一个 JavaScript 脚本,其中包含当前用户的所有角色和权限列表。变量名称可以修改。

使用特性

要添加 Defender 的功能,需要在您的 User 模型中添加特性 HasDefender(通常是 App\User)。

<?php namespace App;

// Declaration of other omitted namespaces
use Artesaos\Defender\Traits\HasDefender;

class User extends Model implements AuthenticatableContract, CanResetPasswordContract {

    use Authenticatable, CanResetPassword, HasDefender;

    // Rest of the class
}

除了配置关系外,此特性还会将以下方法添加到您的对象 App\User

public function hasPermission($permission):

此方法检查登录用户是否有 $permission 权限

在 Defender 中,有两种权限:用户权限角色权限。默认情况下,用户继承的权限是其所属角色的权限。但是,只要设置了用户权限,它就会优先于角色权限。

public function foo(Authenticable $user)
{
    if ($user->hasPermission('user.create'));
}
public function roleHasPermission($permission):

此方法与上一个方法的工作方式相同,唯一的区别是不考虑用户权限,然而,只使用用户所属的角色权限来检查访问权限。

public function foo(Authenticable $user)
{
    if ($user->roleHasPermission('user.create');
}
public function attachRole($role):

将用户附加到角色 $role。变量 $role 可能是类型为 Artesaos\Defender\Role 的对象,或者是一个包含角色 ids 的数组。

public function foo(Authenticable $user)
{
    $role = Defender::findRole('admin'); // Returns an Artesao\Defender\Role
    $user->attachRole($role);

    // or

    $roles = [1, 2, 3]; // Using an array of ids
    $user->attachRole($roles);
}
public function detachRole($role):

从用户中删除角色 $role(与 attachRole() 相反)。

public function foo(Authenticable $user)
{
    $role = Defender::findRole('admin'); // Returns an Artesao\Defender\Role
    $user->detachRole($role);

    // ou

    $roles = [1, 2, 3]; // Using an array of ids
    $user->detachRole($roles);
}
public function syncRoles(array $roles = array()):

这类似于 attachRole() 方法,但只有数组 $roles 中的角色在方法运行后会出现在关系上。$roles 是包含所需角色 ids 的数组。

public function foo(Authenticable $user)
{
    $roles = [1, 2, 3]; // Using an array of ids

    $user->syncRoles($roles);
}
public function attachPermission($permission, array $options = array()):

将用户附加到权限 $permission。变量 $permissionArtesaos\Defender\Permission 类的实例。

public function foo(Authenticable $user)
{
    $permission = Defender::findPermission('user.create');

    $user->attachPermission($permission, [
        'value' => true // true = has the permission, false = doesn't have the permission,
    ]);
}
public function detachPermission($permission):

从用户中移除权限 $permission。变量 $permission 可能是 Artesaos\Defender\Permission 类的实例,或者是一个包含要移除权限 ids 的数组。

public function foo(Authenticable $user)
{
    $permission = Defender::findPermission('user.create');
    $user->detachPermission($permission);

    // or

    $permissions = [1, 3];
    $user->detachPermission($permissions);
}
public function syncPermissions(array $permissions):

这类似于方法 syncRoles,但只有数组 $permissions 中的角色在方法运行后会出现在关系上。

public function foo(Authenticable $user)
{
    $permissions = [
        1 => ['value' => false],
        2 => ['value' => true,
        3 => ['value' => true]
    ];

    $user->syncPermissions($permissions);
}
public function revokePermissions():

移除所有用户权限。

public function foo(Authenticable $user)
{
    $user->revokePermissions();
}
public function revokeExpiredPermissions():

从用户中移除所有临时过期的权限。关于临时权限的更多信息见下文。

public function foo(Authenticable $user)
{
    $user->revokeExpiredPermissions();
}

临时权限

Defender 最酷的功能之一是向组或用户添加临时权限。

例如

用户 John 属于 'admins' 角色,但是我想临时移除 John 创建新用户的权限

在这种情况下,我们需要添加一个值为 false 的权限,明确禁止用户执行该操作。您必须添加此权限,并设置值为 false,因为默认情况下,用户权限继承自其角色的权限。当您分配用户权限时,这始终优先。

例如。以下我们在 7 天内撤销用户 user.create 权限。

public function foo()
{
    $userX = App\User::find(3);
    $permission = Defender::findPermission('user.create');


    $userX->attachPermission($permission, [
        'value' => false, // false means that he will not have the permission,
        'expires' => \Carbon\Carbon::now()->addDays(7) // Set the permission's expiration date
    ]);

}

7 天后,用户将再次获得权限。

允许用户在一定时间内执行某些操作。

要允许用户在一定时间内临时访问执行特定操作,只需设置 expires 键。默认情况下,value 键将为 true

public function foo()
{
    $user = App\User::find(1);
    $permission = Defender::findPermission('user.create');

    $user->attachPermission($permission, [
        'expires' => \Carbon\Carbon::now()->addDays(7)
    ];
}

还可以扩展现有的临时权限:只需使用 $user->extendPermission($permissionName, array $options) 方法。

使用自定义角色和权限模型

要使用自己的角色和权限模型类,首先在 defender.php 配置文件中设置 role_modelpermission_model 键。

以下是使用 jenssegers/laravel-mongodb 驱动程序实现角色和权限模型的两个示例。

    <?php
    
    // Role model
    
    namespace App;
    
    use Jenssegers\Mongodb\Eloquent\Model;
    use Artesaos\Defender\Traits\Models\Role;
    use Artesaos\Defender\Contracts\Role as RoleInterface;
    
    /**
     * Class Role.
     */
    class Role extends Model implements RoleInterface {
        use Role;
    }
    <?php
    
    // Permission model
    
    namespace App;
    
    use Jenssegers\Mongodb\Eloquent\Model;
    use Artesaos\Defender\Traits\Models\Permission;
    use Artesaos\Defender\Contracts\Permission as PermissionInterface;
    
    /**
     * Class Permission.
     */
    class Permission extends Model implements PermissionInterface
    {
        use Permission;    
    }

您必须使用正确的特性,并且每个类都必须实现相应的接口合同。