PHP角色权限控制需严格实现用户→角色→权限三层关系,数据库用users、roles、permissions三张主表加user_roles、role_permissions两张关联表,禁用JSON存权限;登录后缓存用户权限数组至Session或Redis,变更时主动失效;权限校验应通过中间件统一拦截并明确返回403。

PHP角色权限控制不是靠一个函数或框架自动完成的,核心是把「用户→角色→权限」三层关系落到数据库和代码逻辑里,漏掉任意一层都会导致越权。
数据库怎么设计角色权限关系
必须用三张表:users、roles、permissions,再加两张关联表:user_roles(用户-角色多对多)、role_permissions(角色-权限多对多)。不要试图用 JSON 字段存权限,否则无法用 SQL 精确查询、无法加索引、无法审计。
常见错误是把权限直接挂在 users 表里加一个 role 字段——这只能支持单角色,且改权限就得改用户数据,一动全动。
-
roles表只要id和name(如'admin'、'editor') -
permissions表存原子操作,比如'post:create'、'user:delete',别用中文或长描述 -
role_permissions里每条记录只表示「某个角色拥有某项权限」,不带条件、不带范围
如何在请求中快速判断用户是否有某权限
不能每次请求都查 4 张表。典型做法是:用户登录后,一次性查出该用户所有权限标识,缓存在 Session 或 Redis 中,键名建议用 "user_perms_{$uid}";后续用 in_array() 或 array_key_exists() 判断。
立即学习“PHP免费学习笔记(深入)”;
示例逻辑:
$perms = $_SESSION['user_perms'] ?? [];
if (!in_array('post:edit', $perms)) {
http_response_code(403);
exit('Forbidden');
}
注意:缓存必须在角色/权限变更时主动失效(比如管理员改了某角色权限,要删掉对应所有用户的 user_perms_* 缓存),否则会脏读。
中间件或基类里怎么统一拦截无权访问
如果你用的是原生 PHP 或轻量框架(如 Slim、Flight),推荐写一个权限检查中间件,而不是在每个控制器方法开头写重复的 if (!hasPerm('xxx'))。
关键点:
- 中间件应接收一个权限数组(如
['post:list', 'post:read']),满足任一即可,或要求全部满足(需显式传$requireAll = true) - 不要硬编码路径匹配规则,比如
if (strpos($_SERVER['REQUEST_URI'], '/admin/') === 0)—— 这和权限无关,是路由职责 - 拦截后跳转或返回 403 必须明确,不要静默跳首页或空白页,否则前端调试抓狂
Laravel 的 can() 和 @can 为什么有时不生效
根本原因通常是 Gate 定义没加载,或模型策略(Policy)没绑定,或 Auth::user() 返回 null(比如中间件顺序错,放在了 auth middleware 之前)。
排查步骤:
- 确认
Auth::check()为 true,否则can()直接返回 false - 检查
AuthServiceProvider@boot()里是否调用了Gate::resource()或Gate::define() - 策略类命名和位置是否符合 Laravel 约定(如
PostPolicy放在app/Policies/,且在AuthServiceProvider中注册) -
@can('update', $post)中的$post必须是模型实例,不能是数组或空对象,否则策略方法收不到有效参数
最易忽略的一点:Laravel 默认 Gate 不会自动加载策略,必须在 AuthServiceProvider 的 boot() 方法里显式调用 Gate::policy(Post::class, PostPolicy::class),否则 @can 永远走默认 deny。



















