Skip to content

权限系统设计

CondorAdmin 采用基于 RBAC(Role-Based Access Control) 的权限模型,提供灵活的用户、角色、权限三级管理,支持菜单权限、按钮权限和数据权限。

权限模型

RBAC 三级模型

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│   用户        │ N:M  │   角色       │ N:M  │   权限       │
│   (Admin)    │─────→│   (Role)    │─────→│   (Menu)    │
└─────────────┘      └─────────────┘      └─────────────┘
      │                     │                     │
      │                     │                     ├─ 菜单权限
      │                     │                     ├─ 按钮权限
      │                     │                     └─ API 路由
      │                     │
      └─────────────────────┘
           数据权限
        (dataLimit)

关系说明

  • 用户 ↔ 角色:多对多关系(一个用户可以有多个角色)
  • 角色 ↔ 权限:多对多关系(一个角色可以有多个权限)
  • 数据权限:基于角色或用户的数据过滤规则

数据库设计

核心表结构

sql
-- 用户表
system_admin
  ├── id              主键
  ├── username        用户名(唯一)
  ├── password        密码(bcrypt)
  ├── nickname        昵称
  ├── role_ids        角色ID列表(JSON: [1,2,3])
  ├── status          状态(0=禁用 1=启用)
  └── ...

-- 角色表
system_role
  ├── id              主键
  ├── name            角色名称
  ├── rules           权限ID列表(JSON: [1,2,3])
  ├── group_id        角色分组ID
  └── ...

-- 权限表(菜单表)
system_menu_rule
  ├── id              主键
  ├── pid             父级ID(树形结构)
  ├── type            类型(menu=菜单 button=按钮)
  ├── title           标题
  ├── path            路由路径
  ├── name            路由名称
  ├── component       组件路径
  ├── icon            图标
  ├── is_hide         是否隐藏
  ├── is_keepalive    是否缓存
  ├── order           排序
  └── ...

-- 角色分组表
system_role_group
  ├── id              主键
  ├── pid             父级ID
  ├── name            分组名称
  ├── rules           继承的权限ID
  └── ...

权限类型

1. 菜单权限

控制用户可以访问哪些页面(路由)。

菜单树示例

系统管理
├── 管理员管理 (/system/admin)
│   ├── 查看列表 (button: admin:index)
│   ├── 添加 (button: admin:add)
│   ├── 编辑 (button: admin:edit)
│   └── 删除 (button: admin:del)
├── 角色管理 (/system/role)
└── 菜单管理 (/system/menu)

数据库存储

json
{
  "id": 1,
  "type": "menu",
  "title": "管理员管理",
  "path": "/system/admin",
  "component": "view.system_admin",
  "icon": "mdi:account-supervisor"
}

2. 按钮权限

控制页面内的操作按钮(增删改查)。

按钮权限标识

按钮权限标识说明
添加admin:add添加管理员
编辑admin:edit编辑管理员
删除admin:del删除管理员
导出admin:export导出数据

前端权限检查

vue
<template>
  <!-- 使用 v-if 控制按钮显示 -->
  <n-button v-if="hasPermission('admin:add')" @click="handleAdd">
    添加
  </n-button>
  
  <!-- 使用 CondorAuth 组件 -->
  <CondorAuth auth="admin:add">
    <n-button @click="handleAdd">添加</n-button>
  </CondorAuth>
</template>

<script setup lang="ts">
import { useAuthStore } from '@/store';

const authStore = useAuthStore();

// 权限检查函数
const hasPermission = (auth: string) => {
  return authStore.hasPermission(auth);
};
</script>

3. API 路由权限

控制后端接口的访问权限。

路由-权限映射

/api/condoradmin/admin/index  → admin:index
/api/condoradmin/admin/add    → admin:add
/api/condoradmin/admin/edit   → admin:edit
/api/condoradmin/admin/del    → admin:del

后端权限验证

php
// AuthPermission 中间件自动验证
$auth->check($request->path(), $auth->id);

免权限控制

php
class AdminController extends Backend
{
    // 无需登录的方法
    protected $noNeedLogin = ['login', 'captcha'];
    
    // 无需鉴权的方法(但需登录)
    protected $noNeedRight = ['profile', 'updatePassword'];
}

4. 数据权限

控制用户只能看到属于自己范围的数据。

数据权限级别

级别说明应用场景
全部false不限制,查看所有数据超级管理员
部门'auth'查看本人及下级的数据部门主管
个人'personal'只查看本人的数据普通员工

