PHP实现基于角色的访问控制(RBAC):权限设计与数据库模型规范

PHP实现基于角色的访问控制(RBAC):权限设计与数据库模型规范

大家好,今天我们来深入探讨PHP中如何实现基于角色的访问控制(RBAC),包括权限的设计思路和数据库模型的规范。RBAC是一种广泛使用的权限管理机制,它通过角色作为权限的中介,简化了用户权限的管理,提高了系统的灵活性和安全性。

一、RBAC模型的核心概念

RBAC模型的核心概念主要包括以下几个方面:

  • 用户(User): 系统的使用者,拥有唯一的身份标识。
  • 角色(Role): 权限的集合,代表一种职责或职位。一个用户可以拥有多个角色。
  • 权限(Permission): 对系统资源的操作许可,例如读取、写入、删除等。一个角色可以拥有多个权限。
  • 会话(Session): 用户登录后产生的会话,用于跟踪用户的活动和权限。

它们之间的关系可以用下图表示:

User  ----  Role  ----  Permission
  |        /             /
  |       /             /
  |------/------------/

二、RBAC模型的分类

RBAC模型通常分为几个等级,分别是:

  • RBAC0(核心RBAC): 仅包含用户、角色和权限,以及它们之间的关联关系。这是最基础的RBAC模型。
  • RBAC1(角色层级RBAC): 在RBAC0的基础上增加了角色之间的继承关系,允许角色拥有父角色的所有权限。
  • RBAC2(角色约束RBAC): 在RBAC0的基础上增加了角色之间的约束关系,例如互斥角色、静态职责分离等。
  • RBAC3(综合RBAC): 同时包含角色层级和角色约束。

在实际应用中,可以根据系统的需求选择合适的RBAC模型。 我们本次主要讲解RBAC0,并稍微涉及RBAC1。

三、权限设计思路

权限设计是RBAC实现的关键环节。合理的权限设计可以简化权限管理,提高系统的安全性。以下是一些常见的权限设计思路:

  1. 资源导向: 以系统资源为中心,定义对资源的各种操作权限。例如,对于文章资源,可以定义“查看文章”、“创建文章”、“编辑文章”、“删除文章”等权限。

  2. 操作导向: 以用户操作为中心,定义用户可以执行的各种操作权限。例如,“登录系统”、“修改个人信息”、“管理用户”等权限。

  3. 模块导向: 以系统模块为中心,定义对模块的访问权限。例如,“访问用户管理模块”、“访问订单管理模块”等权限。

选择哪种权限设计思路取决于具体的业务需求。 通常,资源导向和操作导向结合使用,可以实现更细粒度的权限控制。

四、数据库模型规范

为了实现RBAC,我们需要设计一套数据库模型来存储用户、角色、权限以及它们之间的关系。以下是一个简单的数据库模型示例:

-- 用户表
CREATE TABLE `users` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(255) NOT NULL UNIQUE,
  `password` VARCHAR(255) NOT NULL,
  `email` VARCHAR(255) NOT NULL UNIQUE,
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 角色表
CREATE TABLE `roles` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL UNIQUE,
  `description` VARCHAR(255) DEFAULT NULL,
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 权限表
CREATE TABLE `permissions` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL UNIQUE,
  `description` VARCHAR(255) DEFAULT NULL,
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 用户-角色关联表
CREATE TABLE `user_roles` (
  `user_id` INT UNSIGNED NOT NULL,
  `role_id` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`user_id`, `role_id`),
  FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 角色-权限关联表
CREATE TABLE `role_permissions` (
  `role_id` INT UNSIGNED NOT NULL,
  `permission_id` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`role_id`, `permission_id`),
  FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
  FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 角色层级关系表 (RBAC1)
