菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
80
0

rbac 教程

原创
05/13 14:22
阅读数 816

说明

前后端分离的前提下,后台接口使用多路由和多语言 rbac

本项目: github 地址

本项目前准备项目: github 地址 基于已开发好的 jwt 和多端路由

安装 spatie/laravel-permission 扩展

composer require spatie/laravel-permission

rbac 教程

发布 migration 文件

php artisan vendor:publish --provider="Spatie.ermission.ermissionServiceProvider" --tag="migrations"
rbac 教程
此命令会在 database/migrations 下生成 _xxxx_create_permissiontables.php 文件
rbac 教程

填充 root 用户

备注:root 用户将作为超级管理员,不受权限限制
php artisan make:migration seed_admins_table
rbac 教程
rbac 教程

php artisan make:migration seed_users_table
rbac 教程
rbac 教程

说明

  • 关于权限验证, 我是通过路由名和权限名一一对应的

  • 关于权限入库,我将权限写在语言文件里(为了多语言功能,并且方便管理),通过一份命令文件将权限入库,具体逻辑见下面说明

  • 权限默认只有 2 级

权限语言文件

resources/language/en 下 新建 permission/admin.phppermission/api.php

备注:此处文件名必须和路由文件名一致

resources/language/en/permission/admin.php

<?php

return [
    // 管理员
    [
        'value' => 'admin.admins.index',
        'title' => 'Admin',
        'children' => [
            ['value' => 'admin.admins.show', 'title' => 'View'],
            ['value' => 'admin.admins.store', 'title' => 'Add'],
            ['value' => 'admin.admins.update', 'title' => 'Update'],
            ['value' => 'admin.admins.destroy', 'title' => 'Delete'],
            ['value' => 'admin.admins.syncRoles', 'title' => 'Associated Role'],
        ]
    ],
    // 角色
    [
        'value' => 'admin.roles.index',
        'title' => 'Role',
        'children' => [
            ['value' => 'admin.roles.show', 'title' => 'View'],
            ['value' => 'admin.roles.store', 'title' => 'Add'],
            ['value' => 'admin.roles.update', 'title' => 'Update'],
            ['value' => 'admin.roles.destroy', 'title' => 'Delete'],
            ['value' => 'admin.roles.syncPermissions', 'title' => 'Association Permissions'],
        ],
    ],
    // 权限
    [
        'value' => 'admin.permissions.index',
        'title' => 'Permission',
    ],
];

resources/language/zh-CN/permission/admin.php

<?php

return [
    // 管理员
    [
        'value' => 'admin.admins.index',
        'title' => '管理员',
        'children' => [
            ['value' => 'admin.admins.show', 'title' => '查看'],
            ['value' => 'admin.admins.store', 'title' => '添加'],
            ['value' => 'admin.admins.update', 'title' => '修改'],
            ['value' => 'admin.admins.destroy', 'title' => '删除'],
            ['value' => 'admin.admins.syncRoles', 'title' => '关联角色'],
        ]
    ],
    // 角色
    [
        'value' => 'admin.roles.index',
        'title' => '角色',
        'children' => [
            ['value' => 'admin.roles.show', 'title' => '查看'],
            ['value' => 'admin.roles.store', 'title' => '添加'],
            ['value' => 'admin.roles.update', 'title' => '修改'],
            ['value' => 'admin.roles.destroy', 'title' => '删除'],
            ['value' => 'admin.roles.syncPermissions', 'title' => '关联权限'],
        ],
    ],
    // 权限
    [
        'value' => 'admin.permissions.index',
        'title' => '权限',
    ],
];

整体目录结构如下
rbac 教程

权限入库命令文件

php artisan make:command SeedPermission
rbac 教程

config/filesystems.php 下找到 disks => [···]

加入如下代码

 'disks' => [
        ·
        ·
        ·
        'root' => [
            'driver' => 'local',
            'root' => '/'
        ]
    ]

