RBAC-基于角色的访问控制模型

RBAC-基于角色的访问控制(Role-Based Access Control)是一种广泛使用的权限管理模型,它通过角色分配权限,用户通过角色获得权限。RBAC 模型的核心概念包括 用户(User)角色(Role)权限(Permission)。这种模型简化了权限管理,使得系统管理员能够更高效地管理用户权限。

以下是对 RBAC 模型的详细解释,以及如何在实际系统中实现 RBAC 用户权限控制。

RBAC 的核心概念

1.1 用户(User)

用户是系统中的一个实体,可以是一个人或其他需要访问系统资源的主体。用户通过登录系统获得身份验证,并根据其角色获得相应的权限。

1.2 角色(Role)

角色是一组权限的集合,代表了用户在系统中的职责或身份。例如:

  • 管理员(Admin):拥有系统管理权限。
  • 普通用户(User):拥有基本的读取权限。
  • 编辑(Editor):拥有内容编辑权限。
1.3 权限(Permission)

权限是用户可以执行的操作或访问的资源。例如:

  • 读取权限(Read):允许用户查看数据。
  • 写入权限(Write):允许用户修改数据。
  • 删除权限(Delete):允许用户删除数据。
1.4 用户 角色 权限
  • 用户与角色的关系:一个用户可以拥有多个角色,一个角色也可以被多个用户拥有。
  • 角色与权限的关系:一个角色可以包含多个权限,一个权限也可以被多个角色拥有。

RBAC 的实现方式

在实际系统中,RBAC 的实现通常包括以下几个步骤:

2.1 数据模型设计

设计用户、角色和权限的数据模型,并建立它们之间的关系。

表:Users
- 用户ID
- 用户名
- 密码
- 等等...

表:Roles
- 角色ID
- 角色名称
- 等等...

表:Permissions
- 权限ID
- 权限名称
- 等等...

表:UserRoles
- 用户ID
- 角色ID

表:RolePermissions
- 角色ID
- 权限ID
2.2 角色分配

为用户分配角色。这通常通过用户管理界面完成,管理员可以将角色分配给用户。

2.3 权限分配

为角色分配权限。这通常通过角色管理界面完成,管理员可以将权限分配给角色。

2.4 权限检查

在用户执行操作时,系统需要检查用户是否拥有相应的权限。这通常通过以下步骤完成:

  1. 获取用户的角色。
  2. 获取角色的权限。
  3. 检查用户是否拥有执行操作所需的权限。

数据库设计

一、整体架构

该模型通过角色(Role)作为中介,将用户(User)权限(Permission)解耦,实现细粒度的权限管理。

表介绍

表名

核心作用

user(用户表)

存储用户基本信息

auth_role(角色表)

定义系统角色

user_role(用户-角色关联表)

用户与角色的多对多映射

auth_elemenoperation(元素操作权限表)

定义最小操作单元的权限(如:按钮)

auth_role_elemenoperation(角色-元素操作关联表)

角色与元素操作的授权关系

auth_page_menu(页面菜单权限表)

定义系统导航菜单的访问权限

auth_role_page_menu(角色-页面菜单关联表)

角色与页面菜单的授权关系

表关系模型图

表关系文字说明
  • 用户表(USER):存储用户的基本信息。
  • 角色表(ROLE):定义系统中的角色。
  • 用户-角色关联表(USER_ROLE):表示用户与角色的多对多关系,一个用户可以拥有多个角色,一个角色可以被多个用户拥有。
  • 元素操作权限表(ELEMENT_OPERATION):定义最小操作单元的权限,如:按钮点击等。

  • 角色-元素操作关联表(ROLE_ELEMENT_OPERATION):定义角色与元素操作的授权关系。
  • 页面菜单权限表(PAGE_MENU):定义系统导航菜单的访问权限,如:某个新页面。
    • up 主视角

    • 粉丝视角

    • 访客视角

注释:UP 主、粉丝、访客看到的菜单选项(收藏、追番追剧、设置等)、页面按钮(已关注、关注、发消息等)是不同的。

  • 角色-页面菜单关联表(ROLE_PAGE_MENU):定义角色与页面菜单的授权关系。
建表语句
  • 角色表(ROLE):定义系统中的角色。
