PHP 中基于 Casbin 做 RBAC + RESTful 权限控制

陆景行
陆景行
发布时间:2026-08-25 | 38人浏览过
原创

前言

在 PHP 项目里接入权限系统,真正难的往往不是单次鉴权,而是角色继承、接口路径匹配和请求方法控制怎么统一管理。本文用 PHP-Casbin 的数据库适配器串起一套 RBAC + RESTful 示例,从模型配置到策略校验逐步拆解,并用实际判定结果说明这套方案的适用边界。

在 PHP 项目里做接口权限控制,常见难点不是“能不能拦”,而是角色继承、资源路径匹配和请求方法判断怎么放进同一套规则里。本文用 PHP-Casbin 的数据库适配器做一个 RBAC + RESTful 权限控制示例,按“安装、建模、写策略、验权限”的顺序拆开讲清楚,并通过 alice 与 bob 两个角色样例,帮助你快速判断这套方案是否适合自己的接口层。

安装 Casbin 与数据库适配器

PHP-Casbin 是一个面向访问控制的开源框架,支持多种权限模型。这里使用官方提供的数据库适配器扩展来保存策略规则,适合把角色、权限和继承关系持久化到数据库中,而不是只放在本地文件。

安装方式很直接,通过 composer 引入核心包和数据库适配器即可:

composer require casbin/casbin
composer require casbin/database-adapter

这一组合的重点在于职责划分比较清楚:casbin/casbin 负责模型解析和权限判定,casbin/database-adapter 负责把策略读写到数据库。后面无论是给用户分配角色,还是给角色追加接口权限,最终都能沉淀成数据库里的规则记录。

RBAC 模型怎样匹配 RESTful 接口

要让 Casbin 同时理解“谁访问什么资源、执行什么动作”,首先要把请求结构和匹配规则定义好。这里使用的是标准的 RBAC Model,并额外配合 RESTful 风格接口常用的路径与方法匹配。

用纸面卡片展示 Casbin 模型中用户、角色、路径与请求方法之间的匹配关系
模型配置如何同时理解角色与接口把请求主体、角色继承、RESTful 路径匹配和方法校验放进同一张专栏式结构图里。

model.conf 内容如下:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

#  RBAC角色继承关系的定义
[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)

这份模型可以直接拆成三层理解:

请求与策略字段的对应关系

r = sub, obj, act 表示一次权限请求包含主体、资源、动作三个字段;p = sub, obj, act 表示每条策略规则也按同样结构保存。对于 Web API 来说,通常可以把它理解成“用户 / 路径 / HTTP 方法”。

角色继承如何生效

g = _, _ 用来描述 RBAC 里的继承关系,例如用户属于某个角色,或者高权限角色再继承低权限角色。这样做的好处是,权限不需要重复分配到每个用户身上,只要先维护角色,再让用户挂到角色下面即可。

为什么能匹配 RESTful 路径

关键在 matchers 这一行:

展示 alice、bob、admin、member 与 foo 资源权限边界的暖色专栏手绘图
alice 与 bob 的最终权限对比同一组策略写入后,alice 与 bob 在读写能力上的差异可以直接通过角色继承关系看出来。
  • g(r.sub, p.sub):判断当前请求主体是否拥有策略中的角色或身份;
  • keyMatch2(r.obj, p.obj):判断请求路径是否匹配策略路径,比如 /foo/1 可以与 /foo/:id 对上;
  • regexMatch(r.act, p.act):判断请求动作是否匹配策略中的方法定义,适合处理 GET、POST、PUT、DELETE 这类 HTTP 方法。

因此,这个模型并不只是普通 RBAC,而是把“角色继承”和“RESTful 路由匹配”结合在了一起。

如何初始化 Casbin Enforcer

模型准备好之后,下一步就是创建 Enforcer,并把模型文件和数据库适配器接起来。示例代码如下:

use CasbinEnforcer;
use CasbinAdapterDatabaseAdapter;

$adapter = Adapter::newAdapter([
    'type'     => 'mysql', 
    'hostname' => '127.0.0.1',
    'database' => 'test',
    'username' => 'root',
]);

$enforcer = new Enforcer('path/to/model.conf', $adapter);

这里有两个实际要点值得注意:

  • 适配器连接的是 mysql,主机为 127.0.0.1,数据库名为 test,用户名为 root;
  • new Enforcer('path/to/model.conf', $adapter) 说明权限判断并不是只依赖代码里的数组或硬编码,而是会结合模型配置和数据库中的策略一起工作。

也就是说,后续你在业务代码中调用 addRoleForUser、addPermissionForUser 或 enforce,本质上都是围绕这个 Enforcer 展开。

角色、权限与继承关系怎么写入

