laravel rbac权限管理系统

开发系统中,关于权限这块儿我想大家都不陌生,就是对一个操作进行权限判定,对用户当前请求的操作效验权限是否允许此用户执行这个动作。最近在学习laravel框架,发现laravel官方没有提供关于我感觉便捷的权限管理。我就自己实现了一个基于rbac模式的权限管理系统

1、什么是rbac?
在这里就简单说下他的思想,rbac的核心定义就是角色与权限的关系、用户与角色的关系。啥意思呢?意思是说,一组用户通过被分配的角色身份去行使对应的权限。
权限管理

2、如何在laravel中实现rbac
以下就是硬货,我尽量对每个类添加一些说明

前引:需要的数据库表迁移

rbac_role.table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('rbac_role', function (Blueprint $table) {
            $table->id();
            $table->string('identifier')->unique()->comment('身份标识符');
            $table->string('description')->comment('描述');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('rbac_role');
    }
};

rbac_power.table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('rbac_power', function (Blueprint $table) {
            $table->id();
            $table->string('identifier')->unique()->comment('身份标识符');
            $table->string('description')->comment('描述');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('rbac_power');
    }
};

rbac_relation_role_power.table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('rbac_relation_role_power', function (Blueprint $table) {
            $table->id();
            $table->integer('relation_rid')->comment('角色id');
            $table->integer('relation_pid')->comment('权限id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('rbac_relation_role_power');
    }
};

rbac_relation_application_role.table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('rbac_relation_application_role', function (Blueprint $table) {
            $table->id();
            $table->string('relation_aid')->comment('应用id');
            $table->integer('relation_rid')->comment('角色id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('rbac_relation_application_role');
    }
};

第一步:定义rbac服务 RbacInterface

其中的接口定义就是rbac的实现核心

<?php
/**
 * Notes:权限接口标准
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac;

use App\Models\RbacPower;
use App\Models\RbacRelationApplicationRole;
use App\Models\RbacRelationRolePower;
use App\Models\RbacRole;
use Illuminate\Database\Eloquent\Model;

/**
 * @function registerPower(String $identifier,String $name) 注册一个权限
 * @function registerRole(String $identifier,String $name) 注册一个角色
 * @function relationRolePower(int $relationRid,int $relationPid) 创建一个角色与权限之间的关联
 * @function relationApplicationRole(int $relationAid,int $relationRid) 创建一个应用与角色之间的关联
 * @function getRolePower(int $relationRid,int $relationPid) 获取角色权限关联
 * @function getApplicationRole(int $relationAid,int $relationRid) 获取应用关联的指定角色
 * @function getRole(String $identifier) 获取一个角色信息
 * @function getPower(String $identifier) 获取一个权限信息
 * @function getApplicationRoles(int $relationAid) 获取应用角色关联
 */
interface RbacInterface
{

    /**
     * Notes:注册一个权限
     * Author:tanyong
     * DateTime:2022/5/16
     * @param String $identifier 权限标识符
     * @param String $name  权限名称
     * @return RbacPower $orm
     */
    public function registerPower(String $identifier,String $name);

    /**
     * Notes:注册一个角色
     * Author:tanyong
     * DateTime:2022/5/16
     * @param String $identifier 角色标识符
     * @param String $name 角色名称
     * @return RbacRole $orm
     */
    public function registerRole(String $identifier,String $name);

    /**
     * Notes:创建一个角色与权限之间的关联
     * Author:tanyong
     * DateTime:2022/5/16
     * @param int $relationRid 角色id
     * @param int $relationPid 权限id
     * @return RbacRelationRolePower $orm
     */
    public function relationRolePower(int $relationRid,int $relationPid);

    /**
     * Notes:获取角色权限关联
     * Author:tanyong
     * DateTime:2022/5/16
     * @param int $relationRid 角色id
     * @param int $relationPid 权限id
     * @return mixed
     */
    public function getRolePower(int $relationRid,int $relationPid);

    /**
     * Notes:创建一个应用与角色之间的关联
     * Author:tanyong
     * DateTime:2022/5/16
     * @param int $relationAid 应用id
     * @param int $relationRid 角色id
     * @return RbacRelationApplicationRole $orm
     */
    public function relationApplicationRole(int $relationAid,int $relationRid);

    /**
     * Notes:获取应用关联的指定角色
     * Author:tanyong
     * DateTime:2022/5/16
     * @param int $relationAid 应用id
     * @param int $relationRid 角色id
     * @return mixed
     */
    public function getApplicationRole(int $relationAid,int $relationRid);

    /**
     * Notes:获取一个角色信息
     * Author:tanyong
     * DateTime:2022/5/16
     * @param String $identifier
     * @return RbacRole
     */
    public function getRole(String $identifier);

    /**
     * Notes:获取一个权限信息
     * Author:tanyong
     * DateTime:2022/5/16
     * @param String $identifier
     * @return RbacPower
     */
    public function getPower(String $identifier);

    /**
     * Notes:获取应用角色关联
     * Author:tanyong
     * DateTime:2022/5/16
     * @param int $relationAid
     * @return RbacRelationApplicationRole[] $orms
     */
    public function getApplicationRoles(int $relationAid);
}

第二步:实现RbacInterface接口

<?php
/**
 * Notes:描述该文件的用途
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac;

use App\Exceptions\AppException;
use App\Exceptions\Ecode;
use App\Models\RbacPower;
use App\Models\RbacRelationApplicationRole;
use App\Models\RbacRelationRolePower;
use App\Models\RbacRole;
use App\Services\Rbac\Exceptions\RbacException;
use App\Services\Rbac\Facades\Rbac;
use Illuminate\Database\Eloquent\Model;

class RbacService implements RbacInterface,CanInterface
{
    public function registerPower(string $identifier, string $name)
    {
        $power = $this->getPower($identifier);

        if(!empty($power))
            throw new AppException(Ecode::RBAC_ERROR,'此权限已被注册');

        $power = new RbacPower();

        $power->identifier = $identifier;
        $power->description = $name;

        if($power->save())
            return $power;
        else
            return false;
    }

    public function registerRole(string $identifier, string $name)
    {
        $role = $this->getRole($identifier);

        if(!empty($role))
            throw new AppException(Ecode::RBAC_ERROR,'此角色已被注册');

        $role = new RbacRole();

        $role->identifier = $identifier;
        $role->description = $name;

        if($role->save())
            return $role;
        else
            return false;
    }

    public function relationRolePower(int $relationRid, int $relationPid)
    {
        $relationRolePower = $this->getRolePower($relationRid,$relationPid);
        if(!empty($relationRolePower))
            throw new AppException(Ecode::RBAC_ERROR,'此关联已被注册,无需重复关联');

        $relationRolePower = new RbacRelationRolePower();
        $relationRolePower->relation_pid = $relationPid;
        $relationRolePower->relation_rid = $relationRid;
        if($relationRolePower->save())
            return $relationRolePower;
        else
            return false;
    }

    public function getRolePower(int $relationRid, int $relationPid)
    {
        return RbacRelationRolePower::where([
            ['relation_rid','=',$relationRid],
            ['relation_pid','=',$relationPid],
        ])->first();
    }

    public function getApplicationRole(int $relationAid, int $relationRid)
    {
        return RbacRelationApplicationRole::where([
            ['relation_aid','=',$relationAid],
            ['relation_rid','=',$relationRid],
        ])->first();
    }

    public function relationApplicationRole(int $relationAid, int $relationRid)
    {
        $relationApplicationRole = $this->getApplicationRole($relationAid,$relationRid);

        if(!empty($relationApplicationRole))
            throw new AppException(Ecode::RBAC_ERROR,'此关联已被注册,无需重复关联');

        $relationApplicationRole = new RbacRelationApplicationRole();
        $relationApplicationRole->relation_aid = $relationAid;
        $relationApplicationRole->relation_rid = $relationRid;
        if($relationApplicationRole->save())
            return $relationApplicationRole;
        else
            return false;
    }
    public function getRole(string $identifier)
    {
        return RbacRole::where('identifier',$identifier)->first();
    }

    public function getPower(string $identifier)
    {
        return RbacPower::where('identifier',$identifier)->first();
    }

    public function getApplicationRoles(int $relationAid)
    {
        return RbacRelationApplicationRole::where('relation_aid',$relationAid)->get();
    }

    public function can(Model $application, string $powerIdentifier)
    {
        $powerORM = Rbac::getPower($powerIdentifier);

        if(empty($powerORM))
            throw new RbacException(\App\Services\Rbac\Exceptions\Ecode::POWER_EMPTY);

        $roleORMs = Rbac::getApplicationRoles($application->id);
        if(empty($roleORMs))
            return false;

        foreach($roleORMs as $roleORM)
        {
            $relationPowerRoleORM = Rbac::getRolePower($roleORM->id,$powerORM->id);
            if(!empty($relationPowerRoleORM))
                return true;
        }

        return false;
    }
}

如上我们可以看到这个类多实现了一个接口CanInterface,这是因为我将效验权限接口单独隔离出来了,怕后期设计对这个接口有单独的处理

第三步:独立定义CanInterface接口

大家可能发现了,can的入参是一个Eloquent Orm模型,这是因为我的用户依赖表主键id

<?php
/**
 * Notes:描述该文件的用途
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac;

use Illuminate\Database\Eloquent\Model;

interface CanInterface
{
    /**
     * Notes:效验权限
     * Author:tanyong
     * DateTime:2022/5/16
     * @param Model $application
     * @param String $powerIdentifier
     * @return boolean 是否效验通过
     */
    public function can(Model $application,String $powerIdentifier);

}

第四步:组件一个小型的工作台,用于在后台生成权限内容

(如果你不嫌麻烦,可以做一个可视化操作,我就是懒,所以简化了这个步骤,制作了一个小型管理后台)

权限定义

<?php
/**
 * Notes:权限定义
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac\Models;

class Powers
{
    /**
     * 资产更新
     */
    const API_UPDATE_FINANCE = 'api_update_finance';

    /**
     * 新建资产
     */
    const API_CREATE_FINANCE = 'api_create_finance';

    /**
     * 资产删除
     */
    const API_DELETE_FINANCE = 'api_delete_finance';
}

