codeigniter权限系统必须用post_controller_constructor钩子校验、三段式节点定义、仅存role_id于session。钩子确保所有请求统一校验;节点如admin.user.edit解耦路由;session只存轻量标识,实时查缓存获取权限。

CodeIgniter 本身不带 RBAC 或角色权限系统,直接用 session 存用户角色 ID 并在每个控制器里手动判断,短期能跑,但很快会失控——漏校验、绕过、缓存不一致、API 和页面逻辑不统一,都是高频翻车点。真正可用的权限控制,必须把校验时机、节点定义、数据存储三个环节卡死。
为什么必须用 post_controller_constructor 钩子做校验
很多项目把权限检查写在基类控制器的 __construct() 里,结果 AJAX 请求、REST_Controller 子类、或某些重定向路径直接跳过了校验。根本原因是 CI 的执行顺序:post_controller_constructor 是控制器实例化完成、但任何方法(包括 index())都还没执行前的**最后统一入口**。
要让它生效,两件事不能漏:
-
$config['enable_hooks'] = TRUE必须在application/config/config.php中开启 -
application/config/hooks.php中要正确定义钩子,比如:$hook['post_controller_constructor'] = [ 'class' => 'Rbac', 'function' => 'check', 'filename' => 'rbac.php', 'filepath' => 'hooks' ]; - 钩子函数内必须能拿到当前路由信息:
$this->router->fetch_class()和$this->router->fetch_method(),别依赖$_SERVER['REQUEST_URI']解析,容易被伪造或错位
Rbac 类里怎么定义权限节点才不会和 URL 耦合
把节点写成 /admin/user/edit/123 或 admin/user/edit 这种 URL 式命名,等于把权限逻辑绑死在路由结构上。一旦改了 URL、加了参数规则、或要做“只允许编辑自己”的细粒度控制,就得全量改权限表和代码。
推荐用三段式语义化节点:admin.user.edit、api.order.export、manage.post.publish。它的好处是:
- 与实际路由解耦,
admin/user/edit和admin/user/update可以映射到同一个节点 - 扩展方便,需要按数据归属控制时,加个后缀即可:
admin.user.edit:own - 数据库字段建议叫
permission_code,不是url或route -
rbac_manage_node_hidden这类配置项只影响菜单渲染,**完全不影响访问控制逻辑**,别误以为设了它就等于关掉了权限检查
Session 里该存什么、不该存什么
把用户所有可访问节点列表(几十上百个字符串)直接塞进 $_SESSION['rbac_nodes'],上线初期没问题,但角色权限一调整,旧 session 就失效——用户得退出重登才能生效,体验差,还埋下安全漏洞。
正确做法是只存轻量标识:
- 存
role_id(整型)或rbac_role_hash(如md5($user_id . $role_updated_at)) - 每次请求时查缓存(Redis 推荐)或数据库,查
role_id → nodes映射;缓存 TTL 设 5–10 分钟,平衡一致性与性能 - 如果用 Ion-Auth,它默认只存
user_id,权限检查走模型层实时查,这种模式更稳妥 - 别在钩子里做
redirect(),尤其对 API 请求;应设$this->rbac->forbidden = TRUE或抛http_response_code(403),让后续逻辑统一处理返回格式
最易被忽略的一点:权限校验和菜单渲染必须用同一套节点定义。菜单表里的 permission_code 字段,必须和钩子中比对的节点字符串完全一致,大小写、分隔符、冒号位置都不能差——否则用户明明有权限,菜单却不显示,或者菜单显示了却 403,排查起来极耗时间。











