首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Casbin + ThinkORM 打造 Webman RBAC 权限管理实战

Casbin + ThinkORM 打造 Webman RBAC 权限管理实战

作者头像
Tinywan
发布2025-09-11 19:25:08
发布2025-09-11 19:25:08
16300
代码可运行
举报
文章被收录于专栏:开源技术小栈开源技术小栈
运行总次数:0
代码可运行

概述

RBAC(Role-Based Access Control,基于角色的访问控制)是一种经典的权限模型,它通过用户-角色-权限的关联来实现灵活的访问控制。

如果你是 PHP 开发者,使用 Webman 框架,那么结合 Casbin 和 ThinkORM,可以轻松构建一个高效的 RBAC 系统。

本文将基于一个典型的数据库设计图(如下所示)

一步步教你如何实现。图中展示了核心表结构,包括 sys_api、sys_menu_api、sys_menu、casbin_rule 等表。

为什么选择 Webman + Casbin ?

  • RBAC 模型:简单高效,用户绑定角色,角色绑定权限(菜单或 API),避免了直接用户-权限的复杂关联。支持多角色继承和数据范围控制。
  • Casbin(webman-permission) :这是 Casbin 的 Webman 专用插件,支持多种访问控制模型(如 ACL、RBAC、ABAC),内置持久化存储(这里用数据库)。插件提供无缝集成,易于中间件使用。
  • ThinkORM:一个轻量 ORM,支持模型关联、查询构建、预加载等特性,非常适合 Webman 的高性能环境。Webman 通过插件轻松集成 ThinkORM,避免了原生 SQL 的繁琐。
  • Webman 框架:高性能、非阻塞 IO,支持协程,适合 API 服务。结合以上工具,系统响应时间可控制在毫秒级。

这种组合的优势:代码简洁、可维护性强,支持动态权限调整,适用于后台管理系统。相比传统 ACL,它更灵活;相比 Shiro 等 Java 工具,它更轻量且 PHP 原生。

数据库设计详解

基于提供的 ER 图,我们设计了以下核心表。这些表实现了用户-角色-菜单-API 的多对多关联,并用 Casbin 规则表存储权限策略。每个表我都会用 Markdown 表格列出字段详情,包括字段名、类型、是否主键、默认值、注释等。

1. 用户表(sys_user)

字段名

类型

主键

默认值

注释

uid

bigint(20) unsigned

AUTO_INCREMENT

用户 ID

username

varchar(50)

''

用户名

password

varchar(255)

''

密码(加密)

email

varchar(100)

''

邮箱

status

tinyint(1)

1

状态(1:启用)

存储基本用户信息。用户通过角色间接获得权限。外键关联:无直接,但通过 sys_user_role 与角色关联。

2. 角色表(sys_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_rolesys_role_menu

3. 用户-角色关联表(sys_user_role)

字段名

类型

主键

默认值

注释

user_id

bigint(20) unsigned

0

用户 ID

role_id

bigint(20) unsigned

0

角色 ID

多对多关联,实现用户绑定多个角色。复合主键:(user_id, role_id)

4. 菜单表(sys_menu)

字段名

类型

主键

默认值

注释

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)

5. 角色-菜单关联表(sys_role_menu)

字段名

类型

主键

默认值

注释

role_id

bigint(20) unsigned

0

角色 ID

menu_id

bigint(20) unsigned

0

菜单 ID

多对多关联,实现角色绑定菜单。

6. API 接口表(sys_api)

字段名

类型

主键

默认值

注释

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 接口信息。

7. 菜单-API 关联表(sys_menu_api)

字段名

类型

主键

默认值

注释

menu_id

bigint(20) unsigned

0

菜单 ID

api_id

bigint(20) unsigned

0

API ID

菜单绑定 API,实现前端菜单对应后端接口的权限联动。

8. Casbin 规则表(casbin_rule)

字段名

类型

主键

默认值

注释

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

首先,安装所需插件:

代码语言:javascript
代码运行次数:0
运行
复制
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.1 配置 ThinkORM:在 config/thinkorm.php 中设置数据库连接:

代码语言:javascript
代码运行次数:0
运行
复制
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 中设置:

代码语言:javascript
代码运行次数:0
运行
复制
<?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

代码语言:javascript
代码运行次数:0
运行
复制
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
return $builder->build();

1.4 创建 Casbin 模型:在 app/model/CasbinRule.php

代码语言:javascript
代码运行次数:0
运行
复制
namespace app\model;

use think\Model;

class CasbinRule extends Model
{
    protected $table = 'casbin_rule';
    public $timestamps = false;
}

步骤2:定义 ThinkORM 模型

app/model 目录下创建模型类,继承 \think\Model。每个模型包含关联定义,便于链式查询。

示例:SysUser.php

代码语言:javascript
代码运行次数:0
运行
复制
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() 关联)

代码语言:javascript
代码运行次数:0
运行
复制
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() 关联)

代码语言:javascript
代码运行次数:0
运行
复制
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(

步骤3:同步权限到 Casbin

在控制器中实现权限同步。示例:RoleController.php

代码语言:javascript
代码运行次数:0
运行
复制
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('权限同步成功');
    }
}

步骤4:权限检查

创建中间件:middleware/AuthorizationMiddleware.php

代码语言:javascript
代码运行次数:0
运行
复制
<?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

代码语言:javascript
代码运行次数:0
运行
复制
return [
    '' => [ // 全局
        app\middleware\AuthorizationMiddleware ::class,
    ],
];

结语

基于 Casbin 和 ThinkORM 的 RBAC 实现,不仅代码优雅,还高度可扩展。赶紧在你的 Webman 项目中试试吧!如果有疑问,欢迎评论区交流。喜欢就点个赞、关注公众号,一起成长~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 为什么选择 Webman + Casbin ?
  • 数据库设计详解
    • 1. 用户表(sys_user)
    • 2. 角色表(sys_role)
    • 3. 用户-角色关联表(sys_user_role)
    • 4. 菜单表(sys_menu)
    • 5. 角色-菜单关联表(sys_role_menu)
    • 6. API 接口表(sys_api)
    • 7. 菜单-API 关联表(sys_menu_api)
    • 8. Casbin 规则表(casbin_rule)
  • 实现步骤
    • 步骤1:环境准备和配置
    • 步骤2:定义 ThinkORM 模型
    • 步骤3:同步权限到 Casbin
    • 步骤4:权限检查
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档