CREATE TABLE `role_hierarchy` (
  `parent_role_id` INT UNSIGNED NOT NULL,
  `child_role_id` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`parent_role_id`, `child_role_id`),
  FOREIGN KEY (`parent_role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
  FOREIGN KEY (`child_role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

表结构说明:

表名 字段名 类型 说明
users id INT UNSIGNED 用户ID,主键,自增长。
username VARCHAR(255) 用户名,唯一。
password VARCHAR(255) 密码。
email VARCHAR(255) 邮箱,唯一。
created_at TIMESTAMP 创建时间。
updated_at TIMESTAMP 更新时间。
roles id INT UNSIGNED 角色ID,主键,自增长。
name VARCHAR(255) 角色名称,唯一。
description VARCHAR(255) 角色描述。
created_at TIMESTAMP 创建时间。
updated_at TIMESTAMP 更新时间。
permissions id INT UNSIGNED 权限ID,主键,自增长。
name VARCHAR(255) 权限名称,唯一。
description VARCHAR(255) 权限描述。
created_at TIMESTAMP 创建时间。
updated_at TIMESTAMP 更新时间。
user_roles user_id INT UNSIGNED 用户ID,外键关联 users 表的 id 字段。
role_id INT UNSIGNED 角色ID,外键关联 roles 表的 id 字段。
role_permissions role_id INT UNSIGNED 角色ID,外键关联 roles 表的 id 字段。
permission_id INT UNSIGNED 权限ID,外键关联 permissions 表的 id 字段。
role_hierarchy parent_role_id INT UNSIGNED 父角色ID,外键关联 roles 表的 id 字段。
child_role_id INT UNSIGNED 子角色ID,外键关联 roles 表的 id 字段。

五、PHP代码实现

接下来,我们用PHP代码来实现RBAC的核心功能。 我们将创建一个简单的RBAC类,包含用户认证、角色分配、权限验证等功能。

<?php

class RBAC
{
    private $db; // 数据库连接对象

    public function __construct($db)
    {
        $this->db = $db;
    }

    /**
     * 用户认证
     *
     * @param string $username
     * @param string $password
     * @return array|false 用户信息,认证失败返回 false
     */
    public function authenticate(string $username, string $password): array|false
    {
        $stmt = $this->db->prepare("SELECT id, username, password FROM users WHERE username = ?");
        $stmt->execute([$username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && password_verify($password, $user['password'])) {
            return $user;
        }

        return false;
    }

    /**
     * 获取用户角色
     *
     * @param int $userId
     * @return array 角色ID数组
     */
    public function getUserRoles(int $userId): array
    {
        $stmt = $this->db->prepare("SELECT role_id FROM user_roles WHERE user_id = ?");
        $stmt->execute([$userId]);
        $roles = $stmt->fetchAll(PDO::FETCH_COLUMN);

        return $roles;
    }

    /**
     * 获取角色权限
     *
     * @param int $roleId
     * @return array 权限ID数组
     */
    public function getRolePermissions(int $roleId): array
    {
        $stmt = $this->db->prepare("SELECT permission_id FROM role_permissions WHERE role_id = ?");
        $stmt->execute([$roleId]);
        $permissions = $stmt->fetchAll(PDO::FETCH_COLUMN);

        return $permissions;
    }

    /**
     * 检查用户是否拥有指定权限
     *
     * @param int $userId
     * @param string $permissionName
     * @return bool
     */
    public function checkPermission(int $userId, string $permissionName): bool
    {
        // 1. 获取用户角色
        $roles = $this->getUserRoles($userId);

        if (empty($roles)) {
            return false; // 用户没有角色,则没有权限
        }

        // 2. 获取所有角色的权限
        $permissions = [];
        foreach ($roles as $roleId) {
            $permissions = array_merge($permissions, $this->getRolePermissions($roleId));
        }

        // 3. 去重权限ID
        $permissions = array_unique($permissions);

        // 4. 根据权限名称查找权限ID
        $stmt = $this->db->prepare("SELECT id FROM permissions WHERE name = ?");
        $stmt->execute([$permissionName]);
        $permission = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$permission) {
            return false; // 权限不存在
        }

        // 5. 检查用户是否拥有该权限
        return in_array($permission['id'], $permissions);
    }

    /**
     * (RBAC1)获取角色继承的所有权限,包括父角色的权限
     *
     * @param int $roleId
     * @return array 权限ID数组
     */
    public function getInheritedRolePermissions(int $roleId): array
    {
        $permissions = $this->getRolePermissions($roleId); // 获取当前角色权限
        $parentRoles = $this->getParentRoles($roleId); // 获取父角色

        foreach ($parentRoles as $parentRoleId) {
            $permissions = array_merge($permissions, $this->getInheritedRolePermissions($parentRoleId)); //递归获取父角色权限
        }

        return array_unique($permissions);
    }

     /**
     * (RBAC1) 获取角色的父角色
     * @param int $roleId
     * @return array
     */
    public function getParentRoles(int $roleId): array
    {
        $stmt = $this->db->prepare("SELECT parent_role_id FROM role_hierarchy WHERE child_role_id = ?");
        $stmt->execute([$roleId]);
        $roles = $stmt->fetchAll(PDO::FETCH_COLUMN);

        return $roles;
    }

     /**
     * (RBAC1) 检查用户是否拥有指定权限,包含继承的权限
     *
     * @param int $userId
     * @param string $permissionName
     * @return bool
     */
    public function checkPermissionWithInheritance(int $userId, string $permissionName): bool
    {
         // 1. 获取用户角色
        $roles = $this->getUserRoles($userId);

        if (empty($roles)) {
            return false; // 用户没有角色,则没有权限
        }

        // 2. 获取所有角色的权限 (包括继承的权限)
        $permissions = [];
        foreach ($roles as $roleId) {
            $permissions = array_merge($permissions, $this->getInheritedRolePermissions($roleId));
        }

        // 3. 去重权限ID
        $permissions = array_unique($permissions);

        // 4. 根据权限名称查找权限ID
        $stmt = $this->db->prepare("SELECT id FROM permissions WHERE name = ?");
        $stmt->execute([$permissionName]);
        $permission = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$permission) {
            return false; // 权限不存在
        }

        // 5. 检查用户是否拥有该权限
        return in_array($permission['id'], $permissions);
    }
}