修改 app/Commands/SeedPermission.php 为如下代码

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Spatie\Permission\Models\Permission;

class SeedPermission extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'seed-permission';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '填充权限';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // 获取 zh-CN 语言包中的权限下所有文件
        $files = Storage::disk('root')->files(resource_path('lang/zh-CN/permission'));

        try {
            is_null(!$files);
        } catch (\Exception $e) {
            report($e);
            return false;
        }

        DB::transaction(function () use ($files) {
            foreach ($files as $file) {
                // 获取守卫名称
                $guardName = basename($file, '.php');
                $array = include(resource_path('lang/zh-CN/permission/') . $guardName . '.php');

                $values = [];
                foreach ($array as $arr) {
                    $values[] = $arr['value'];
                    if (isset($arr['children']) && is_array($arr['children'])) {
                        foreach ($arr['children'] as $child) {
                            $values[] = $child['value'];
                        }
                    }
                }
                // 获取数据库中的权限
                $permissions = Permission::select('name')
                    ->where('guard_name', $guardName)
                    ->get()
                    ->pluck('name');
                // 筛选出不同的权限
                $diff = collect($values)->diff($permissions);
                foreach ($diff as $item) {
                    Permission::create(['name' => $item, 'guard_name' => $guardName]);
                }
                // 反向删除
                $diff2 = collect($permissions)->diff($values);
                foreach ($diff2 as $item) {
                    Permission::where(['name' => $item, 'guard_name' => $guardName])->delete();
                }
            }
        });

        $this->info('权限已更新');
    }
}

执行 php artisan seed-permission
rbac 教程
可以看到数据库 permissions 表已更新
rbac 教程

说明

关于权限的验证, 上文已说过,是通过权限名和路由名一一对应验证,考虑大部分名称统一,所以我写了一个中间件,

如果某些路由不需要验证,或者某些路由和其他路由共用,则会有一份权限路由文件单独处理。具体见以下代码逻辑

创建中间件

语言中间件

php artisan make:middleware Locale
rbac 教程

app/Http/Middleware/Locale.php

<?php

namespace App\Http\Middleware;

use Closure;

class Locale
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // 是否设置语言参数,没有的情况下默认使用中文
        $language = $request->has('language') ? $request->language : 'zh-CN';
        app()->setLocale($language);
        return $next($request);
    }
}

权限验证中间件

php artisan make:middleware CheckPermissions
rbac 教程

app/Http/Middleware/CheckPermissions.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Spatie\Permission\Guard;

class CheckPermissions
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // 超级管理员默认通过所有权限
        if (Auth::user()->isRoot()) {
            return $next($request);
        }

        // 获取当前路由名称
        $currentRouteName = Route::currentRouteName();

        // 获取当前守卫名称
        $guardName = Guard::getDefaultName(self::class);
        // 引入当前守卫的权限文件
        $routes = include(app_path('Permissions/') . $guardName . '.php');

        // 替换设置了关联关系的权限
        if (is_array($routes) && key_exists($currentRouteName, $routes)) {
            $currentRouteName = $routes[$currentRouteName];
        }

        // 当路由不为 null 时,验证权限
        if (!is_null($currentRouteName)) {
            Gate::authorize($currentRouteName);
        }

        return $next($request);
}

app/Admin.php 增加如下代码

  /**
     * 判断是否是 root 用户
     *
     * @return bool
     */
    public function isRoot()
    {
        return $this->name === 'root';
    }

app 下新建 Permissions/admin.phpPermissions/api.php

备注:此处文件名必须和路由文件名一致

app/Permissions/admin.php

<?php
return [
    'admin.admins.current.show' => null,
    'admin.admins.current.update' => null,
];

app/Permissions/api.php

<?php

return [
    'api.users.current.show' => null,
    'api.users.current.update' => null,
];

如上所述,这里我们为特殊的路由额外设置关联

注册中间件

app/Http/Kernel.php 中找到 protected $routeMiddleware = [···]

