返回

用户角色权限系统设计:RBAC最佳实践

mysql

用户角色与权限系统设计最佳实践

角色与权限管理是应用程序开发中的常见需求。它关乎系统的安全性与灵活性,糟糕的设计会为维护与扩展带来巨大挑战。

权限控制模型选择

权限控制模型并非只有一种。 常用的包括:基于角色的访问控制(RBAC), 基于属性的访问控制(ABAC), 以及访问控制列表(ACL) 。 这里我们将关注RBAC, 因为它在大多数web应用中具有良好表现,也相对简单易懂。RBAC将用户与角色关联,角色绑定权限。

原因分析 : 直接基于用户绑定权限不利于维护;如果一个用户的权限发生更改,需要更改多个地方。使用角色管理可以将用户的权限变化归于角色变化,将权限修改的影响最小化,同时方便权限集中管理。

方案 : 采用RBAC模型,为用户定义不同的角色。例如,管理员编辑访客 等,不同角色拥有不同操作权限。

数据模型设计

明确模型是成功的第一步。在关系型数据库(如MySQL)中,常见做法是建立如下几张表:

  1. users 表: 存储用户信息

    CREATE TABLE users (
        id INT AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(255) NOT NULL UNIQUE,
        password VARCHAR(255) NOT NULL,
        email VARCHAR(255) UNIQUE,
        group_id INT,  
        -- 其他用户字段
    
    
        FOREIGN KEY (group_id) REFERENCES groups(id)  -- 假设存在 groups 表
    );
    
  2. roles 表:存储角色信息

    CREATE TABLE roles (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL UNIQUE
    );
    
  3. groups表: 存储用户组信息

    CREATE TABLE groups (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL UNIQUE,
        parent_id INT NULL,  
        FOREIGN KEY (parent_id) REFERENCES groups(id) -- 自引用表示子组, 可以为空。
        );
    
  4. permissions 表: 存储权限信息

    CREATE TABLE permissions (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL UNIQUE
    );
    
  5. role_permissions 表: 存储角色与权限的对应关系

    CREATE TABLE role_permissions (
      role_id INT,
        permission_id INT,
        PRIMARY KEY (role_id, permission_id),
      FOREIGN KEY (role_id) REFERENCES roles(id),
        FOREIGN KEY (permission_id) REFERENCES permissions(id)
    );
    
  6. user_roles 表: 存储用户与角色的对应关系

    CREATE TABLE user_roles (
       user_id INT,
       role_id INT,
       PRIMARY KEY (user_id, role_id),
       FOREIGN KEY (user_id) REFERENCES users(id),
        FOREIGN KEY (role_id) REFERENCES roles(id)
    
    
    );
    
  7. group_permissions 表: 存储组与权限的对应关系

      CREATE TABLE group_permissions (
      group_id INT,
      permission_id INT,
      PRIMARY KEY (group_id, permission_id),
       FOREIGN KEY (group_id) REFERENCES groups(id),
      FOREIGN KEY (permission_id) REFERENCES permissions(id)
    
    );
    
  8. content_permissions 表: 存储特定内容访问权限. 这张表允许更精细的内容级别控制。

  CREATE TABLE content_permissions (
  content_id INT,    -- 关联具体内容的id, 依据不同的内容表修改
  permission_id INT, -- 特定操作的权限,对应`permissions`表
   group_id INT NULL,  -- 允许的组
  user_id  INT NULL,   --允许的特定用户。 group_id 和 user_id至少有一个值, 或者都可以为空代表公开访问.
  PRIMARY KEY(content_id, permission_id, group_id,user_id ),
  FOREIGN KEY(permission_id) REFERENCES permissions(id),
 FOREIGN KEY(group_id) REFERENCES groups(id),
 FOREIGN KEY(user_id) REFERENCES users(id)
);

方案解读 :

  • users表: 用户组(group_id),这允许进行用户分组管理,可以简化权限分配(权限继承)。
  • roles表: 管理系统内各种角色名称,比如:管理员,编辑者,查看者等。
  • groups表: 构建组织机构树。允许嵌套组。
  • permissions : 管理具体的操作权限名称, 如:create_postedit_postdelete_post, read_user_listcreate_useredit_user 等。
  • role_permissions : 表明不同角色拥有的操作权限。方便不同角色复用权限。
  • user_roles : 将用户和角色进行绑定。 一个用户可以拥有多个角色。
  • group_permissions : 为用户组分配角色,同理方便了权限复用和管理
  • content_permissions表: 将特定的权限附加到具体内容上, 它可以针对某个用户或者某个用户组开放内容。为具体内容进行权限配置, 让控制粒度更精细。