后端配置

php
class OrderController extends Backend
{
    protected $model = Order::class;
    
    // 启用数据权限
    protected $dataLimit = 'auth'; // 或 'personal'
    
    // 数据权限字段(关联的用户ID字段)
    protected $dataLimitField = 'admin_id';
}

自动过滤逻辑

php
// Backend 基类的 getDataLimitAdminIds() 方法
protected function getDataLimitAdminIds()
{
    // 超级管理员不受限制
    if ($this->auth->isSuperAdmin()) {
        return [];
    }
    
    // 根据 dataLimit 类型获取可见的管理员ID
    if ($this->dataLimit === 'auth') {
        // 获取本人及所有下级的ID
        return $this->auth->getChildrenAdminIds(true);
    }
    
    if ($this->dataLimit === 'personal') {
        // 只返回本人ID
        return [$this->auth->id];
    }
    
    return [];
}

// 查询时自动应用
$query->whereIn($this->dataLimitField, $childrenAdminIds);

权限验证流程

前端权限验证

1. 用户登录成功

2. 获取用户信息(包含 roles + buttons)

3. Store 存储权限列表
   - userInfo.roles: ['admin', 'editor']
   - userInfo.buttons: ['admin:add', 'admin:edit']

4. 路由守卫检查菜单权限
   - 根据用户角色过滤路由
   - 无权限路由重定向到 403

5. 组件内检查按钮权限
   - hasPermission('admin:add')
   - 返回 true/false 控制按钮显示

路由守卫代码

typescript
// src/router/guard/route.ts
router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore();
  
  // 未登录
  if (!authStore.isLogin) {
    if (to.meta.constant) {
      next();
    } else {
      next({ name: 'login' });
    }
    return;
  }
  
  // 已登录,初始化路由
  if (!routeStore.isInitAuthRoute) {
    await routeStore.initAuthRoute();
  }
  
  // 检查路由权限
  if (to.name === 'not-found') {
    next();
    return;
  }
  
  next();
});

后端权限验证

1. 请求进入 AuthToken 中间件

2. 验证 Token 有效性(Redis)
   - 无效 → 返回 401
   - 有效 → 继续

3. 请求进入 AuthPermission 中间件

4. 检查控制器的 $noNeedLogin 和 $noNeedRight
   - 在白名单 → 跳过权限验证
   - 不在白名单 → 继续

5. 调用 Auth::check() 验证权限
   - 根据请求路径查找对应的权限ID
   - 判断用户是否拥有该权限

6. 权限验证结果
   - 有权限 → 继续执行
   - 无权限 → 返回 403

AuthPermission 中间件代码

php
public function process(Request $request, callable $handler): Response
{
    $controller = $request->controller;
    $action = $request->action;
    
    // 获取控制器鉴权配置
    $class = new \ReflectionClass($controller);
    $properties = $class->getDefaultProperties();
    
    $noNeedLogin = $properties['noNeedLogin'] ?? [];
    if (in_array($action, $noNeedLogin)) {
        return $handler($request);
    }
    
    $noNeedRight = $properties['noNeedRight'] ?? [];
    $auth = new Auth();
    $auth->initUser();
    
    if (in_array($action, $noNeedRight)) {
        Context::set('auth', $auth);
        return $handler($request);
    }
    
    // 检查权限
    if ($auth->check($request->path(), $auth->id) !== true) {
        return json(['code' => 403, 'msg' => trans('common.access.denied')]);
    }
    
    Context::set('auth', $auth);
    return $handler($request);
}

超级管理员

特殊标识

超级管理员拥有所有权限,无需配置。

判断逻辑

php
// Auth 类
public function isSuperAdmin(): bool
{
    return in_array('*', $this->roles);
}

特权

  • 绕过所有权限检查
  • 绕过数据权限限制
  • 可以修改所有字段(批量更新时)
php
// Backend::multi() 方法
$values = $this->auth->isSuperAdmin() 
    ? $values 
    : array_intersect_key($values, array_flip($this->multiFields));

超级管理员配置

数据库中设置:

sql
UPDATE system_admin SET role_ids = '["*"]' WHERE id = 1;

或在角色表中设置:

sql
UPDATE system_role SET rules = '["*"]' WHERE id = 1;

权限缓存

Redis 缓存结构

# Token 缓存(过期自动删除)
admin:token:{token} = {admin_id}
TTL: 7200 秒(2小时)