角色与权限的关联定义

<?php
/**
 * Notes:角色与权限的关联定义
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac\Models;

class RelationPowerRole
{
    /**
     * 权限关联
     */
    public static function get()
    {
        return [
            Roles::APPLICATION   =>  [
                Powers::API_UPDATE_FINANCE,Powers::API_CREATE_FINANCE
            ],
            Roles::ADMIN        =>  [
                Powers::API_DELETE_FINANCE
            ]
        ];
    }
}

角色定义

<?php
/**
 * Notes:角色定义
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac\Models;


class Roles
{

    /**
     * 应用
     */
    const APPLICATION = 'application';

    /**
     * 管理员
     */
    const ADMIN = 'admin';
}

第五步:创建laravel命令,用于管理权限

php artisan make:command RbacManager
<?php

namespace App\Console\Commands;

use App\Exceptions\AppException;
use App\Services\Rbac\Models\Powers;
use App\Services\Rbac\Models\RelationPowerRole;
use App\Services\Rbac\Models\Roles;
use App\Services\Rbac\RbacInterface;
use App\Services\Rbac\RbacService;
use Illuminate\Console\Command;

class RbacManager extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'rbac:genner';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        //第一步:创建权限
        $this->checkPower();

        //第二步:创建角色
        $this->checkRole();

        //第三步:创建角色与权限之间的关联
        $this->checkRelationRolePower();

        dd('run success');
    }

    public function checkRelationRolePower()
    {
        /**
         * @var RbacInterface $rbacService
         */
        $rbacService = app()->get(RbacInterface::class);

        $rules = RelationPowerRole::get();

        foreach($rules as $role=>$powers)
        {
            //角色效验
            $roleORM = $rbacService->getRole($role);
            if(empty($roleORM))
                throw new AppException([
                    'code'      =>  101,
                    'message'   =>  '此角色尚未注册'
                ]);
            foreach($powers as $power)
            {
                //权限效验
                $powerORM = $rbacService->getPower($power);
                if(empty($roleORM))
                    throw new AppException([
                        'code'      =>  102,
                        'message'   =>  '此权限尚未注册'
                    ]);

                //关系效验
                $relation = $rbacService->getRolePower($roleORM->id,$powerORM->id);
                if(empty($relation))
                {
                    $rbacService->relationRolePower($roleORM->id,$powerORM->id);
                }
            }
        }
    }

    public function checkPower()
    {
        /**
         * @var RbacInterface $rbacService
         */
        $rbacService = app()->get(RbacInterface::class);

        $Reflection = new \ReflectionClass(Powers::class);

        $constants = $Reflection->getConstants();

        foreach($constants as $k=>$constant)
        {
            $constantObj = new \ReflectionClassConstant(Powers::class,$k);
            $powerDescription = $this->getDoc($constantObj->getDocComment());
            $powerIdentifier  = $constant;

            $powerORM = $rbacService->getPower($powerIdentifier);
            if(empty($powerORM))
                $rbacService->registerPower($powerIdentifier,$powerDescription);
        }
    }

    public function checkRole()
    {
        /**
         * @var RbacInterface $rbacService
         */
        $rbacService = app()->get(RbacInterface::class);

        $Reflection = new \ReflectionClass(Roles::class);

        $constants = $Reflection->getConstants();

        foreach($constants as $k=>$constant)
        {
            $constantObj = new \ReflectionClassConstant(Roles::class,$k);
            $roleDescription = $this->getDoc($constantObj->getDocComment());
            $roleIdentifier  = $constant;

            $roleORM = $rbacService->getRole($roleIdentifier);
            if(empty($roleORM))
                $rbacService->registerRole($roleIdentifier,$roleDescription);
        }
    }

    public function getDoc(String $doc)
    {
        $doc = str_replace('*','',$doc);
        $doc = str_replace('/','',$doc);

        return trim($doc);
    }
}

