ThinkPHP 8 细粒度接口授权需四点落地:一、权限码须语义化(如article:delete),路由显式绑定或规则映射,存入auth_rule.rule_code并加唯一索引;二、校验前置且缓存到Redis角色粒度(key为role_permissions_{$roleId}),权限变更强清理;三、API禁用Session,JWT payload直签permissions数组,中间件in_array校验;四、后端必须字段级过滤响应数据,不可仅依赖前端按钮显隐。

ThinkPHP 8 实现细粒度接口授权,关键不是“加个权限中间件”就完事,而是把授权落到具体动作、绑定明确上下文、保障校验时机与数据一致性。以下四点是落地核心。
一、权限码必须带模块+动作语义,不能靠方法名拼接
比如「删除文章」的权限应是 article:delete,而不是仅用 $this->request->action() 得到的 delete。原因很直接:同一 delete 方法可能被用于用户删除、订单删除、评论删除,没有上下文就无法区分。
推荐做法:
- 在路由定义时显式绑定权限码,例如:
Route::delete('admin/article/:id', 'Admin/Article.delete')->option(['permission' => 'article:delete']); - 或统一映射规则:将路由路径
admin/article/delete自动转为article:delete(去掉前缀、小写、冒号分隔) -
auth_rule.rule_code字段必须存这个字符串,且加唯一索引,禁用中文、空格、特殊符号
二、校验必须在控制器执行前完成,且走缓存不查库
权限判断若每次请求都查 user_role → role_permission → auth_rule 三张表,高并发下数据库立刻成为瓶颈。必须用 Redis 按角色粒度缓存权限集合。
立即学习“PHP免费学习笔记(深入)”;
正确姿势:
- 缓存 key 设计为
role_permissions_{$roleId},存成 Set 结构(如 SMEMBERS) - 中间件中用
SISMEMBER判断,时间复杂度 O(1) - 后台修改权限(分配/回收)时,必须同步
DEL role_permissions_{$roleId},否则新权限永不生效 - 禁止用 static 数组缓存——Swoole 多 worker 下不共享,极易误判
三、API 场景禁用 Session 依赖,权限必须从 Token 中来
Auth::check() 默认依赖 Session,在纯 API 场景下恒返回 false,跟配置无关。JWT 是主流方案,权限应直接签发进 payload:
"permissions": ["article:delete", "user:export"]
中间件里只需:
- 解析 JWT 后取
$token->permissions - 用
in_array('article:delete', $token->permissions)判断即可 - 避免再查库或调用 Auth::check(),减少冗余开销
四、按钮显隐只是前端体验,后端必须做字段级响应过滤
前端根据权限控制按钮是否显示,但攻击者可绕过前端直接调用接口。后端不仅要校验接口是否有权限,还要对响应数据做字段级裁剪。
- 例如用户无权查看
is_admin字段,返回数据前必须 unset 或屏蔽该键 - 不要依赖模板层过滤,应在控制器或中间件中统一处理响应体
- 配合模型的
hidden属性或序列化器(如使用 think-serializer)更稳妥



