DROP TABLE IF EXISTS `auth_role`;
CREATE TABLE `auth_role` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` VARCHAR(255) DEFAULT NULL COMMENT '角色名称',
  `code` VARCHAR(50) NOT NULL COMMENT '角色唯一编码',
  `createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_code` (`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='角色表';
  • 用户-角色关联表(USER_ROLE):表示用户与角色的多对多关系,一个用户可以拥有多个角色,一个角色可以被多个用户拥有。
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `userId` BIGINT NOT NULL COMMENT '用户ID',
  `roleId` BIGINT NOT NULL COMMENT '角色ID',
  `createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_userId` (`userId`),
  KEY `idx_roleId` (`roleId`)
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='用户角色关联表';
  • 元素操作权限表(ELEMENT_OPERATION):定义最小操作单元的权限,如:按钮点击等。
DROP TABLE IF EXISTS `auth_element_operation`;

CREATE TABLE `auth_element_operation` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `elementName` VARCHAR(255) DEFAULT NULL COMMENT '页面元素名称',
  `elementCode` VARCHAR(50) DEFAULT NULL COMMENT '页面元素唯一编码',
  `operationType` VARCHAR(5) DEFAULT NULL COMMENT '操作类型:0可点击  1可见',
  `createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='权限控制--页面元素操作表';
  • 角色-元素操作关联表(ROLE_ELEMENT_OPERATION):定义角色与元素操作的授权关系。
DROP TABLE IF EXISTS `auth_role_element_operation`;

CREATE TABLE `auth_role_element_operation` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `roleId` BIGINT NOT NULL COMMENT '角色ID',
  `elementOperationId` BIGINT NOT NULL COMMENT '元素操作ID',
  `createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_roleId` (`roleId`),
  KEY `idx_elementOperationId` (`elementOperationId`)
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='权限控制--角色与元素操作关联表';
  • 页面菜单权限表(PAGE_MENU):定义系统导航菜单的访问权限,如:某个新页面。
DROP TABLE IF EXISTS `auth_menu`;

CREATE TABLE `auth_menu` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` VARCHAR(255) DEFAULT NULL COMMENT '菜单项目名称',
  `code` VARCHAR(50) DEFAULT NULL COMMENT '唯一编码',
  `createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='权限控制-页面访问表';
  • 角色-页面菜单关联表(ROLE_PAGE_MENU):定义角色与页面菜单的授权关系。
DROP TABLE IF EXISTS `auth_role_menu`;

CREATE TABLE `auth_role_menu` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `roleId` BIGINT NOT NULL COMMENT '角色ID',
  `menuId` BIGINT NOT NULL COMMENT '页面菜单ID',
  `createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_roleId` (`roleId`),
  KEY `idx_menuId` (`menuId`)
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='权限控制--角色页面菜单关联表';

二、权限控制流程

  1. 用户登录:验证账号密码(结合盐值加密)。
  2. 角色加载:根据用户关联的角色列表,加载所有角色。
  3. 权限校验
    • 页面级:检查角色是否有权访问当前页面(通过 auth_role_page_menu)。
    • 元素级:检查角色是否有权执行特定操作(通过 auth_role_elemenoperation)。
  1. 动态拦截:前端/后端根据权限标记隐藏或禁用操作按钮。

三、设计亮点

  1. 细粒度控制
    • 既能控制页面访问(如 "商品页"),也能控制页面内的具体操作(如 "删除按钮")。
  1. 灵活扩展
    • 新增角色或权限时,仅需修改关联表,无需改动核心表结构。
  1. 安全性
    • 密码存储使用盐值,避免明文泄露风险。
  1. 易维护性
    • 通过角色实现权限批量分配,降低管理成本。

四、潜在优化点

  1. 权限缓存:增加缓存层(如Redis),减少实时查询压力。
  2. 预加载策略:用户首次登录时预加载全部权限,提升后续操作响应速度。
  3. 审计日志:记录权限变更历史(如谁在何时给角色添加了哪些权限)。

前端展示权限控制:元素操作(如:视频投稿)、页面菜单(如:购买邀请码)

后端将用户拥有的权限下发给前端,前端基于权限码,控制前端页面的展示。如:是否展示视频投稿按钮,是否展示购买邀请码这个菜单选项。

初始化一些数据

  • 为用户 1 赋予 p5 的角色,p5 的角色可以点击发布视频。
  • 为用户 2 赋予 p9 的角色,p9 的角色可以看见购买邀请码。
1.1. 初始化角色表

1.2. 初始化页面元素操作表

1.3. 为角色赋予权限

表明:LV1 用户拥有视频投稿这个功能,即可点击。

1.4. 为用户赋予个角色

1.5. 初始化页面菜单表:一般是跳转到一个新的页面

1.6. 为角色赋予权限

1.7. 为用户赋予角色

接口开发:查询用户权限

1.1.1. 登录获取用户 token

1.1.2. 查询用户权限

接口地址:http://localhost:8888/api/user-auth

用户 1:

{
  "code": "0",
  "data": {
    "roleElementOperationList": [
      {
        "authElementOperation": {
          "createTime": null,
          "elementCode": "VIDEO_POST_BUTTON",
          "elementName": "视频投稿按钮",
          "id": 1,
          "operationType": "0",
          "updateTime": null
        },
        "createTime": "2025-03-10 19:33:31",
        "elementOperationId": 1,
        "roleId": 1
      }
    ],
    "roleMenuList": []
  },
  "msg": "成功"
}

用户 2:

{
  "code": "0",
  "data": {
    "roleElementOperationList": [],
    "roleMenuList": [
      {
        "authMenu": {
          "code": "PurchaseInvitationCode",
          "createTime": null,
          "id": 1,
          "name": "购买邀请码",
          "updateTime": null
        },
        "createTime": "2025-03-10 19:38:32",
        "menuId": 1,
        "roleId": 5
      }
    ]
  },
  "msg": "成功"
}

后端需要控制的权限:接口、数据权限控制

仿制恶意用户绕过前端代码直接访问数据,前后端结合的权限控制使我们的数据更加安全。

1. Spring AOP切面编程

什么是 AOP?

AOP(Aspect-Oriented Programming)是一种编程范式,用于在不修改原有代码的情况下,为程序添加额外的功能。比如,你可以在方法执行前后添加日志记录、性能监控、事务管理等功能。通过使用Spring AOP,你可以创建更干净、模块化、易于维护的代码,同时让系统变得更加灵活和可扩展。

AOP中的几个关键概念
  • 连接点(Join Point):程序执行过程中的某个点,比如方法的调用。在Spring AOP中,连接点通常是方法的执行
  • 切点(Pointcut):定义了哪些连接点是我们感兴趣的,也就是哪些方法需要被拦截。比如,你可以定义一个切点,拦截所有以save开头的方法。切点定义了哪些方法需要被拦截。Spring AOP使用AspectJ的表达式语法来定义切点。
  • 通知(Advice):当切点被触发时,要执行的代码。比如,在方法执行前打印日志、在方法执行后记录结果等。
    • 前置通知(Before Advice):在目标方法执行之前执行。
    • 后置通知(After Returning Advice):在目标方法成功执行之后执行。
    • 异常通知(After Throwing Advice):在目标方法抛出异常时执行。
    • 最终通知(After Finally Advice):无论目标方法是否成功,都会执行。
    • 环绕通知(Around Advice):可以完全控制目标方法的执行,可以在执行前后插入代码。
  • 切面(Aspect):把切点通知组合在一起,定义了一个完整的AOP功能。比如,一个切面可以是“在所有save方法执行前打印日志”。切面类是定义AOP功能的核心。我们需要用@Aspect注解标记一个类为切面类,并用@Component@Configuration将其注册为Spring的Bean。

2. 实例:日志记录切面

这段代码的核心目标是通过**面向切面编程(AOP)**的方式,为指定的方法添加日志记录功能。具体来说,它会在方法执行前后分别记录日志,而不需要在方法内部手动编写日志代码。

1 自定义注解:LoggingMonitor
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoggingMonitor {
}
  • 作用:这是一个自定义注解,用于标记需要被监控的方法。
  • @Retention(RetentionPolicy.RUNTIME):表示这个注解在运行时仍然有效,这样切面类可以在运行时通过反射获取到它。
  • @Target(ElementType.METHOD):表示这个注解只能应用于方法上。
2 服务类:MyService
@Service
public class MyService {
    @LoggingMonitor
    public void doSomething() {
        System.out.println("Doing something in MyService");
    }
}
  • 作用:这是一个普通的业务服务类,其中有一个方法doSomething()
  • @Service:这是Spring框架中的注解,表示这是一个服务类,会被Spring容器管理。
  • @LoggingMonitor:这个注解标记了doSomething()方法,表示这个方法需要被日志切面监控。
3 切面类:LoggingAspect
@Aspect
@Component
public class LoggingAspect {
    @Pointcut("@annotation(org.example.service.annotation.LoggingMonitor)")
    public void serviceMethods() {
        // 空实现,不需要任何逻辑
    }

    @Before("serviceMethods()")
    public void logBefore() {
        System.out.println("Method is about to be executed");
    }

    @After("serviceMethods()")
    public void logAfter() {
        System.out.println("Method has been executed");
    }
}
  • 作用:切面类用于定义在方法执行前后要做的事情(比如日志记录)。
  • @Aspect:这是AOP的核心注解,表示这是一个切面类。
  • @Component:表示这个类会被Spring容器管理。
  • @Pointcut:定义了一个切入点,表示哪些方法会被切面类监控。这里的切入点是所有被@LoggingMonitor注解标记的方法。
  • @Before:定义了一个前置通知,表示在方法执行之前执行的逻辑。
  • @After:定义了一个后置通知,表示在方法执行之后执行的逻辑。
4. 运行流程梳理
4.1 方法调用触发

MyService.doSomething()方法被调用时,Spring AOP框架会检测到这个方法上有@LoggingMonitor注解。

4.2 前置通知(logBefore)执行
  • 切面类LoggingAspect中的@Before注解标记的logBefore()方法会被触发。
  • 执行logBefore()方法中的逻辑,打印日志:
Method is about to be executed
4.3 原始方法执行
  • 接下来,MyService.doSomething()方法本身会被执行。
  • 打印:
Doing something in MyService
4.4 后置通知(logAfter)执行
  • 切面类LoggingAspect中的@After注解标记的logAfter()方法会被触发。
  • 执行logAfter()方法中的逻辑,打印日志:
Method has been executed

通过AOP技术,我们可以在不修改原始业务逻辑代码的情况下,为方法添加额外的功能(如日志记录)。具体来说:

  1. 自定义注解@LoggingMonitor标记需要监控的方法。
  2. 切面类LoggingAspect通过@Pointcut定义监控的切入点。
  3. 使用@Before@After注解分别定义方法执行前后的行为。
  4. 当被标记的方法被调用时,AOP框架会自动触发切面类中的逻辑。
5. 接口 测试

输出结果:

3. 接口权限控制

ApiLimitedAspect

功能:等级为 P5 的用户不能发布动态消息,即不能调用发布动态那个接口

设计思路

这段代码的设计思路是“黑名单”模式,即:

  • 使用@ApiLimited注解标记接口方法,并指定一组受限角色。
  • 如果用户拥有这些受限角色之一,则禁止访问该接口。

这种设计思路适用于一些特殊场景,例如:

  • 某些接口只允许非管理员访问。
  • 某些接口只允许非黑名单用户访问。
扩展:如果限制角色(黑名单数量)越来越多怎么办呢?会造成我们的注解十分庞大
  • 可以引入”角色组
  • 动态加载受限角色

如果受限角色列表可能动态变化,可以将受限角色存储在数据库或配置中心中,而不是硬编码在注解里。例如:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiLimitedRole {
    String roleKey() default ""; // 动态角色键
}

然后在切面中动态加载受限角色:

@Before("check() && @annotation(apiLimitedRole)")
public void doBefore(JoinPoint joinPoint, ApiLimitedRole apiLimitedRole) {
    Long userId = userSupport.getCurrentUserId();
    List<UserRole> userRoleList = userRoleService.getUserRoleByUserId(userId);
    String roleKey = apiLimitedRole.roleKey();
    String[] limitedRoleCodeList = roleConfigService.getLimitedRoles(roleKey); // 动态加载受限角色
    ...
}
接口测试

接口地址:http://localhost:8888/api/userMoment

输出结果:

4. 数据权限控制

DataLimitedAspect

字段取值的限制,可能不同的角色要求的字段值不同。如:p9 用户只能发布视频类型得内容。

优化建议
动态配置限制规则

目前的限制规则是硬编码的(ROLE_P9type == "0")。如果未来需要支持更复杂的规则,可以将规则存储在配置文件或数据库中,动态加载。例如:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DataLimited {
    String roleKey() default ""; // 动态角色键
    String typeValue() default ""; // 限制的类型值
}

然后在切面中动态加载规则:

@Before("check() && @annotation(dataLimited)")
public void doBefore(JoinPoint joinPoint, DataLimited dataLimited) {
    Long userId = userSupport.getCurrentUserId();
    List<UserRole> userRoleList = userRoleService.getUserRoleByUserId(userId);
    Set<String> roleCodeSet = userRoleList.stream().map(UserRole::getRoleCode).collect(Collectors.toSet());
    String requiredRole = dataLimited.roleKey();
    String requiredType = dataLimited.typeValue();

    Object[] args = joinPoint.getArgs();
    for (Object arg : args) {
        if (arg instanceof UserMoment) {
            UserMoment userMoment = (UserMoment) arg;
            if (roleCodeSet.contains(requiredRole) && !requiredType.equals(userMoment.getType())) {
                log.warn("User [{}] is denied access to method [{}] due to data limitation: Role [{}] requires type [{}]", 
                         userId, joinPoint.getSignature().getName(), requiredRole, requiredType);
                throw new ConditionException("参数异常");
            }
        }
    }
}
接口测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小编码上说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值