开发系统中,关于权限这块儿我想大家都不陌生,就是对一个操作进行权限判定,对用户当前请求的操作效验权限是否允许此用户执行这个动作。最近在学习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©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模型实现完成
今天的文章laravel rbac权限管理系统分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/32105.html