本文目录导读:

实现PHP项目的用户自定义权限,通常有几种成熟的设计模式,最常用、也最灵活的是 RBAC(基于角色的访问控制),更高级的场景可以使用 ACL(访问控制列表)。
下面我为你梳理一套从简单到复杂的实现方案,并提供可直接运行的代码示例。
核心概念
- 用户 (User):系统的使用者。
- 角色 (Role):权限的集合(如:管理员、编辑、普通用户)。
- 权限 / 节点 (Permission):最小操作单元(如:user.create, article.edit)。
- 用户组:与角色类似,通常用于大规模用户权限继承。
基于角色的简单权限控制(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判断,适合用户数少、不需要按权限筛选用户的项目。
生产环境建议
- 缓存:权限表几乎不变,务必使用缓存(Redis / Memcached / 文件缓存),避免每次请求都查数据库。
- 中间件:在框架(如Laravel、ThinkPHP、Symfony)中,将权限检查做成中间件,统一处理。
- 权限树:如果权限很多(几十个),建议在管理后台实现权限分组,以树形结构展示,方便管理员勾选。
- 超级管理员:通常设置一个
is_super = 1的字段或SUPER_ADMIN角色,该用户跳过所有权限检查。 - 前端适配:权限不仅要控制后端API,前端菜单和按钮也应根据权限动态显示。
- 推荐方案:使用RBAC(方案一) 作为基础,如果用户需要自定义权限,再引入ACL(方案二) 的
user_permission表作为补充。 - 核心思维:用户 -> (角色) + (自定义) -> 权限集合 -> 每次请求判断。
如果你正在使用具体的框架(如Laravel ThinkPHP),我可以提供该框架下的更具体的实现方式(比如Laravel的Gate或Spatie权限包)。