执行命令即可将我们创建的角色,权限,及关联关系绑定起来

php artisan rbac:genner

第六步:创建一个中间件,用于权限管理

php artisan make:middleware AppRbacMiddleware

AppRbacMiddleware

<?php

namespace App\Http\Middleware;

use App\Exceptions\AppException;
use App\Exceptions\Ecode;

use App\Models\User;
use App\Services\Rbac\Facades\Rbac;
use App\Services\Rbac\Models\UrlPowers;
use App\Services\User\UserService;
use Closure;
use Illuminate\Http\Request;

class AppRbacMiddleware
{
    public $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next,$power=null)
    {
        $appid = $request->header('appid',null);
        if(empty($appid))
            throw new AppException(Ecode::PARAMS_EMPTY,'not get appid');

        $userORM = $this->userService->get($appid);

        $auth = false;
        if(!empty($power))
        {
            $auth = Rbac::can($userORM,$power);
        }else{
            $rules = UrlPowers::get($request->path());
            foreach($rules as $power)
            {
                $auth = Rbac::can($userORM,$power);
                if(!$auth)
                    throw new AppException(Ecode::RBAC_ERROR,'您无权进行此操作:'.(Rbac::getPower($power))->description);
            }
        }

        if($auth)
            return $next($request);

        throw new AppException(Ecode::RBAC_ERROR,'您无权进行此操作');
    }
}

