RBAC(Role-Based Access Control,基于角色的访问控制)是一种经典的权限模型,它通过用户-角色-权限的关联来实现灵活的访问控制。
如果你是 PHP 开发者,使用 Webman 框架,那么结合 Casbin 和 ThinkORM,可以轻松构建一个高效的 RBAC 系统。
本文将基于一个典型的数据库设计图(如下所示)
一步步教你如何实现。图中展示了核心表结构,包括 sys_api、sys_menu_api、sys_menu、casbin_rule
等表。
这种组合的优势:代码简洁、可维护性强,支持动态权限调整,适用于后台管理系统。相比传统 ACL,它更灵活;相比 Shiro 等 Java 工具,它更轻量且 PHP 原生。
基于提供的 ER 图,我们设计了以下核心表。这些表实现了用户-角色-菜单-API 的多对多关联,并用 Casbin 规则表存储权限策略。每个表我都会用 Markdown 表格列出字段详情,包括字段名、类型、是否主键、默认值、注释等。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
uid | bigint(20) unsigned | 是 | AUTO_INCREMENT | 用户 ID |
username | varchar(50) | 否 | '' | 用户名 |
password | varchar(255) | 否 | '' | 密码(加密) |
varchar(100) | 否 | '' | 邮箱 | |
status | tinyint(1) | 否 | 1 | 状态(1:启用) |
存储基本用户信息。用户通过角色间接获得权限。外键关联:无直接,但通过 sys_user_role 与角色关联。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
id | bigint(20) unsigned | 是 | AUTO_INCREMENT | 角色 ID |
name | varchar(50) | 否 | '' | 角色名称(如“管理员”) |
code | varchar(50) | 否 | '' | 角色编码(如“admin”) |
data_scope | varchar(50) | 否 | 'all' | 数据范围(如“all”) |
status | tinyint(1) | 否 | 1 | 状态(1:启用) |
角色是权限的载体,一个用户可有多角色。外键关联:通过 sys_user_role
和 sys_role_menu
。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
user_id | bigint(20) unsigned | 否 | 0 | 用户 ID |
role_id | bigint(20) unsigned | 否 | 0 | 角色 ID |
多对多关联,实现用户绑定多个角色。复合主键:(user_id, role_id)
。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
id | bigint(20) unsigned | 是 | AUTO_INCREMENT | 菜单 ID |
name | varchar(50) | 否 | '' | 菜单名称(英文) |
title | varchar(100) | 否 | '' | 菜单标题(中文) |
type | tinyint(1) | 否 | 0 | 类型(0:目录,1:菜单,2:按钮) |
parent_id | bigint(20) unsigned | 否 | 0 | 父菜单 ID |
component | varchar(255) | 否 | '' | 前端组件路径 |
path | varchar(255) | 否 | '' | 路由路径 |
icon | varchar(100) | 否 | '' | 图标 |
sort | int(11) | 否 | 0 | 排序 |
菜单代表前端路由或按钮权限。支持树形结构(parent_id)
。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
role_id | bigint(20) unsigned | 否 | 0 | 角色 ID |
menu_id | bigint(20) unsigned | 否 | 0 | 菜单 ID |
多对多关联,实现角色绑定菜单。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
id | bigint(20) unsigned | 是 | AUTO_INCREMENT | API ID |
name | varchar(50) | 否 | '' | API 名称 |
title | varchar(100) | 否 | '' | API 标题 |
path | varchar(255) | 否 | '' | API 路径(如“/user/list”) |
type | varchar(10) | 否 | '' | 类型(如“GET”) |
action | varchar(50) | 否 | '' | 动作(如“read”) |
存储后端 API 接口信息。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
menu_id | bigint(20) unsigned | 否 | 0 | 菜单 ID |
api_id | bigint(20) unsigned | 否 | 0 | API ID |
菜单绑定 API,实现前端菜单对应后端接口的权限联动。
字段名 | 类型 | 主键 | 默认值 | 注释 |
---|---|---|---|---|
id | bigint(20) unsigned | 是 | AUTO_INCREMENT | 规则 ID |
ptype | varchar(128) | 否 | '' | 策略类型(如“p”) |
v0 | varchar(128) | 否 | '' | 主体(如“admin”) |
v1 | varchar(128) | 否 | '' | 对象(如“/user/list”) |
v2 | varchar(128) | 否 | '' | 动作(如“GET”) |
v3 | varchar(128) | 否 | '' | 扩展(如 data_scope) |
v4 | varchar(128) | 否 | '' | 扩展 |
v5 | varchar(128) | 否 | '' | 扩展 |
Casbin 使用此表存储权限规则。形成完整的 RBAC 链条:用户 → 角色 → 菜单 → API,并由 Casbin 统一执行策略。
假设你已安装 Webman。
安装地址:https://www.workerman.net/doc/webman/install.html
首先,安装所需插件:
composer require -W workerman/webman-framework
composer require -W webman/think-orm
composer require -W casbin/webman-permission
安装后,重启 Webman:php start.php restart
(或 -d
守护模式)。
1.1 配置 ThinkORM:在 config/thinkorm.php
中设置数据库连接:
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => env('DB_HOST', '127.0.0.1'),
'database' => env('DB_DATABASE', 'rbac_db'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'hostport' => env('DB_PORT', 3306),
'charset' => 'utf8mb4',
'prefix' => 'sys_',
],
],
];
1.2 配置 Casbin:在 config/plugin/casbin/webman-permission/permission.php
中设置:
<?php
/**
* @desc permission.php 描述信息
* @author Tinywan(ShaoBo Wan)
*/
declare(strict_types=1);
return [
'default' => 'basic',
/** 日志配置 */
'log' => [
'enabled' => false,
'logger' => 'Casbin',
'path' => runtime_path() . '/logs/casbin.log'
],
/** 默认配置 */
'basic' => [
'model' => [
'config_type' => 'file',
'config_file_path' => config_path() . '/plugin/casbin/webman-permission/rbac_model.conf',
'config_text' => '',
],
// 适配器
'adapter' => Casbin\WebmanPermission\Adapter\DatabaseAdapter::class, // ThinkORM 适配器
// 数据库设置
'database' => [
'connection' => '',
'rules_table' => 'casbin_rule',
'rules_name' => null
],
],
];
1.3 配置 DI 容器:在 config/container.php
:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
return $builder->build();
1.4 创建 Casbin 模型:在 app/model/CasbinRule.php
:
namespace app\model;
use think\Model;
class CasbinRule extends Model
{
protected $table = 'casbin_rule';
public $timestamps = false;
}
在 app/model
目录下创建模型类,继承 \think\Model
。每个模型包含关联定义,便于链式查询。
示例:SysUser.php
namespace app\model;
usethink\Model;
class SysUser extends Model
{
protected $table = 'sys_user';
protected $pk = 'uid'; // 主键字段
// 用户-角色关联(多对多)
publicfunction roles()
{
return$this->belongsToMany(SysRole::class, 'sys_user_role', 'role_id', 'user_id');
}
// 示例:获取用户所有角色编码
publicfunction getRoleCodes()
{
return$this->roles()->column('code');
}
}
SysRole.php(类似,添加 menus() 关联)
namespace app\model;
usethink\Model;
class SysRole extends Model
{
protected $table = 'sys_role';
// 角色-菜单关联
publicfunction menus()
{
return$this->belongsToMany(SysMenu::class, 'sys_role_menu', 'menu_id', 'role_id');
}
}
SysMenu.php(添加 apis() 关联)
namespace app\model;
usethink\Model;
class SysMenu extends Model
{
protected $table = 'sys_menu';
// 菜单-API 关联
publicfunction apis()
{
return$this->belongsToMany(SysApi::class, 'sys_menu_api', 'api_id', 'menu_id');
}
}
其他模型类似。使用预加载:如 role->with(['menus.apis'])->find(
在控制器中实现权限同步。示例:RoleController.php
namespace app\controller;
usesupport\Request;
useapp\model\SysRole;
useCasbin\WebmanPermission\Permission;
class RoleController
{
publicfunction assignMenu(Request $request)
{
$roleId = $request->post('roleId');
$menuIds = $request->post('menuIds', []); // 数组,防空
// 步骤3.1: 查找角色
$role = SysRole::find($roleId);
if (!$role) {
return response('角色不存在', 404);
}
// 步骤3.2: 同步菜单关联(ThinkORM 的 sync 方法会删除旧的,添加新的)
$role->menus()->sync($menuIds);
// 步骤3.3: 清空旧规则(基于角色编码)
Permission::deletePermissionsForUser($role->code);
// 步骤3.4: 同步新规则(遍历菜单和 API)
$role->load('menus.apis'); // 预加载
foreach ($role->menus as $menu) {
foreach ($menu->apis as $api) {
// 添加策略:sub, obj, act
Permission::addPolicy($role->code, $api->path, $api->type); // type 如 GET
}
}
return response('权限同步成功');
}
}
创建中间件:middleware/AuthorizationMiddleware.php
<?php
/**
* @desc 权限检查中间件 AuthorizationMiddleware.php
* @author Tinywan(ShaoBo Wan)
*/
declare(strict_types=1);
namespaceapp\middleware;
useCasbin\Exceptions\CasbinException;
useCasbin\WebmanPermission\Permission;
useTinywan\ExceptionHandler\Exception\ForbiddenHttpException;
useTinywan\ExceptionHandler\Exception\UnauthorizedHttpException;
useTinywan\Jwt\JwtToken;
useWebman\Http\Request;
useWebman\Http\Response;
useWebman\MiddlewareInterface;
class AuthorizationMiddleware implements MiddlewareInterface
{
/**
* @param Request $request
* @param callable $handler
* @return Response
* @throws ForbiddenHttpException
* @throws UnauthorizedHttpException
*/
publicfunction process(Request $request, callable $handler): Response
{
$role = JwtToken::getExtendVal('role');
if (empty($role)) {
throw new UnauthorizedHttpException();
}
try {
$url = request()->path();
$action = request()->method();
if (!Permission::enforce($role, $url, strtoupper($action))) {
throw new ForbiddenHttpException();
}
} catch (CasbinException $exception) {
throw new ForbiddenHttpException();
}
return $handler($request);
}
}
注册中间件: config/middleware.php
return [
'' => [ // 全局
app\middleware\AuthorizationMiddleware ::class,
],
];
基于 Casbin 和 ThinkORM 的 RBAC 实现,不仅代码优雅,还高度可扩展。赶紧在你的 Webman 项目中试试吧!如果有疑问,欢迎评论区交流。喜欢就点个赞、关注公众号,一起成长~