Skip to content
第 56 章 后端 ⏱ 10 分钟阅读

第 56 章:RBAC 权限模型 ​

学习目标 ​

  • 学会 RBAC 数据模型
  • 掌握权限注解
  • 实现按钮级 / 接口级 / 数据级权限

一、RBAC 是啥 ​

RBAC(Role-Based Access Control):基于角色的访问控制。

用户 → 角色 → 权限
                  ├─ 菜单权限
                  ├─ 按钮权限
                  └─ 数据权限

核心表(5 张):

sql
-- 用户
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL
);

-- 角色
CREATE TABLE role (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50) NOT NULL
);

-- 权限 / 菜单
CREATE TABLE permission (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50),
    type TINYINT,            -- 1=菜单,2=按钮,3=接口
    code VARCHAR(100)        -- 权限标识,如 user:create
);

-- 用户-角色(多对多)
CREATE TABLE user_role (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY (user_id, role_id)
);

-- 角色-权限(多对多)
CREATE TABLE role_permission (
    role_id BIGINT,
    permission_id BIGINT,
    PRIMARY KEY (role_id, permission_id)
);

示例数据(3 用户 + 3 角色 + 6 权限):

sql
-- 用户表 user
1 | admin
2 | 张三
3 | 李四

-- 角色表 role
1 | 系统管理员(ADMIN)
2 | 部门经理(MANAGER)
3 | 普通员工(EMPLOYEE)

-- 权限表 permission
1 | 创建用户       | 2(按钮) | user:create
2 | 删除用户       | 2(按钮) | user:delete
3 | 查看所有订单   | 3(接口) | order:view_all
4 | 查看本部门订单 | 3(接口) | order:view_dept
5 | 用户管理菜单   | 1(菜单) | menu:user
6 | 订单管理菜单   | 1(菜单) | menu:order

-- 用户-角色
user_id | role_id
   1    |   1        -- admin   → 管理员
   2    |   2        -- 张三    → 部门经理
   3    |   3        -- 李四    → 普通员工

-- 角色-权限
role_id | permission_id
   1    |   1, 2, 3, 5, 6    -- ADMIN → 全部权限
   2    |   4, 5, 6          -- MANAGER → 本部门订单 + 用户/订单菜单
   3    |   4, 6              -- EMPLOYEE → 本部门订单 + 订单菜单

实际能干啥:

用户角色能做
admin系统管理员所有事(创/删用户、查全部订单)
张三部门经理看本部门订单、看用户菜单(但不能创建/删除用户)
李四普通员工看本部门订单、看订单菜单(连用户菜单都没有)

便利性:新员工入职只要 INSERT user_role(user_id, role_id),自动拥有该角色所有权限;改角色权限 = 改一批人的权限。

二、权限注解 ​

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
    String value();                                 // 权限标识
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
    String[] value();                               // 角色名
}

用法:

java
@RequiresPermission("user:create")
@PostMapping("/users")
public Result<Void> create(@RequestBody UserDTO dto) { }

@RequiresRole("ADMIN")
@DeleteMapping("/users/{id}")
public Result<Void> delete(@PathVariable Long id) { }

三、权限拦截器 ​

java
@Aspect
@Component
public class PermissionAspect {

    @Around("@annotation(requiresPermission)")
    public Object check(ProceedingJoinPoint pjp, RequiresPermission requiresPermission) throws Throwable {
        String requiredCode = requiresPermission.value();
        Set<String> userPerms = SecurityUtils.getCurrentUserPermissions();
        if (!userPerms.contains(requiredCode)) {
            throw new BusinessException(ErrorCode.FORBIDDEN);
        }
        return pjp.proceed();
    }
}

四、按钮级权限(前端) ​

json
{
  "id": 1,
  "username": "admin",
  "permissions": ["user:create", "user:update", "user:delete"]
}

前端 Vue 3 用法:

vue
<template>
  <el-button v-if="hasPermission('user:create')" @click="handleCreate">新建</el-button>
</template>

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
const hasPermission = (code) => userStore.permissions.includes(code)
</script>

五、数据权限(行级) ​

场景:销售只能看自己的订单,经理能看部门的。

sql
-- 注解式
WHERE 1=1
  [AND user_id = #{currentUserId}]              -- 本人
  [AND dept_id = #{currentUserDeptId}]          -- 部门
  [AND dept_id IN #{currentUserSubDeptIds}]     -- 下属部门
  [AND 1=1]                                    -- 全部
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
    int scope();                              // 1=本人,2=部门,3=下属,4=全部
}

AOP 拦截 mapper(给所有标了 @DataScope 的方法,自动拼 WHERE):

java
@Aspect @Component
public class DataScopeAspect {

    @Before("@annotation(dataScope)")         // ① 拦截所有带 @DataScope 的方法
    public void before(DataScope dataScope) { //    dataScope = 被拦截的注解对象(小写名字)
        int scope = dataScope.scope();
        Long userId = SecurityUtils.getCurrentUserId();
        Long deptId = SecurityUtils.getCurrentDeptId();

        QueryWrapper<?> q = getCurrentQueryWrapper();  // ② 从 ThreadLocal 拿当前 SQL
        switch (scope) {
            case 1: q.eq("user_id", userId); break;                  // 本人
            case 2: q.eq("dept_id", deptId); break;                  // 本部门
            case 3: q.in("dept_id", getSubDeptIds(deptId)); break;   // 下属部门
            case 4: /* 全部:不加条件 */ break;
        }
    }
}

关键点:

  • @DataScope 是自定义注解,贴在 mapper 方法上,声明"我需要数据范围过滤"(类似 48 章的 @ValidStatus)。
  • @Before("@annotation(dataScope)") = Spring AOP 前置通知:凡是标了 @DataScope 的方法,执行前自动把注解对象传进来跑这段代码,动态改 WHERE 条件。
  • 简单场景业务层手动拼更省事:
java
// 不上 AOP,业务层直接:
List<Order> list = orderMapper.selectList(
    new LambdaQueryWrapper<Order>()
        .eq(Order::getDeptId, SecurityUtils.getCurrentDeptId())
);

@DataScope 贴在哪:贴在 mapper 接口的方法上(不是 service 上),AOP 才能拦截到:

java
public interface OrderMapper extends BaseMapper<Order> {

    @DataScope(scope = 1)              // 1 = 本人:AOP 自动拼 WHERE user_id = 当前用户ID
    List<Order> selectList(QueryWrapper q);

    @DataScope(scope = 4)              // 4 = 全部:不加条件(管理员用)
    List<Order> selectAll(QueryWrapper q);
}

六、本章小结 ​

要点关键
5 张表user / role / permission / user_role / role_permission
权限粒度菜单 / 按钮 / 接口 / 数据
注解@RequiresPermission / @RequiresRole
按钮权限前端 v-if 判断

动手练习 ​

  1. 设计 RBAC 5 张表,加外键和索引
  2. 实现 @RequiresPermission 切面
  3. 实现"销售只能看自己订单"的数据权限

下一章:第 57 章:OpenAPI 文档 →

本站基于 VitePress 构建 · 由 StackHub 团队维护