这一部分是整套方案最核心的地方。示例里定义了两个用户:alice 和 bob;两个角色:admin 和 member。其中 admin 不仅拥有自己的写权限,还继承 member 的读权限。

先给用户分配角色

// alice has the admin role
$enforcer->addRoleForUser('alice', 'admin'); 
// bob has the member role
$enforcer->addRoleForUser('bob', 'member');

这一步确定的是用户身份归属:alice 归到 admin,bob 归到 member。此时用户还不是直接拿到权限,而是先挂到角色体系上。

给 member 角色分配只读权限

示例中,member 角色只能查看 foo 资源:

$enforcer->addPermissionForUser('member', '/foo', 'GET');
$enforcer->addPermissionForUser('member', '/foo/:id', 'GET');

这里分成两条规则很重要。一条对应列表或集合资源 /foo,另一条对应带参数的单个资源 /foo/:id。这样做之后,访问 /foo 和 /foo/1 都能有明确的匹配依据。

让 admin 继承 member,并追加写权限

admin 角色不需要把读取权限重新抄一遍,可以直接继承 member:

// admin inherits all permissions of member
$enforcer->addRoleForUser('admin', 'member');

$enforcer->addPermissionForUser('admin', '/foo', 'POST');
$enforcer->addPermissionForUser('admin', '/foo/:id', 'PUT');
$enforcer->addPermissionForUser('admin', '/foo/:id', 'DELETE');

这段配置表达得很清楚:

  • admin 先继承 member,因此天然拥有读取 /foo 与 /foo/:id 的能力;
  • 然后再单独补上 POST、PUT、DELETE 三类写操作;
  • 最终 admin 就形成了对 foo 资源的增删改查权限。

分配完成后,数据库中的策略规则大致如下:

g, alice, admin
g, bob, member

p, memeber, /foo, GET
p, memeber, /foo/:id, GET

g, admin, member

p, admin, /foo, POST
p, admin, /foo/:id, PUT
p, admin, /foo/:id, DELETE

从这组规则可以直接看出,g 负责角色关系,p 负责具体权限。用户、角色、资源路径、请求方法分别落在不同规则里,后续维护时也更容易定位问题。

如何用 enforce 验证最终权限

策略写入后,最终还是要回到接口请求能否放行这个问题。Casbin 的判断入口就是 enforce,传入“用户、资源、动作”三个参数即可。

alice:拥有 admin,并继承 member 权限

alice 具有 admin 角色,因此会同时拥有 admin 和 member 的全部授权:

$enforcer->enforce('alice', '/foo', 'GET'); // true
$enforcer->enforce('alice', '/foo', 'GET'); // true

$enforcer->enforce('alice', '/foo', 'POST'); // true
$enforcer->enforce('alice', '/foo/1', 'PUT'); // true
$enforcer->enforce('alice', '/foo/1', 'DELETE'); // true

判断结果说明两件事:一是角色继承确实生效了,二是 keyMatch2 对 /foo/1 与 /foo/:id 的匹配也正常工作。因此,alice 对 foo 资源具备完整的读写能力。

bob:只有 member,可读不可写

bob 只有 member 角色,所以只会继承该角色本身定义的读取权限:

$enforcer->enforce('bob', '/foo', 'GET'); // true
$enforcer->enforce('bob', '/foo', 'GET'); // true

$enforcer->enforce('bob', '/foo', 'POST'); // false
$enforcer->enforce('bob', '/foo/1', 'PUT'); // false
$enforcer->enforce('bob', '/foo/1', 'DELETE'); // false

这里的结果也很直观:GET 可以通过,POST、PUT、DELETE 都会被拒绝。这正对应前面为 member 角色设定的只读权限边界。

这套写法适合什么场景

如果你的 PHP 项目里存在明显的角色层级,例如访客、普通成员、管理员,并且接口遵循 RESTful 风格,那么这一组合会比较顺手:

  • 角色关系通过 g 维护,适合处理继承;
  • 接口权限通过 p 维护,适合按路径和方法控制;
  • 资源路径交给 keyMatch2,适合带参数的路由;
  • 请求方法交给 regexMatch,便于对 HTTP 动作做精确判断。

对于需要把“用户是谁”“能访问哪些接口”“哪些接口只能读、哪些接口能写”拆开治理的项目,这种做法比在控制器里手写一堆 if/else 更容易扩展,也更适合后期统一审计和调整。

总结

这套 PHP-Casbin 示例的核心思路并不复杂:先用 model.conf 定义 RBAC 与 RESTful 路径匹配规则,再通过数据库适配器持久化角色和策略,最后统一用 enforce 做请求判定。这样一来,角色继承、接口路径和请求方法就能落在同一套权限体系里管理,既保留了 RBAC 的清晰层级,也兼顾了 Web API 场景下对资源与动作的细粒度控制。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表