PHP项目如何实现用户自定义权限?

wen java案例 2

本文目录导读:

PHP项目如何实现用户自定义权限?

  1. 核心概念
  2. 方案一:基于角色的简单权限控制(RBAC)管理系统、企业后台、权限颗粒度不特别精细的系统。
  3. 方案二:基于ACL的细粒度控制(比RBAC更灵活)
  4. 方案三:极简版(适合小型项目)
  5. 生产环境建议

实现PHP项目的用户自定义权限,通常有几种成熟的设计模式,最常用、也最灵活的是 RBAC(基于角色的访问控制),更高级的场景可以使用 ACL(访问控制列表)

下面我为你梳理一套从简单到复杂的实现方案,并提供可直接运行的代码示例。

核心概念

  1. 用户 (User):系统的使用者。
  2. 角色 (Role):权限的集合(如:管理员、编辑、普通用户)。
  3. 权限 / 节点 (Permission):最小操作单元(如:user.create, article.edit)。
  4. 用户组:与角色类似,通常用于大规模用户权限继承。

基于角色的简单权限控制(RBAC)管理系统、企业后台、权限颗粒度不特别精细的系统。

数据库表结构设计(MySQL)

-- 1. 用户表(假设已有)省略,只需要包含 role_id 字段
-- 2. 角色表
CREATE TABLE `roles` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '角色名称',
  `description` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 3. 权限表(定义系统中所有操作)
CREATE TABLE `permissions` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '权限标识,如 user.create',
  `display_name` varchar(50) NOT NULL COMMENT '显示名称',
  `module` varchar(50) DEFAULT NULL COMMENT '所属模块',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 4. 角色-权限关联表(多对多)
