在 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 风格接口常用的路径与方法匹配。

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 这一行:

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 场景下对资源与动作的细粒度控制。













