Casbin 常被用来解决“用户能不能访问某个接口”这类权限判断问题,而 ThinkPHP 5.1 在 5.1.6+ 之后已经具备完整的中间件能力,刚好适合把权限校验放到请求入口统一处理。本文按安装扩展、生成配置、接入中间件、准备策略和验证效果的顺序整理一遍,让你能判断这套做法是否适合自己当前的 ThinkPHP 项目。
为什么在 ThinkPHP 里用 Casbin 做权限控制
PHP-Casbin 是一个开源访问控制框架,支持多种访问控制模型。对于 ThinkPHP 项目来说,它的价值不只是“能做权限”,更重要的是可以把“谁能访问什么 URI、允许什么请求方法”变成可维护的策略数据,而不是散落在控制器里的条件判断。
Think-Casbin 则是专门面向 ThinkPHP 5.1 的扩展包,目的是让 Casbin 在项目中更容易落地。结合中间件后,常见的接口鉴权流程就会比较清晰:先拿到当前登录用户,再读取请求路径和请求方法,最后交给 Casbin 统一判断是否放行。
安装扩展并初始化 Casbin
如果项目还没有创建,可以先生成一个 ThinkPHP 5.1 工程:

composer create-project topthink/think=5.1.* tp5
进入 ThinkPHP 项目后,安装 Think-Casbin 扩展:
composer require casbin/think-adapter
安装完成后,需要发布资源文件:
php think casbin:publish
这一步会自动生成两个关键配置文件:
config/casbin-basic-model.conf:Casbin 的模型配置文件config/casbin.php:Casbin 在 ThinkPHP 中的运行配置
由于 Think-Casbin 默认把策略(Policy)存储到数据库中,所以还要执行数据迁移:
php think casbin:migrate
执行前要先确认数据库连接信息配置正确。如果你希望单独调整 Casbin 使用的数据库连接或表名,可以直接修改 config/casbin.php。迁移完成后,会自动创建策略表 casbin_rule。
把权限校验放进中间件
ThinkPHP 从 5.1.6+ 开始正式支持中间件,因此最直接的接入方式就是新增一个授权中间件,把每次请求都交给 Casbin 判断。

先通过命令生成中间件:
php think make:middleware Authorization
命令会在 application/http/middleware 目录下生成一个 Authorization 中间件。核心逻辑是从 Session 中读取当前用户名,再取出当前请求的 URL 和方法,最后调用 Casbin::enforce() 进行授权判断:
<?php
namespace apphttpmiddleware;
use Casbin;
use thinkfacadeSession;
class Authorization
{
public function handle($request, Closure $next)
{
// 当前登录用户名,这里以session为例
// $user = Session::get('user_name') ?: 'test_user';
$user = Session::get('user_name');
$url = $request->url();
$action = $request->method();
// var_dump($user, $uri, $action);die;
if (!$user){
return response()->data('Unauthenticated.')->code(401);
}
if (!Casbin::enforce($user, $url, $action)) {
return response()->data('Unauthorized.')->code(403);
}
return $next($request);
}
}
这段逻辑可以看成三层判断:
- 没有登录用户,直接返回
401,表示未认证 - 用户已登录,但策略不允许访问当前资源,返回
403 - 校验通过,继续执行后续请求流程
这种写法的好处是,控制器本身不必重复处理权限判断,接口是否可访问由策略表统一决定。
Casbin Model 配置怎么理解
发布资源后会得到模型配置文件,文中的示例内容如下:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && r.act == p.act
这份配置表达的是一个很典型的权限匹配模型:
sub表示访问主体,也就是用户obj表示访问对象,这里对应请求路径act表示动作,这里对应 HTTP 方法,如GET
真正决定效果的是 [matchers] 这一段:
r.sub == p.sub:请求用户必须和策略中的用户一致keyMatch2(r.obj, p.obj):请求路径支持按规则匹配,例如/users/1可以匹配/users/:idr.act == p.act:请求方法要和策略定义一致
也就是说,这套配置天然适合“按用户 + 路由 + 请求方法”做接口授权。如果项目权限模型还不复杂,这样的 basic model 已经能覆盖很多后台和 API 场景。
写入策略并绑定路由验证效果
在执行授权验证前,需要先往数据库的 casbin_rule 表里添加策略。示例策略如下:

| id | ptype | v0 | v1 | v2 |
|---|---|---|---|---|
| 1 | p | test_user | /users | GET |
| 2 | p | test_user | /users/:id | GET |
这两条策略表示:
test_user可以访问GET /userstest_user可以访问GET /users/:id
然后再定义路由,并把授权中间件挂到路由组上:
Route::group('users', function () {
Route::get('', function () {
return 'Users data.';
});
Route::get('/:id', function ($id) {
return 'User: '.$id;
});
})->middleware(apphttpmiddlewareAuthorization::class);
接下来,只要在登录后把用户名写入 SESSION,就可以访问 /users 和 /users/1 测试权限效果。整个验证链路是连贯的:
- Session 中存在当前用户,例如
test_user - 请求进入
Authorization中间件 - 中间件把用户名、URL、请求方法交给 Casbin
- Casbin 根据
casbin_rule和 model 配置决定是否放行
如果路径、请求方法或用户任意一项与策略不匹配,就会返回 403。如果根本没有登录用户,则返回 401。这也解释了为什么 Casbin 非常适合放在接口统一入口处理,而不是零散地写在各个控制器方法中。
落地时值得注意的几点
从这套示例可以看出,ThinkPHP 集成 Casbin 的关键不在代码量,而在于权限数据和请求信息是否能稳定对齐。实际使用时,至少要注意三件事。
1. 用户标识要统一
示例里使用的是 Session::get('user_name'),那么策略表中的 v0 也必须使用同一套用户名标识。如果项目里真正用于鉴权的是用户 ID、角色名或组织标识,就应该统一成同一种值。
2. 路由写法要和匹配规则一致
这里之所以能让 /users/1 命中 /users/:id,依赖的是 keyMatch2。如果你的实际路由风格不同,就要确认 model 里的匹配函数是否仍然适用。
3. 先想清楚是做用户授权还是角色授权
本文示例是直接对用户授权,即把 test_user 直接写进策略里。这种方式适合快速验证,也适合权限规模不大的项目。若后续需要更复杂的 RBAC 设计,再进一步引入角色关系会更合适。
文末原文还提到 Management API 和 RBAC API。如果你后面要做动态增删策略、角色继承或后台权限管理,这两部分通常就是下一步需要补上的能力。









