二进制权限设计
RBAC模型
RBAC(Role-Based Access Control,基于角色的访问控制)是一种访问控制模型,通过将权限分配给角色,再将角色分配给用户,来实现对系统资源的访问控制。在RBAC模型中,用户与角色之间、角色与权限之间都是多对多的关系。每个用户可以被分配一个或多个角色,每个角色拥有若干权限。这种模型简化了用户和权限的关系,使得权限管理更加灵活和易于维护。
举例
如果10个可以自由搭配的权限,则可能有1024种结果。
如果每个角色对应一种结果,则需要建立1024个角色,然后每个用户只对应一个角色。
如果每个角色对应一个权限,则用户最多要添加10个角色,用户数据是个增量规模较大的数据,翻10倍以后,数据量会非常大,而且如果后续继续增加权限,则不好控制数据量。查询效率也会下降。
使用一个字段存储所有权限
如果把一个用户的所有权限用一个字段存储起来,则数据量规模与用户数据大小一致。这个字段可以是权限ID的集合。
但是这种方案也有缺点,不支持反查,比如要查询拥有管理员的用户有哪些则不支持。这时候可以考虑结合RBAC模型。一般情况下C端是不需要支持反查的。C端查询的时候查询用户权限信息表,后台反查的时候查询明细表。
存储字段设计
直接存储权限ID集合,数据库直观的看出拥有哪些权限,大小比二进制大,而且内存判断使用遍历没有位运算效率高
存储二进制数字的十进制,int类型最多32个权限,long类型最多64个权限,而且数据库不能够直观看到权限,需要计算。
存储二进制字符串,缺点也是权限数量限制,优点是能够直观看到有哪些权限
存储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编码后存储。
总结
使用一个字段存储数据可以避免权限膨胀引起的用户数据膨胀。
使用二进制来表示权限,可以通过位运算方便增加、移除、检查权限,但是受到长度限制,且不是幂等的。
使用位图则可以避免二进制的缺点,发扬其优点。