PHP数据权限怎么控制

wen PHP项目 1

本文目录导读:

PHP数据权限怎么控制

  1. 基于用户ID的权限(最基础)
  2. 基于角色或部门的权限(RBAC扩展)
  3. 基于组织树(层级结构)的权限(较复杂)
  4. 基于数据归属(数据本身字段)的权限
  5. 更优雅的解决方案:使用中间件与设计模式
  6. 总结与最佳实践

在PHP中控制数据权限(Data-Level Permissions),通常比功能权限(如“能否访问用户管理页面”)更复杂,核心原则是:根据当前用户(或用户角色、所属组织),在查询数据库时动态追加 WHERE 条件,限制其只能看到或操作被授权的数据。

主要有以下几种常见的实现模式:

基于用户ID的权限(最基础)

用户只能看到自己的数据。

  • 场景:用户中心,用户只能查看和编辑自己的资料。
  • 实现:在SQL查询中强制加入 WHERE user_id = ?
<?php
$currentUserId = $_SESSION['user_id']; // 从登录状态获取
// 查询用户自己的订单
$stmt = $pdo->prepare("SELECT * FROM orders WHERE user_id = ?");
$stmt->execute([$currentUserId]);
// 更新时也需校验
$stmt = $pdo->prepare("UPDATE users SET nickname = ? WHERE id = ?");
$stmt->execute([$newNickname, $currentUserId]); // 注意:此处已经限制了只能改自己的

关键点$currentUserId 不能被用户伪造,必须来自后端 Session/Token。

基于角色或部门的权限(RBAC扩展)

不同角色可以看到不同范围的同类型数据。 销售只能看自己的客户,销售经理可以看全部门的客户。

  • 场景:CRM系统。
  • 实现:将权限判断逻辑封装成“数据范围过滤器”。
<?php
function getDataPermissionSQL($pdo, $userId, $tableAlias = 'c') {
    // 1. 获取用户的角色和部门信息
    $user = getUserById($pdo, $userId);
    $role = $user['role']; // 如:'saler', 'manager', 'admin'
    $departmentId = $user['department_id'];
    // 2. 根据角色返回不同的SQL片段
    switch ($role) {
        case 'admin':
            // 管理员看全部
            return "1=1"; // 无限制
        case 'manager':
            // 经理看自己部门的
            return "{$tableAlias}.department_id = " . intval($departmentId);
        case 'saler':
            // 销售看自己的
            return "{$tableAlias}.user_id = " . intval($userId);
        default:
            return "1=0"; // 不允许查看
    }
}
// 在查询时使用
$permissionFilter = getDataPermissionSQL($pdo, $currentUserId, 'o');
$stmt = $pdo->prepare("SELECT * FROM orders o WHERE {$permissionFilter} AND o.status = ?");
$stmt->execute(['active']);

注意:直接拼接SQL有SQL注入风险,上面的例子中,intval() 确保了传入的是整数,如果是字符串必须使用参数绑定。更好的方式是使用ORM的查询构造器来构建条件。

基于组织树(层级结构)的权限(较复杂)

大型企业系统中,数据权限通常是树状结构,集团CEO可以看全国数据,省总经理看本省,城市经理看本市。

  • 场景:SaaS平台,多租户中的内部层级权限。
  • 实现:在数据结构中包含 org_path 字段,使用 LIKE 或字符串匹配 + 索引。
-- 用户表结构
-- id, name, org_id
-- 组织表结构
-- id, name, parent_id, path (如: "/1/10/100/")
-- 权限判断
-- 假如当前用户所属的org_path是 /1/10/100/
-- 那么他可以查看所有 org_path LIKE '/1/10/100/%' 的数据
-- 或者查看所有 org_id IN (100, 101, 1001...)  (需要先递归查出子组织)
// PHP逻辑
function getChildOrgIds($pdo, $orgId) {
    // 使用递归查询(MySQL 8.0+ 用CTE)或者循环查询
    $children = [$orgId];
    $query = "SELECT id FROM organizations WHERE parent_id = ?";
    // ... 递归逻辑
    return $children;
}
$accessibleOrgIds = getChildOrgIds($pdo, $currentUserOrgId);
$placeholders = implode(',', array_fill(0, count($accessibleOrgIds), '?'));
$stmt = $pdo->prepare("SELECT * FROM reports WHERE org_id IN ($placeholders)");
$stmt->execute($accessibleOrgIds);

基于数据归属(数据本身字段)的权限

数据本身有一个 owner_idcreated_by,结合上面的角色判断。

-- 例如项目管理系统,一个问题(Issue)有 创建者、负责人、关注者。
-- 权限规则:创建者、负责人、管理员可以编辑。
-- PHP 查询时
$currentUserId = $_SESSION['user_id'];
// 获取用户角色(是否为管理员)
$isAdmin = checkUserRole($currentUserId, 'admin');
if ($isAdmin) {
    $sql = "SELECT * FROM issues WHERE project_id = ?"; // 管理员看全部
} else {
    // 普通用户只能看与自己相关的
    $sql = "SELECT * FROM issues WHERE project_id = ? AND (owner_id = ? OR assignee_id = ?)";
}
$stmt = $pdo->prepare($sql);
if ($isAdmin) {
    $stmt->execute([$projectId]);
} else {
    $stmt->execute([$projectId, $currentUserId, $currentUserId]);
}

更优雅的解决方案:使用中间件与设计模式

为了不让权限逻辑散落得到处都是,可以采用 AOP(面向切面编程) 思想。

推荐的工具和模式:

  • 使用ORM的全局作用域(Global Scope)
    • Laravel 的 Global Scopes,在模型中定义 apply 方法,自动为所有查询追加 where user_id = ?,这是非常爽的一种方式,能彻底杜绝忘记加权限过滤的情况。
  • 使用Repository模式
    • 创建一个 OrderRepository,所有对订单的查询都必须通过这个Repository,在Repository内部统一处理数据权限过滤。
  • 使用中间件(Middleware)

    对于API请求,在进入Controller之前,中间件解析出用户信息并注入到请求对象中,后续的Service层可以使用该信息。

总结与最佳实践

  1. 永远信任后端,不信任前端:所有的 user_idorg_id 必须从Session/Token中读取,不能从请求参数中取。
  2. 统一过滤入口:尽量在模型层(ORM Scope)或数据访问层(Repository)解决,不要在Controller里每次手动写。
  3. 性能考虑
    • 避免在十万级数据表上使用 LIKE '/path/%' 进行权限过滤(会导致索引失效)。
    • 使用组织树权限时,可以先获取用户有权限的 org_id 列表,然后用 IN 查询,但注意 IN 中的ID数量,太多的话可以分批或改用临时表关联。
    • 关键索引:确保 user_id, org_id, owner_id 等过滤字段建立索引。
  4. 避免硬编码SQL片段:像 getDataPermissionSQL() 返回字符串的方式容易导致注入和安全风险。推荐使用ORM的 Query Builder 来动态构建条件,如 $query->whereIn(...)$query->where(...)
  5. 考虑使用现成的权限包
    • Laravel: Spatie/laravel-permission (主要做功能权限,可以辅助做数据权限的逻辑判断)。
    • 通用PHP: 结合 Doctrine ORM 的过滤器(Filters)。

最核心的思想就是:在数据查询语句被执行前,强制插入一条WHERE条件,这个条件基于当前已验证的用户身份。

抱歉,评论功能暂时关闭!