# 用户权限缓存
admin:rules:{admin_id} = ["admin:add", "admin:edit", ...]
TTL: 3600 秒(1小时)

缓存更新时机

  • 用户登录:写入 Token
  • 角色/权限变更:清除相关用户的权限缓存
  • 用户退出:删除 Token
php
// 清除权限缓存
Redis::del("admin:rules:{$adminId}");

使用示例

示例1:配置角色权限

php
// 创建角色
$role = new SystemRole();
$role->name = '编辑';
$role->rules = json_encode([10, 11, 12, 13]); // 权限ID
$role->save();

// 分配角色给用户
$admin = SystemAdmin::find(5);
$admin->role_ids = json_encode([2]); // 角色ID
$admin->save();

示例2:前端权限控制

vue
<template>
  <div>
    <!-- 菜单权限:路由守卫自动处理 -->
    
    <!-- 按钮权限:使用 v-if -->
    <n-button v-if="hasPermission('admin:add')" @click="handleAdd">
      添加
    </n-button>
    
    <!-- 按钮权限:使用 CondorAuth 组件 -->
    <CondorAuth auth="admin:edit">
      <n-button @click="handleEdit">编辑</n-button>
    </CondorAuth>
    
    <!-- 多权限或判断(任一满足即可) -->
    <CondorAuth :auth="['admin:del', 'admin:destroy']">
      <n-button @click="handleDelete">删除</n-button>
    </CondorAuth>
    
    <!-- 多权限且判断(全部满足才显示) -->
    <n-button v-if="hasAllPermissions(['admin:del', 'superuser'])" @click="handleForceDelete">
      强制删除
    </n-button>
  </div>
</template>

<script setup lang="ts">
import { useAuthStore } from '@/store';

const authStore = useAuthStore();

const hasPermission = (auth: string) => {
  return authStore.hasPermission(auth);
};

const hasAllPermissions = (auths: string[]) => {
  return auths.every(auth => authStore.hasPermission(auth));
};
</script>

示例3:后端数据权限

php
class OrderController extends Backend
{
    protected $model = Order::class;
    
    // 部门数据权限
    protected $dataLimit = 'auth';
    protected $dataLimitField = 'admin_id';
    
    // 查询时自动过滤
    // 普通管理员只能看到自己和下级的订单
    // 超级管理员可以看到所有订单
}

示例4:自定义权限检查

php
// Controller 中
public function customAction(Request $request)
{
    // 检查特定权限
    if (!$this->auth->check('custom:permission', $this->auth->id)) {
        return $this->fail('权限不足');
    }
    
    // 检查是否为超级管理员
    if (!$this->auth->isSuperAdmin()) {
        return $this->fail('仅超级管理员可操作');
    }
    
    // 业务逻辑
}

最佳实践

1. 权限命名规范

模块:操作

示例:
admin:index   # 管理员-列表
admin:add     # 管理员-添加
admin:edit    # 管理员-编辑
admin:del     # 管理员-删除
order:export  # 订单-导出

2. 角色设计原则

  • 按职能划分:管理员、编辑、运营、客服
  • 最小权限原则:只分配必需的权限
  • 继承机制:通过角色分组实现权限继承

3. 数据权限应用场景

  • 订单管理:销售只能看自己的订单
  • 客户管理:客服只能看自己负责的客户
  • 报表统计:按数据权限统计个人/部门业绩

4. 安全建议

  • 定期审计:定期检查用户权限分配
  • 权限回收:员工离职及时回收权限
  • 日志记录:记录敏感操作日志
  • 双因素认证:高权限账号启用 2FA

常见问题

Q1: 如何添加新权限?

后端

  1. system_menu_rule 表添加权限记录
  2. 清除权限缓存

前端: 无需修改代码,权限从后端动态获取

Q2: 如何调试权限问题?

php
// 打印用户权限列表
Log::info('User Permissions', $this->auth->getRuleList());

// 检查特定权限
if (!$this->auth->check('admin:add', $this->auth->id)) {
    Log::warning('Permission Denied', [
        'user_id' => $this->auth->id,
        'permission' => 'admin:add'
    ]);
}

Q3: 数据权限如何配置?

在控制器中设置 $dataLimit$dataLimitField

php
protected $dataLimit = 'auth'; // 或 'personal'
protected $dataLimitField = 'admin_id'; // 数据所属用户字段

确保数据表中有对应字段,并在添加数据时自动填充:

php
protected $createdByField = 'admin_id';

相关文档