RBAC权限模型笔记

RBAC权限模型笔记

RBAC权限模型 最近在写一个项目,原本在权限控制模块使用的是层级的权限控制模型。而项目涉及到交错、交叉的权限,所以层级的权限控制就显得非常弱小。故而去学习了RBAC的权限模型,写了一篇学习笔记。 什么是RBAC权限模型? RBAC(Role-Based Acess Control)即基于角色的访问

RBAC权限模型

最近在写一个项目,原本在权限控制模块使用的是层级的权限控制模型。而项目涉及到交错、交叉的权限,所以层级的权限控制就显得非常弱小。故而去学习了RBAC的权限模型,写了一篇学习笔记。


什么是RBAC权限模型?

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

其内涵可以用简单的一句话进行概括。即—— 权限分配给角色,角色分配给用户,以此完成权限的控制。这也是RBAC权限模型中已定义的概念,角色、权限、分配、用户。

  1. 角色(Role): 是对一组权限集合的抽象。
  2. 权限(Permission): 是对系统指定资源或操作的许可或通行证。
  3. **用户(User):**系统的主要使用者,一个用户可以被分配一个或多个角色。
  4. 分配(Assignment): 将用户与角色联系,让用户拥有具体的权限。

为什么需要RBAC权限模型?

非角色的权限模型缺点

  • 在传统的权限模型中。如果简简单单,仅用一个Role字段,来控制用户对资源或者操作的访问和使用,过于粗糙,不易于控制、且极容易出错,风险过高。
  • 但若采用扁平限模型(直接分配权限模型)时,如果有新的用户,因为有大部分用户都是具有相似甚至相同的权限的,所以需要对其进行重复的、繁琐的权限分配这一操作。这个过程中不仅繁琐、浪费时间,还容易不小心导致出错。
  • 面对交叉权限需求时,直接分配权限模型极易容易导致混乱。如下面的**例子,**这种类似情况通常在后台管理系统,对管理员权限进行精细控制时尤为严重。

例子:

一个二手市场同时混合了电商元素的系统,其可能会有"要求商家用户在拥有普通用户的权限(如发布二手商品等)的同时,拥有商家特有的店铺权限,即该账户同时拥有了'商家'和'用户'两个权限'"。这种情况下,如果商家的业务发生变化,如新增了一个功能。这时就需要在大量账户中匹配到商家账户并为其添加该权限,这极大增加了维护成本。

通俗来说: 在直接分配权限模型中,对于一些高精度权限控制需求的实现,会极大增加现有权限模块的复杂程度,降低效率和性能,导致日常维护困难,且权限模块的复杂化实现也会大大增加维护成本。

RBAC权限模型优点

  • 将权限集合抽离出来,按使用者分类存放在角色这一集合体中。仅为用户赋予角色,不直接赋予权限。实现了权限与用户的解耦。
  • 当有权限变动时,只需更改角色拥有的权限。而不需要最每一个用户单独重复手动处理。效率高。
  • 当有更交错复杂的权限控制时,RBAC模型可以在角色的基础上附加其他条件,以此实现更精细的控制,更简便、效率高。

RBAC权限模型的主要分类

还有一些更有针对性的RBAC权限模型变体,他们针对特定领域有更好的适配性和可靠性。这里不再举例,仅展示了RBAC的常见以及主要模型

模型 特点 描述
RBAC0 用户->角色->权限 RBAC最基础的模型。其他RBAC模型均为以该模型为基础。
RBAC1 RBAC0 + 角色继承 在RBAC0基础上增加了角色继承。即高等级角色可以继承低等级角色都所有权限
RBAC2 RBAC0 + 约束模型 在RBAC0基础上增加了约束模型。如互斥约束:同一用户只能有一个角色;先决约束:一个用户必须拥有某一角色后才能拥有另一个角色等等约束条件。
RBAC3 RBAC1 + RBAC2 集成了角色继承和约束模型。也是完整程度最高的RBAC模型,但是实现成本很高。
混合模型 RBAC + 其他权限模型 这个模型并不算RBAC权限模型的范畴。其是将RBAC的优点结合其他权限模型混合使用,已达到更好的效果

RBAC的简单实践

这里以RBAC0作为讲解,以基于mysql等关系数据库为例实现RBAC0。

RBAC0的三个核心组件,是用户、角色、权限。

表设计

表字段
用户表 id
其他
角色表 id
type(角色名称)
其他
用户角色关联表 user_id
role_id
status
其他
角色权限关联表 role_id
permission_id
status(是否启用)
其他
权限表 id
code(权限码)
name(权限名称)
description(权限具体描述)
其他

他们之间的关系为:

用户-用户角色关联表(一对多); 角色-角色权限表(一对多);

用户角色管理表-角色(多对一); 角色权限表-权限(多对一);

