PHP 怎么PHP 权限治理

wen PHP项目 3

本文目录导读:

PHP 怎么PHP 权限治理

  1. 核心概念:权限管理三要素
  2. 数据库表结构设计 (以 RBAC 为例)
  3. PHP 代码实现 (原生 + 面向过程示例)
  4. 进阶与最佳实践
  5. 常见坑与建议

在 PHP 中进行权限治理(权限管理/访问控制)是一个非常核心且常见的安全与架构需求,由于 PHP 本身没有内置的、统一的权限系统,你需要根据项目规模和复杂度自行实现或使用第三方包。

下面我会从核心概念常见实现模式代码示例以及进阶方案四个维度来详细讲解。


核心概念:权限管理三要素

几乎所有权限系统都围绕这三个问题设计:

  1. Who:谁?(用户、角色)
  2. What:对什么?(资源,如文章、订单、API)
  3. How:怎么做?(操作,如增、删、改、查)

常见模型对比

模型 适用场景 优点 缺点
ACL (访问控制列表) 小团队、用户极少 简单、直接 用户多时管理噩梦
RBAC (基于角色的访问控制) 大多数中小型项目 权限与用户解耦,易扩展 角色粒度较粗
ABAC (基于属性的访问控制) 大型、复杂业务(如金融、OA) 最灵活、细粒度 实现复杂,性能消耗较大

一般推荐使用 RBAC(角色-权限),这是最平衡的方案。


数据库表结构设计 (以 RBAC 为例)

你需要设计至少 4-5 张表。

-- 1. 用户表
CREATE TABLE `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
);
-- 2. 角色表
CREATE TABLE `roles` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,  -- 如 admin, editor, viewer
  PRIMARY KEY (`id`)
);
-- 3. 权限表 (定义具体操作)
CREATE TABLE `permissions` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL, -- 如 article.create, user.delete
  `guard_name` varchar(50) DEFAULT 'web', -- 区分 web/api
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
);
-- 4. 用户-角色关联表
CREATE TABLE `role_user` (
  `user_id` int(11) unsigned NOT NULL,
  `role_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`user_id`, `role_id`),
  FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
  FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`)
);
-- 5. 角色-权限关联表
CREATE TABLE `role_has_permissions` (
  `permission_id` int(11) unsigned NOT NULL,
  `role_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`permission_id`, `role_id`),
  FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`),
  FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`)
);

PHP 代码实现 (原生 + 面向过程示例)

用户登录后,将权限缓存到 Session

<?php
// login.php
function login($userId) {
    // 1. 查询该用户的所有角色
    $roles = DB::query("SELECT role_id FROM role_user WHERE user_id = ?", [$userId]);
    // 2. 根据角色查询对应权限
    $permissions = [];
    foreach ($roles as $role) {
        $perms = DB::query("SELECT p.name 
                            FROM permissions p 
                            JOIN role_has_permissions rp ON p.id = rp.permission_id 
                            WHERE rp.role_id = ?", [$role['role_id']]);
        foreach ($perms as $perm) {
            $permissions[] = $perm['name'];
        }
    }
    // 3. 去重并存入 Session
    $_SESSION['user_permissions'] = array_unique($permissions);
}

权限检查函数

<?php
// helper.php
function can($permissionName) {
    // 超级管理员跳过检查 (一般通过角色名判断)
    if ($_SESSION['role'] === 'super_admin') {
        return true;
    }
    // 检查当前用户是否有此权限
    return in_array($permissionName, $_SESSION['user_permissions'] ?? []);
}

在控制器或路由中使用

<?php
// ArticleController.php
class ArticleController {
    public function create() {
        if (!can('article.create')) {
            http_response_code(403);
            die('无权限创建文章');
        }
        // 权限通过,继续业务逻辑
        echo "创建文章成功";
    }
    public function delete($id) {
        // 更细粒度:检查是否为自己的文章 (需要额外逻辑)
        $article = Article::find($id);
        if (!can('article.delete') || $article->user_id !== $_SESSION['user_id']) {
            http_response_code(403);
            die('无权删除');
        }
        // ...
    }
}

进阶与最佳实践

使用 Middleware (中间件) 统一拦截

在现代 PHP 框架(Laravel、ThinkPHP、Symfony)中,推荐将权限检查放在中间件层,避免在每个控制器方法中重复写 if(can())

Laravel 示例:

// 1. 创建中间件 CheckPermission
public function handle($request, Closure $next, $permission)
{
    if (!$request->user()->can($permission)) {
        abort(403);
    }
    return $next($request);
}
// 2. 路由中使用
Route::post('/articles', [ArticleController::class, 'store'])
    ->middleware('permission:article.create');

细粒度数据权限

RBAC 只能控制操作权限,如果你需要控制数据行级别(销售只能看自己的客户),需要引入数据权限规则

  • 方案一:在 SQL 查询中加入用户条件 WHERE user_id = ?
  • 方案二:使用 ABAC策略模式 (Policy)

Laravel Policy 示例:

// app/Policies/PostPolicy.php
public function update(User $user, Post $post)
{
    return $user->id === $post->user_id || $user->isAdmin();
}

使用现成的权限包 (强烈推荐)

除非你是在作业或极简单的项目中,否则不建议自己手写权限系统,成熟的包已经处理好了缓存、性能优化、多 guard、丰富 API 等大量细节。

包名 (Composer) 适合框架 特点
spatie/laravel-permission Laravel 最流行,支持角色、权限、多 guard、缓存、Blade 指令、API 资源
cartalyst/sentinel 通用/自建框架 轻量、功能完整、不绑定特定框架
yii2-rbac Yii2 Yii2 官方自带

学会使用一个成熟的包是更有迁移价值和性价比的做法。


常见坑与建议

  1. 权限尽量不要用 int 位运算
    • 早期常用 1,2,4,8 二进制位表示权限,$permission = 7 表示有增删改,这在小项目可以,但在大项目中维护性差、扩展性极差(32 位/64 位溢出),现在基本不推荐。
  2. 缓存权限数据
    • 每次请求都查数据库读角色/权限,当用户数大时会变慢,建议登录后缓存到 SessionRedis文件缓存
  3. 前后端分离 (API) 的场景

    将权限写入 Token (JWT payload) 或 通过 Redis 校验,使用中间件拦截所有 API 请求。

  4. 超管设计
    • 给一个特殊角色(如 super_admin),在权限检查时直接返回 true,不要把所有权限都挂到这个角色上。
  5. 权限名称命名规范
    • 动词 + 资源article.createarticle.edituser.delete
    • 不要用中文,容易导致编码问题。
你的场景 推荐做法
简单 demo 用 Session + 数组存权限名,in_array() 检查
中小型商业项目 使用 RBAC + 数据库,用现成包(推荐 spatie/laravel-permission
大型复杂项目 RBAC + ABAC 混合,配合策略定义数据级别权限

建议先手动模拟一次基本的 RBAC 流程(用户 -> 角色 -> 权限),再使用成熟的包在生产环境。

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