protected $routeMiddleware = [
        ·
        ·
        ·
        // 设置语言中间件
        'locale' => \App\Http\Middleware\Locale::class,
        // 检查权限
        'check.permissions' => CheckPermissions::class
    ];

修改 routes/admin.php

<?php

Route::group([
    'prefix' => 'v1',
    'middleware' => ['bindings', 'locale']
], function () {
    // 登录接口
    Route::group([
    ], function () {
        // 获取 token
        Route::post('authorizations', 'AuthorizationsController@store')
            ->name('admin.authorizations.store');
        // 刷新 token
        Route::put('authorizations/current', 'AuthorizationsController@update')
            ->name('admin.authorizations.update');
        // 删除 token
        Route::delete('authorizations/current', 'AuthorizationsController@destroy')
            ->name('admin.authorizations.destroy');
    });

    // 需要 token 验证的接口
    Route::group([
        'middleware' => [
            // 此处认证的是 admin 守卫
            'auth:admin',
            'check.permissions'],
    ], function () {
        /****************************************************管理员*******************************************************/
        // 列表
        Route::get('admins', 'AdminsController@index')
            ->name('admin.admins.index');
        // 查看当前用户
        Route::get('admins/current/show', 'AdminsController@currentShow')
            ->name('admin.admins.current.show');
        // 详情
        Route::get('admins/{admin}', 'AdminsController@show')
            ->name('admin.admins.show');
        // 新增
        Route::post('admins', 'AdminsController@store')
            ->name('admin.admins.store');
        // 修改当前用户
        Route::patch('admins/current/update', 'AdminsController@currentUpdate')
            ->name('admin.admins.current.update');
        // 修改
        Route::patch('admins/{admin}', 'AdminsController@update')
            ->name('admin.admins.update');
        // 删除
        Route::delete('admins/{admin}', 'AdminsController@destroy')
            ->name('admin.admins.destroy');
        // 关联角色
        Route::post('admins/{admin}/syncRoles', 'AdminsController@syncRoles')
            ->name('admin.admins.syncRoles');

        /****************************************************角色*******************************************************/
        // 列表
        Route::get('roles', 'RolesController@index')
            ->name('admin.roles.index');
        // 详情
        Route::get('roles/{role}', 'RolesController@show')
            ->name('admin.roles.show');
        // 新增
        Route::post('roles', 'RolesController@store')
            ->name('admin.roles.store');
        // 修改
        Route::patch('roles/{role}', 'RolesController@update')
            ->name('admin.roles.update');
        // 删除
        Route::delete('roles/{role}', 'RolesController@destroy')
            ->name('admin.roles.destroy');
        // 关联权限
        Route::post('roles/{role}/syncPermissions', 'RolesController@syncPermissions')
            ->name('admin.roles.syncPermissions');

        /****************************************************权限*******************************************************/
        // 列表
        Route::get('permissions', 'PermissionsController@index')
            ->name('admin.permissions.index');
    });
});

首先我们来验证一下

用 admin1 的 token 请求

rbac 教程

此时 403, 访问被拒绝

用 root 的 token 请求

rbac 教程

成功返回数据

接下来我们给 admin1 用户添加一个角色,并且该角色拥有查看管理员列表的权限

创建角色控制器

php artisan make:controller Admin/RolesController

rbac 教程

app/Http/Controllers/Admin/RolesController.php

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Requests\Admin\RoleRequest;
use App\Http\Resources\RoleResource;
use Illuminate\Http\Request;
use Spatie\Permission\Guard;
use Spatie\Permission\Models\Role;

class RolesController extends Controller
{
    /**
     * 列表
     *
     * @param Request $request
     * @param Role $role
     * @return mixed
     */
    public function index(Request $request, Role $role)
    {
        // 默认 20 页
        $limit = $request->has('limit') ? $request->limit : 20;

        // 获取当前守卫名称
        $guardName = Guard::getDefaultName(self::class);
        $roles = $role->where('guard_name', $guardName)->paginate($limit);
        return RoleResource::collection($roles);
    }