简单填充表数据

# 用户表
INSERT user(id,...其他)
VALUES
      (1,...其他),
      (2,...其他);

# 角色表
INSERT role(id,type,...其他)
VALUES
       (1,'user',...其他),
       (2,'merchart',...其他);

# 权限表
INSERT permission(id,code,name,description,...其他)
VALUES
       (1,'user:changePassword','修改密码','更改账户密码',...其他),
       (2,'merchart:publishProduct','发布店铺商品','用于商家账户发布正品全新商品',...其他);

# 用户角色关联表 (status = 1表示启用)
INSERT user_role(user_id,role_id,status,...其他)
VALUES
       (1,1,1,...其他),
       (1,2,1,....其他);

# 角色权限关联表 (status = 1表示启用)
INSERT role_permission(role_id,permission_id,status,...其他)
VALUES
      (1,1,1,...其他),
      (2,2,1,...其他),
      (2,1,1,...其他);

编码权限控制逻辑

这里以Java为例,使用spring框架实现,其他语言都是相似逻辑。

定义权限注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionCheck {
    /**
     * 所需的角色
     */
    Role Role();

    /**
     * 所需的权限
     */
    String permission();

    /**
     * 权限逻辑关系
     * AND: role和permission都需满足
     * OR: 满足role和permission中的任意一个
     * 默认: OR
     */
    Logic logic() default Logic.OR;

    enum Logic{
        AND,
        OR,
    }
}

定义权限对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class permissionInfo{

    private List<Role> role;
    private List<String> permissionCode;
}

@AllArgsConstructor
@Getter
public enum Role {
    user(1),
    Merchant(2);

    private final int Id;

    public static Role getRoleById(int id) {
        for (Role role : Role.values()) {
            if (role.getId() == id) {
                return role;
            }
        }
        throw new IllegalArgumentException("错误的角色码, Id:"+ id);
    }
}

定义sql服务类

// 这里简单只展示sql,使用mybatis框架 其他自行补充

@Mapper
public interface permissionMapper{

@Select("""
SELECT
     rp.role_id AS role
     p.code AS permission_code,
FROM role r
JOIN role_permission rp ON r.id = rp.role_id
JOIN permission p ON p.id = rp.permission_id
WHERE r.user_id = #{userId}
""")
// 这里涉及到 与对象映射时的多个记录 一个对象接收问题 因篇幅问题 这里简单写的  具体需要具体解决
permissionInfo getPermissions(Long userId)

}

定义切面类

@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class PermissionAspect {

   private final PermissionCacheService permissionCacheService;

   @Around("@annotation(permissionCheck)")
   public Object checkPermission(ProceedingJoinPoint joinPoint, PermissionCheck permissionCheck) throws Throwable {

    //效验权限
    boolean result1 = checkPermission(xxxx,xxxx,xxxx);
    //效验角色
    boolean result2 = checkRole(xxxx,xxxx,xxxx)

    if(permissionCheck.logic().equals(PermissionCheck.Logic.AND){
          return result1 & result2 ? joinPoint.proceed() : false; //或者抛出异常、记录日志等操作
     }

    return result1 == true ? joinPoint.proceed() : false; //或者抛出异常、记录日志等操作

   }

}

使用注解

@PermissionCheck(Role = Role.user,permission = "user:changePassword",logic = PermissionCheck.Logic.OR)
                // 指定角色 和 权限  logic默认为OR 这里显然不适合使用AND 所以直接使用默认logic即可
    public Result<String> updatePassword(
            @Parameter(description = "密码修改信息", required = true)
            @RequestBody @Valid UserUpdatePwDTO updatePw,
            @Parameter(description = "用户编号", required = true)
            @PathVariable String userSn,
            @RequestHeader("Authorization")
            String token
    ){
        userService.updatePassword(updatePw, userSn,token);
        return Result.success();
    }

到此已经成功实现了一个基础的RBAC0的权限控制模块。

当然这是使用mysql关系型数据库实现的,在进入接口处频繁的查库会造成性能浪费和过度消耗。所以在此基础上可以增设Redis等缓存层,来缓存权限信息,以此达到更好的性能,降低数据库压力。

当然你可以基于项目的难度,采用更完整的RBAC权限模型。亦或者采用混合模型,取RBAC权限模型的优点,与其他权限模型的优点进行混合使用,以达到更高效更可靠的权限系统。

比如二手市场权限控制时,可以采用RBAC + 权限禁用 。 即在RBAC0基础上 ,再添加一个表,用户标识单个用户被禁用的权限。这样在用户访问接口时,可以先效验是否被禁用,以此更好地适配电商等场景对一些异常风险账户操作的限制。

@Transactional注解事务与锁引发的时序问题 2026-03-27

评论区