具体场景解决方案

问题中的需求可以用上面的数据模型解决:

  1. 根用户 (root user) : 创建角色为"超级管理员(super admin)"的用户, 将所有权限赋给此角色,根用户可以创建新的子根用户,并把其他权限分配给子根用户. 子根用户可以是同组或者不同组的用户。
  2. 子根用户 : 赋予子根用户"管理用户(manage_user)" 权限, 创建用户时将新用户添加进自己的用户组。 为组设定 group_permissions ,或者设置该组角色。 这样组内用户就可以拥有该组权限。
  3. 用户权限 : 基于用户所属的group和赋予的角色权限,查询用户的权限。对于每个需要进行权限验证的操作, 可以检查该用户的user_roles 和用户所属的groupsgroup_permissions来验证用户是否具有对应的操作权限。通过结合内容表和 content_permissions表可以实现更精细化的内容权限控制, 根据内容的属性如(所有者、创建者所属的用户组),以及内容绑定的用户和组的权限规则来实现不同的权限组合。

代码示例 (PHP)

以下是权限验证示例, 需要依赖ORM(Doctrine or Eloquent). 如果未使用ORM需要自行实现SQL查询.

<?php
// 假设已获取当前用户 $user 和需要检查的权限 $permission_name

// 根据用户 id 获取用户角色
function getUserRoles(int $user_id) : array {
  // UserRoles 对应 `user_roles` table 对应的 ORM model
    $userRoles = UserRoles::where("user_id", $user_id)->with('role')->get()->toArray();
    $roles = [];
   foreach($userRoles as $ur) {
    $roles[] = $ur['role']['name'];
  }
    return $roles;
}
//根据 role_id 获得拥有的所有 permission 的 id
function getRolePermissions (array $roles) : array{
  //Role  对应 `roles` table 的 ORM model,  `permission` 是`permissions`的ORM model
  //role_permissions 对应 `role_permissions` table 的ORM model

  $allPermissions = [];
  foreach ($roles as $role){

      $r = Role::where("name",$role)->with(['permissions'])->first();
      foreach($r['permissions'] as $p){

         $allPermissions[]=  $p->name;

       }


  }

   return array_unique($allPermissions);
}
// 根据用户的 `group_id` 获取 group 的permission ids.

function getGroupPermissions(int $group_id) : array{
    $groupPermissions =  GroupPermissions::where("group_id",$group_id)->with("permission")->get()->toArray();

    $permissionNames = [];
      foreach($groupPermissions as $gp){
            $permissionNames[] = $gp['permission']['name'];
        }


    return array_unique($permissionNames);


}

function checkUserPermission(User $user, string $permissionName) : bool
{

  // 获取用户的 role name

  $roles =  getUserRoles($user->id);
  // 根据用户roles, 获取 permission list
   $userPermissions = getRolePermissions($roles);


  // 获得group permission 
   $groupPermissions = getGroupPermissions($user->group_id);

  $finalPermissions =  array_merge($userPermissions, $groupPermissions);
   
  return in_array($permissionName, $finalPermissions);

}

// 在访问资源或者执行操作时,调用检查用户权限

$canEditPost  =  checkUserPermission($user, 'edit_post');
if($canEditPost) {

  echo "可以编辑文章.";
}

额外建议:

  • 密码哈希存储:避免明文存储密码, 使用 bcrypt 或 Argon2 等强哈希算法。
  • 定期审计: 定期审查用户角色、权限分配,及时删除过期权限。
  • 权限最小化原则: 仅赋予用户所需的最低权限。
  • 防止SQL注入:确保使用参数化查询来防止SQL注入攻击。
  • 输入验证: 对用户输入进行校验。

设计良好的角色与权限系统对于应用的长期健康发展至关重要。一个清晰的结构将使维护更加简单, 也降低错误风险,最终提升用户体验。