CREATE TABLE `role_permission` (
  `role_id` int(11) unsigned NOT NULL,
  `permission_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`role_id`, `permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

PHP 核心实现

<?php
// PermissionChecker.php
class PermissionChecker {
    private $db;
    private $userRoles = []; // 缓存用户的角色
    public function __construct($pdo) {
        $this->db = $pdo;
    }
    // 加载用户角色及权限(建议在登录后缓存到Session)
    public function loadUserPermissions($userId) {
        $stmt = $this->db->prepare("
            SELECT r.id as role_id, r.name as role_name, p.name as permission_name
            FROM users u
            JOIN roles r ON u.role_id = r.id
            LEFT JOIN role_permission rp ON r.id = rp.role_id
            LEFT JOIN permissions p ON rp.permission_id = p.id
            WHERE u.id = ?
        ");
        $stmt->execute([$userId]);
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $permissions = [];
        foreach ($results as $row) {
            if ($row['permission_name']) {
                $permissions[] = $row['permission_name'];
            }
        }
        // 这里你可以将 $permissions 存入 $_SESSION['user_permissions']
        return $permissions;
    }
    // 核心判断:检查当前用户是否有某个权限
    public function hasPermission($userId, $permissionName) {
        // 1. 尝试从Session获取(避免重复查询数据库)
        if (isset($_SESSION['user_permissions'])) {
            return in_array($permissionName, $_SESSION['user_permissions']);
        }
        // 2. 如果Session没有,从数据库加载
        $permissions = $this->loadUserPermissions($userId);
        // 更新Session
        $_SESSION['user_permissions'] = $permissions;
        return in_array($permissionName, $permissions);
    }
    // 便捷方法:用于在控制器/中间件中直接检查权限
    public function check($permissionName) {
        // 假设当前用户ID从Session获取
        $userId = $_SESSION['user_id'] ?? 0;
        if (!$userId || !$this->hasPermission($userId, $permissionName)) {
            // 抛出异常或跳转到403页面
            header('HTTP/1.1 403 Forbidden');
            exit('您没有权限执行此操作');
        }
        return true;
    }
}

使用方式

// 在控制器中
$checker = new PermissionChecker($pdo);
$checker->check('article.create'); // 无权限则自动终止
// 或者在视图/模板中判断
if ($checker->hasPermission($userId, 'article.edit')) {
    echo '<a href="/article/edit?id=123">编辑</a>';
}

基于ACL的细粒度控制(比RBAC更灵活)

适用于:SaaS平台、允许用户自定义任意权限组合的场景。

设计思路

  • 用户不再固定属于某个角色,而是可以直接添加/删除权限
  • 可以采用位运算或JSON存储权限,但推荐保持多对多关系,便于管理。

数据库调整(基于方案一扩展)

-- 5. 用户-权限直接关联表(用于自定义权限,代替或补充角色)
CREATE TABLE `user_permission` (
  `user_id` int(11) unsigned NOT NULL,
  `permission_id` int(11) unsigned NOT NULL,
  `is_deny` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否拒绝访问(黑名单模式)',
  PRIMARY KEY (`user_id`, `permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

权限判断逻辑(同时支持角色+自定义)

<?php
// 升级版权限检查:先看角色权限,再看用户自定义权限
public function hasPermission($userId, $permissionName) {
    // 1. 获取用户的自定义权限
    $customStmt = $this->db->prepare("
        SELECT up.is_deny
        FROM user_permission up
        JOIN permissions p ON up.permission_id = p.id
        WHERE up.user_id = ? AND p.name = ?
        LIMIT 1
    ");
    $customStmt->execute([$userId, $permissionName]);
    $custom = $customStmt->fetch(PDO::FETCH_ASSOC);
    // 2. 如果用户明确拒绝该权限,直接返回false
    if ($custom && $custom['is_deny'] == 1) {
        return false;
    }
    // 3. 如果用户明确授予该权限,直接返回true
    if ($custom && $custom['is_deny'] == 0) {
        return true;
    }
    // 4. 如果没有自定义设置,则回退到角色权限进行判断
    return $this->hasRolePermission($userId, $permissionName);
}
private function hasRolePermission($userId, $permissionName) {
    // 从角色权限表查询(实现同方案一)
}

极简版(适合小型项目)

如果项目很简单,不想建复杂关联表,可以用位运算JSON字段

使用位运算(权限数有限时)

// 定义常量
define('PERM_ADD', 1);      // 0001
define('PERM_EDIT', 2);     // 0010
define('PERM_DELETE', 4);   // 0100
define('PERM_VIEW', 8);     // 1000
// 用户表增加字段 permission_sum INT
// 赋予权限:add + edit + view = 1+2+8 = 11
$userPermission = 11;
// 检查是否有edit权限
if ($userPermission & PERM_EDIT) {
    echo '有编辑权限';
}

缺点:权限数量有限(32位或64位),难以扩展。

使用JSON字段(灵活但查询不便)

-- 用户表增加字段 permissions JSON
-- 数据示例: {"article": ["create", "edit"], "user": ["view"]}

PHP中直接用json_decode解析后in_array判断,适合用户数少、不需要按权限筛选用户的项目。


生产环境建议

  1. 缓存:权限表几乎不变,务必使用缓存(Redis / Memcached / 文件缓存),避免每次请求都查数据库。
  2. 中间件:在框架(如Laravel、ThinkPHP、Symfony)中,将权限检查做成中间件,统一处理。
  3. 权限树:如果权限很多(几十个),建议在管理后台实现权限分组,以树形结构展示,方便管理员勾选。
  4. 超级管理员:通常设置一个is_super = 1的字段或SUPER_ADMIN角色,该用户跳过所有权限检查
  5. 前端适配:权限不仅要控制后端API,前端菜单和按钮也应根据权限动态显示。
  • 推荐方案:使用RBAC(方案一) 作为基础,如果用户需要自定义权限,再引入ACL(方案二)user_permission表作为补充。
  • 核心思维:用户 -> (角色) + (自定义) -> 权限集合 -> 每次请求判断。

如果你正在使用具体的框架(如Laravel ThinkPHP),我可以提供该框架下的更具体的实现方式(比如Laravel的Gate或Spatie权限包)。

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