权限应存入$_session['permissions']索引数组并设更新时间戳,登录后通过users→user_roles→role_permissions→permissions四表关联查询加载,校验时严格匹配原子权限名,中间件统一拦截确保检查前置。

直接用 session 存权限数组最简单,但容易出安全问题——比如没校验用户身份就写入权限、没清理过期 session、或把权限名硬编码在前端控制里。
怎么设计数据库才不容易被绕过
别用单表存 user.permissions 字段(如 JSON 或逗号分隔),这种设计无法做外键约束,也难审计权限变更。必须拆成四张表:
-
users:只存身份信息,password必须用password_hash()加密 -
roles:角色是逻辑分组,不是权限本身,比如editor、admin -
permissions:每条记录是一个原子操作,如post:delete、user:edit_own,命名要带模块前缀 -
role_permissions和user_roles:两个关联表,确保多对多关系可查、可删、可审计
关键点:user_roles 表必须存在,不能靠 users.role_id 外键直连——一个用户可能有多个角色,比如既是 editor 又是 reviewer。
登录后怎么安全加载权限到 session
session 里不能只存 $_SESSION['role'],得存具体权限标识集合。否则每次判断都要查库,而且容易漏掉角色继承或动态权限变更。
- 登录成功后,立即用用户 ID 查
user_roles→role_permissions→permissions.name,拿到完整权限数组 - 存进
$_SESSION['permissions'],不是字符串拼接,而是array_values()去重后的索引数组 - 加个时间戳
$_SESSION['permissions_updated_at'] = time(),后续可设 15 分钟自动刷新,防权限变更后 session 滞后 - 务必在所有权限检查前调用
session_start(),且不能有任何输出(包括 UTF-8 BOM)
错误示范:$_SESSION['permissions'] = 'delete_user,edit_post' —— 这种字符串无法用 in_array() 安全判断,还容易被注入逗号绕过。
checkPermission() 函数为什么总失效
常见失效原因不是逻辑错,而是上下文没对齐。这个函数必须满足三个前提:
- 它只能读
$_SESSION['permissions'],绝不能自己再去查数据库(否则失去缓存意义,还可能因 session 未初始化而报错) - 传入的权限名必须和
permissions.name完全一致,大小写敏感,不能带空格或斜杠(如'post:delete '就会失败) - 必须在页面最开头调用,不能放在 HTML 输出之后,否则
header()重定向会失败 - 如果用在 AJAX 接口里,要额外检查
$_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest',返回 JSON 错误而非跳转
示例正确写法:
function checkPermission($perm) {
if (!isset($_SESSION['permissions']) || !is_array($_SESSION['permissions'])) {
return false;
}
return in_array($perm, $_SESSION['permissions'], true);
}
if (!checkPermission('user:delete_own')) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
exit;
}
为什么中间件比每个页面写 checkPermission() 更可靠
因为中间件天然卡在路由解析之后、控制器执行之前,能统一拦截所有请求路径,包括静态资源、API 接口、甚至未命名路由。而手写 checkPermission() 很容易漏掉某个 include 文件或 AJAX 入口。
- 中间件里不要依赖
$_SERVER['REQUEST_URI']做字符串匹配(比如strpos('/admin/', ...)),容易被/admin/../etc/passwd绕过 - 应该从路由定义中提取权限标识,例如 Laravel 的
$route->action['permission'],或自定义路由注释解析 - 中间件必须 throw Exception 或
exit,不能只 echo 提示——否则后续代码仍会执行 - 测试时重点验证:未登录用户访问受控路由 → 302 跳转;已登录但无权限 → 403;权限刚被后台回收 → 下次请求即生效(靠
permissions_updated_at控制)
最常被忽略的一点:权限检查必须在数据库查询之前完成。比如一个 delete_user.php?id=123 页面,不能先查出用户数据再判断权限,而应在读取 id 后立刻校验 user:delete_own,否则攻击者可能通过修改 id 参数触发越权读取。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