第七步:定义url path 地址对应权限

<?php
/**
 * @copyright Copyright&copy;2022,浙江销巴科技有限公司保留所有权利
 * Notes:路由对应的权限
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac\Models;

class UrlPowers
{
    /**
     * @var 权限效验列表
     */
    public static $rules = [
        'tanyong-api/rbac/index1'   =>  [
            Powers::API_CREATE_FINANCE,Powers::API_DELETE_FINANCE
        ]
    ];

    public static function get($path)
    {
        return static::$rules[$path]??false;
    }
}

第八步:为用户分配权限

/**
     * Notes:操作授权
     * Author:tanyong
     * DateTime:2022/5/16
     * @return \Illuminate\Auth\Access\Response|void
     */
    public function register(UserFormRequest $request,UserService $userService)
    {
        //用户注册
        $userORM = $userService->register($request);

        //对用户进行授权
        $role = Rbac::getRole(Roles::APPLICATION);

        $relationAuth = Rbac::relationApplicationRole($userORM->id,$role->id);

        if($relationAuth)
            return response()->json([
                'code'  =>  0,
                'msg'   =>  'register success'
            ])->setEncodingOptions(JSON_UNESCAPED_UNICODE);

        throw new AppException(Ecode::REGISTER_ERROR,'注册失败');
    }

扩展:facades 门面,用于方便调用rbac服务

<?php
/**
 * Notes:Rbac 门面
 * History:文件历史
 * tanyong 2022/5/16
 */

namespace App\Services\Rbac\Facades;

use Illuminate\Support\Facades\Facade;

class Rbac extends Facade
{
    public static function getFacadeAccessor()
    {
        return \App\Services\Rbac\RbacInterface::class;
    }
}

服务提供者

php artisan make:provider RbacProvider

RbacProvider

<?php

namespace App\Providers;


use Illuminate\Support\ServiceProvider;

class RbacProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
         //容器注册 Rbac 服务
        $this->app->singleton(\App\Services\Rbac\RbacInterface::class,\App\Services\Rbac\RbacService::class);
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

之后将这个提供器放入配置,app.php providers 数组中

至此,rbac模型实现完成

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值