    /**
     * 详情
     *
     * @param $role
     * @return RoleResource
     */
    public function show($role)
    {
        return new RoleResource($role);
    }

    /**
     * 新增
     *
     * @param RoleRequest $request
     * @param Role $role
     * @return RoleResource
     */
    public function store(RoleRequest $request, Role $role)
    {
        $role = $role::create($request->all());
        return new RoleResource($role);
    }

    /**
     * 修改
     *
     * @param RoleRequest $request
     * @param Role $role
     * @return RoleResource
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function update(RoleRequest $request, Role $role)
    {
        $role->update($request->all());
        return new RoleResource($role);
    }

    /**
     * 删除
     *
     * @param Role $role
     * @return \Illuminate\Http\Response
     * @throws \Exception
     */
    public function destroy(Role $role)
    {

        $role->delete();
        return response()->noContent();
    }

    /**
     * 给角色添加权限
     *
     * @param RoleRequest $request
     * @param Role $role
     * @return RoleResource
     */
    public function syncPermissions(RoleRequest $request, Role $role)
    {
        $role->syncPermissions($request->permissions);
        return new RoleResource($role);
    }
}

创建资源

php artisan make:resource RoleResource

我们用 root 用户创建一个角色

rbac 教程

然后我们为该角色添加查看管理员列表和详情的权限

rbac 教程

接下来我们为 admin1 用户关联角色

修改 app/Admin.php

use Spatie\Permission\Traits\HasRoles;

class Admin extends Authenticatable implements JWTSubject
{
    use HasRoles;

    // admin 表我们使用的是 admin 守卫
    protected $guard_name = 'admin';
}

修改 app/Http/Controllers/Admin/AdminsController.php

 /**
     * 为管理员添加角色
     *
     * @param Request $request
     * @param Admin $admin
     * @return AdminResource
     */
    public function syncRoles(Request $request, Admin $admin)
    {
        $admin->syncRoles($request->roles);
        return new AdminResource($admin);
    }

rbac 教程

此时我们再用 admin1 的 token 去请求管理员列表接口时便会返回数据

权限列表查看

php artisan make:controller Admin/PermissionsController

rbac 教程

app/Http/Controllers/Admin/PermissionsController.php

<?php

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;

class PermissionsController extends Controller
{
    /**
     * 列表
     *
     * @return array|\Illuminate\Contracts\Translation\Translator|string|null
     */
    public function index()
    {
        $permissions = trans('permission/admin', [], app()->getLocale());
        return response()->json(['data' => $permissions]);
    }
}

rbac 教程
rbac 教程

补充

此时我们角色表是可以跨端操作的,所以我加了 policy 不允许跨端操作

php artisan make:policy RolePolicy

rbac 教程

app/Providers/AuthServiceProvider.php 找到 protected $policies = [···];

use App.olicies.olePolicy;
use Spatie.ermission.odels.ole;
protected $policies = [
    Role::class => RolePolicy::class
];

修改 app/Policies/RolePolicy.php

use Spatie\Permission\Guard;
use Spatie\Permission\Models\Role;

     /**
     * 不允许跨守卫操作
     *
     * @param Role $role
     * @return bool
     */
    public function authorize($current, Role $role)
    {
        return $role->guard_name == Guard::getDefaultName(self::class);
    }

app/Http/Controllers/Admin/RolesController.php 中找到 show(), update(), destroy()

加入以下代码

$this->authorizeForUser(Auth::guard('admin')->user(), 'authorize', $role);

总结

另外一个端以及一些验证参见具体代码,逻辑整体不变。

还有点缺陷的是角色多语言并未实现,之前考虑的一个思路是在 roles 表里横向扩展,比如 name_en, name_zh_CN。

参考教程

spatie/laravel-permission

发表评论

0/200
80 点赞
0 评论
收藏
为你推荐 换一批