// 示例用法
// 假设已经创建了数据库连接 $db

// 初始化RBAC类
// $rbac = new RBAC($db);

// 用户认证
// $user = $rbac->authenticate('testuser', 'password');

// if ($user) {
//     echo "用户认证成功!<br>";

//     // 检查用户是否拥有 'edit_article' 权限
//     if ($rbac->checkPermission($user['id'], 'edit_article')) {
//         echo "用户拥有编辑文章的权限!<br>";
//     } else {
//         echo "用户没有编辑文章的权限!<br>";
//     }
// } else {
//     echo "用户认证失败!<br>";
// }

代码说明:

  • authenticate() 方法用于用户认证,验证用户名和密码是否正确。
  • getUserRoles() 方法用于获取用户拥有的角色ID。
  • getRolePermissions() 方法用于获取角色拥有的权限ID。
  • checkPermission() 方法用于检查用户是否拥有指定的权限。它首先获取用户的所有角色,然后获取这些角色的所有权限,最后检查权限列表中是否包含指定的权限。
  • getInheritedRolePermissions() 方法用于获取角色继承的所有权限,包括父角色的权限。
  • getParentRoles() 方法用于获取角色的父角色。
  • checkPermissionWithInheritance() 方法用于检查用户是否拥有指定的权限,包含继承的权限。

六、安全注意事项

在实现RBAC时,需要注意以下安全事项:

  • 密码安全: 使用安全的密码存储方式,例如使用 password_hash() 函数进行哈希加密。
  • 防止SQL注入: 使用参数化查询或预处理语句,防止SQL注入攻击。
  • 会话管理: 使用安全的会话管理机制,例如使用 session_start() 函数开启会话,并设置合理的会话过期时间。
  • 权限控制: 严格控制用户的权限,避免权限过度分配。
  • 日志记录: 记录用户的操作日志,方便审计和追踪问题。

七、实际应用中的一些考量

  • 动态权限: 某些情况下,权限可能需要根据业务规则动态变化。例如,用户只能编辑自己创建的文章。 这可以通过在 checkPermission() 方法中加入额外的业务逻辑来实现。

  • 缓存: 为了提高性能,可以将用户的角色和权限信息缓存起来,避免频繁查询数据库。 可以使用Memcached、Redis等缓存系统。

  • 权限管理界面: 为了方便管理权限,可以开发一个权限管理界面,用于创建、编辑和删除用户、角色和权限。

  • 集成框架: 许多PHP框架都提供了RBAC组件或扩展,可以直接使用,例如Laravel的spatie/laravel-permission包。

八、快速构建RBAC的PHP代码案例(基于Laravel框架的spatie/laravel-permission包)

这里提供一个基于Laravel框架和spatie/laravel-permission包的快速实现RBAC的代码案例,演示如何使用该包简化RBAC的实现。

  1. 安装依赖:

    composer require spatie/laravel-permission
  2. 配置:

    • 运行迁移:

      php artisan migrate
    • User 模型中使用 HasRoles trait:

      namespace AppModels;
      
      use IlluminateFoundationAuthUser as Authenticatable;
      use SpatiePermissionTraitsHasRoles;
      
      class User extends Authenticatable
      {
          use HasRoles;
      
          // ...
      }
  3. 使用:

    use AppModelsUser;
    use SpatiePermissionModelsRole;
    use SpatiePermissionModelsPermission;
    
    // 创建角色和权限
    $role = Role::create(['name' => 'editor']);
    $permission = Permission::create(['name' => 'edit articles']);
    
    // 将权限分配给角色
    $role->givePermissionTo($permission);
    
    // 将角色分配给用户
    $user = User::find(1);
    $user->assignRole('editor'); // 或者 $user->assignRole($role);
    
    // 检查用户是否拥有权限
    $user->hasPermissionTo('edit articles'); // true
    
    // 检查用户是否拥有角色
    $user->hasRole('editor'); // true
    
    // 使用中间件保护路由
    Route::get('/articles/edit', function () {
        // 只有拥有 'edit articles' 权限的用户才能访问
    })->middleware('permission:edit articles');
    

这个案例展示了如何使用spatie/laravel-permission包快速实现RBAC,极大地简化了代码量,并且提供了丰富的功能,例如角色层级、权限缓存等。

权限设计与数据库模型是基础

今天我们学习了PHP中实现RBAC的方法,包括权限设计思路、数据库模型规范和代码实现。 核心在于理解RBAC模型的核心概念,以及如何将其转化为可操作的数据库结构和代码逻辑。 同时,安全问题和实际应用中的一些考量也需要重视。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注