RBAC模型

RBAC(Role-Based Access Control,基于角色的访问控制)是一种访问控制模型,通过将权限分配给角色,再将角色分配给用户,来实现对系统资源的访问控制。在RBAC模型中,用户与角色之间、角色与权限之间都是多对多的关系。每个用户可以被分配一个或多个角色,每个角色拥有若干权限。这种模型简化了用户和权限的关系,使得权限管理更加灵活和易于维护。‌

举例

如果10个可以自由搭配的权限,则可能有1024种结果。

  • 如果每个角色对应一种结果,则需要建立1024个角色,然后每个用户只对应一个角色。

  • 如果每个角色对应一个权限,则用户最多要添加10个角色,用户数据是个增量规模较大的数据,翻10倍以后,数据量会非常大,而且如果后续继续增加权限,则不好控制数据量。查询效率也会下降。

使用一个字段存储所有权限

  • 如果把一个用户的所有权限用一个字段存储起来,则数据量规模与用户数据大小一致。这个字段可以是权限ID的集合。

  • 但是这种方案也有缺点,不支持反查,比如要查询拥有管理员的用户有哪些则不支持。这时候可以考虑结合RBAC模型。一般情况下C端是不需要支持反查的。C端查询的时候查询用户权限信息表,后台反查的时候查询明细表。

存储字段设计

  1. 直接存储权限ID集合,数据库直观的看出拥有哪些权限,大小比二进制大,而且内存判断使用遍历没有位运算效率高

  2. 存储二进制数字的十进制,int类型最多32个权限,long类型最多64个权限,而且数据库不能够直观看到权限,需要计算。

  3. 存储二进制字符串,缺点也是权限数量限制,优点是能够直观看到有哪些权限

  4. 存储bitset的base64字符串,缺点不够直观,优点不受权限数量限制

二进制权限设计

参考LINUX操作系统的文件权限设计,读(r)、写(w)、执行(x),分别用二进制4、2、1表示,7(111)代表拥有读写执行全部权限。例如linux命令

chomd 777 test.txt

赋予用户读写执行权限。

示例代码

二进制
package com.zoe.business.authroity;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 权限枚举设置
 *
 * @author zoe
 * @date 2024/8/5 16:45
 */
@Getter
@AllArgsConstructor
public enum ArticleAuthEnumEnum {
    VIEW(1, "访问"),
    EDIT(1 << 1, "修改"),
    DELETE(1 << 2, "删除"),
    COMMENT(1 << 3, "评论"),
    DEL_COMMENT(1 << 4, "删除评论"),
    ;

    private final int code;
    private final String desc;

    private static final Map<Integer, ArticleAuthEnumEnum> VALUES = new HashMap<>();

    static {
        for (final ArticleAuthEnumEnum item : ArticleAuthEnumEnum.values()) {
            VALUES.put(item.getCode(), item);
        }
    }

    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static ArticleAuthEnumEnum of(int code) {
        return VALUES.get(code);
    }

    public boolean hasAuthority(int auth) {
        return code == (code & auth);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 32; i++) {
            List<String> list = new ArrayList<>();
            for (ArticleAuthEnumEnum value : values()) {
                boolean b = value.hasAuthority(i);
                if (b) {
                    list.add(value.desc);
                }
            }
            System.out.println(i + "拥有的权限:" + list);
        }
    }
}
注意事项

新增、删除、查询都可以直接加、减、与运算。但是注意,这个不是幂等的。比如给一个没有任何权限的用户赋予访问权限,如果在原来0的基础上加1,则有了访问权限,如果重复提交请求,多次加1则权限会不正确。

位图
package com.zoe.business.authroity2;

import lombok.Getter;

import java.util.BitSet;

/**
 * 权限枚举设置
 *
 * @author zoe
 * @date 2024/8/5 16:45
 */
@Getter
public enum Permission {

    // 定义一些示例权限
    READ(0),

    WRITE(1),

    EXECUTE(2),

    DELETE(3),

    SHARE(4),

    DOWNLOAD(1000);
    // 可以继续添加更多权限

    private final int bitPosition;

    Permission(int bitPosition) {
        this.bitPosition = bitPosition;
    }

    public int getBitPosition() {
        return bitPosition;
    }

    // 设置权限
    public static void setPermission(BitSet permissions, Permission permission) {
        permissions.set(permission.getBitPosition());
    }

    // 移除权限
    public static void clearPermission(BitSet permissions, Permission permission) {
        permissions.clear(permission.getBitPosition());
    }

    // 检查权限
    public static boolean hasPermission(BitSet permissions, Permission permission) {
        return permissions.get(permission.getBitPosition());
    }

    public static void main(String[] args) {
        BitSet userPermissions = new BitSet();

        // 设置读和写权限
        setPermission(userPermissions, Permission.READ);
        setPermission(userPermissions, Permission.WRITE);
        setPermission(userPermissions, Permission.DOWNLOAD);

        System.out.println("User Permissions: " + userPermissions.toString());

        // 检查权限
        System.out.println("Has READ: " + hasPermission(userPermissions, Permission.READ));
        System.out.println("Has WRITE: " + hasPermission(userPermissions, Permission.WRITE));
        System.out.println("Has EXECUTE: " + hasPermission(userPermissions, Permission.EXECUTE));
        System.out.println("Has EXECUTE: " + hasPermission(userPermissions, Permission.DOWNLOAD));
    }
}
注意事项

使用位图可以避免int和long的长度限制,而且天然支持幂等。但是存储的时候需要序列化,使用byte[] byteArray = bitSet.toByteArray();转成二进制数组存储,或者base64编码后存储。

总结

  1. 使用一个字段存储数据可以避免权限膨胀引起的用户数据膨胀。

  2. 使用二进制来表示权限,可以通过位运算方便增加、移除、检查权限,但是受到长度限制,且不是幂等的。

  3. 使用位图则可以避免二进制的缺点,发扬其优点。