0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

复杂场景下的权限系统该怎么玩?

jf_ro2CN3Fa 来源:芋道源码 作者:芋道源码 2022-12-06 10:19 次阅读


ABAC授权模型

基于属性的访问控制(ABAC)- 阿里云IDaaS:

  • https://help.aliyun.com/document_detail/174235.html

ABAC- 百度百科:

  • https://baike.baidu.com/item/abac/3555041?fr=aladdin

个人觉得这两篇文章已经完美描述了ABAC模型的原理

常用的授权模型

此节摘自基于属性的访问控制(ABAC)- 阿里云IDaaS

  • ACL(Access Control List)

在ACL中,包含用户、资源、资源操作 三个关键要素。通过将资源以及资源操作授权给用户而使用户获取对资源进行操作的权限。

  • RBAC(Role-Based Access Control )

是把用户按角色进行归类,通过用户的角色来确定用户能否针对某项资源进行某项操作。RBAC相对于ACL最大的优势就是它简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来,而用户与权限变成了间接关联。

  • ABAC(Attribute Base Access Control)

基于属性的权限控制不同于常见的将用户通过某种方式关联到权限的方式,ABAC则是通过动态计算一个或一组属性来是否满足某种条件来进行授权判断(可以编写简单的逻辑)。

属性通常来说分为四类:用户属性(如用户年龄),环境属性(如当前时间),操作属性(如读取)和对象属性,所以理论上能够实现非常灵活的权限控制,几乎能满足所有类型的需求。

ABAC的访问控制

基于ABAC访问控制需要动态计算实体的属性、操作类型、相关的环境来控制是否有对操作对象的权限,所以在设计的时候需要考虑的条件判断的灵活性、通用性、易用性,用户只需要通过web页面即可配置授权,这需要减少硬编码似得逻辑变得简单通用,那么这需要满足一些运算符来实现。

类型 运算符
算术运算符 +, -, *, /, %, ^, div, mod
关系运算符 <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
逻辑运算符 and, or, not, &&, ||, !
条件 ?:

使用场景

用户只需要配置 user.age > 20 的条件即可获得特定的权限。

表达式语言

正如上一节所说的需要对某种条件进行解析那么就需要表达式语言,这让我想起了Spring Framework@Value注解和MyBatis

//相信很多Javaboy都使用过的吧
@Value("A?B:C")
privateStringA;
"XXX">
<iftest="user!=null">
XXXX
if>

看到这里大家应该大致猜到了ABAC的的核心就是Expression Language(EL),虽然上面的代码演示是使用Java生态作为演示,但是可以大胆的相信其他的编程语言都是有着自己的EL框架的。

java EL框架列表

  • spring-expression
  • OGNL
  • MVEL
  • JBoss EL

这里就不一一列举了感兴趣可以查看 Java EL生态排名:

https://mvnrepository.com/open-source/expression-languages

SpEL性能

Spring Expression Language (SpEL)官方文档:

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions

Spring官方文档摘取 翻译

Spring Framework 4.1 包含一个基本的表达式编译器。表达式通常被解释,这在评估期间提供了很多动态灵活性,但没有提供最佳性能。对于偶尔的表达式使用,这很好,但是,当由其他组件(如 Spring Integration)使用时,性能可能非常重要,并且没有真正需要动态性。

SpEL 编译器旨在满足这一需求。在求值期间,编译器生成一个 Java 类,它体现了运行时的表达式行为,并使用该类来实现更快的表达式求值。由于缺少表达式周围的类型,编译器在执行编译时使用在表达式的解释评估期间收集的信息。例如,它不能纯粹从表达式中知道属性引用的类型,但在第一次解释评估期间,它会找出它是什么。当然,如果各种表达式元素的类型随时间发生变化,基于此类派生信息进行编译可能会在以后造成麻烦。出于这个原因,编译最适合其类型信息在重复计算时不会改变的表达式。

考虑以下基本表达式:

someArray[0].someProperty.someOtherProperty< 0.1

由于前面的表达式涉及数组访问、某些属性取消引用和数字操作,因此性能提升非常显着。在一个运行 50000 次迭代的微型基准测试示例中,使用解释器评估需要 75 毫秒,使用表达式的编译版本仅需要 3 毫秒。

有关SpEL的性能Spring官方描述说SpEL的性能很棒(个人感觉Spring对自己的测试结果是不是少打了一个0啊,3ms的时间有点无法理解)

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

ABAC实践

本章仅实现ABAC的原理,不会对Spring Security和 Apache Shiro做任何的集成

因为笔者本人是一位Spring boy,所以工程项目会以Spring Boot框架作为基础,使用其它编程语言的同学可能需要受苦一下了, 大家看懂原理就可以了。

  • Java 8
  • Spring Boot 2.7.6
  • MyBatis Plus 3.5.2
  • MySQL 8.0

数据库设计

f1b5260c-7508-11ed-8abf-dac502259ad0.png图片
表名 作用
user 用户表(ACL和RBAC都有这张表)
user_contribution 用户的附属信息 (用户属性之类的,不能不一定只有这张表)
permission 权限表达式(ACL和RBAC都有这张表)
abac rbac表达式
abac_permission rbac表达式和权限的绑定关系, o2m
>基于SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element实现的后台管理系统+用户小程序,支持RBAC动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
>
>*项目地址://github.com/YunaiV/yudao-cloud>
>*视频教程//doc.iocoder.cn/video/>

#用户表
DROPTABLEifEXISTSuser;
CREATETABLEuser(
idbigint(20)NOTNULLCOMMENT'主键ID',
namevarchar(30)NULLDEFAULTNULLCOMMENT'姓名',
ageint(11)NULLDEFAULTNULLCOMMENT'年龄',
emailvarchar(50)NULLDEFAULTNULLCOMMENT'邮箱',
PRIMARYKEY(id)
);
#用户边缘数据
DROPTABLEifEXISTSuser_contribution;
CREATETABLEuser_contribution(
idbigint(20)NOTNULLCOMMENT'主键ID',
user_idbigint(20)NOTNULLCOMMENT'用户表ID',
repositoryvarchar(100)NOTNULLCOMMENT'仓库',
PRIMARYKEY(id)
);
#权限表
DROPTABLEifEXISTSpermission;
CREATETABLEpermission(
idbigint(20)NOTNULLCOMMENT'主键ID',
permissionvarchar(100)NOTNULLCOMMENT'权限名称',
PRIMARYKEY(id)
);
#abac表达式表
DROPTABLEifEXISTSabac;
CREATETABLEabac(
idbigint(20)NOTNULLCOMMENT'主键ID',
expressionvarchar(100)NOTNULLCOMMENT'abac表达式',
PRIMARYKEY(id)
);
#abac表和权限表的关联表,o2m
DROPTABLEifEXISTSabac_permission;
CREATETABLEabac_permission(
idbigint(20)NOTNULLCOMMENT'主键ID',
abac_idbigint(20)NOTNULLCOMMENT'abac表ID',
permission_idbigint(20)NOTNULLCOMMENT'permission表ID',
PRIMARYKEY(id)
);

java程序

因为篇幅问题, 只会使用必要的代码, 代码文件结构

|src
||test
|||java
||||plus.wcj.abac.AbacApplicationTests.java测试类代码
||main
|||resources
||||application.yml
||||db
|||||schema-h2.sql//DDL
|||||data-h2.sql//DML
|||java
||||plus.wcj.abac
|||||||AbacApplication.java//SpringBoot启动类
|||||||security
||||||||MetadataCustomizer.java//自定义user信息
||||||||SecurityContext.java//Security上下文
|||||||entity
||||||||Abac.java
||||||||User.java
|||||||dao
||||||||UserDao.java
||||||||AbacDao.java
|||||||service
||||||||UserService.java
||||||||AbacService.java
|pom.xml

crud代码

pom.xml



org.springframework.boot
spring-boot-starter



org.springframework.boot
spring-boot-starter-test
test



com.baomidou
mybatis-plus-boot-starter
3.5.2



mysql
mysql-connector-java



org.projectlombok
lombok
compile


entity类

/**
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
@Data
publicclassAbac{
privateLongid;
privateStringexpression;

/**expression对应的permissions列表*/
@TableField(exist=false)
privateListpermissions;
}

@Data
publicclassUser{
privateLongid;
privateStringname;
privateIntegerage;
privateStringemail;

/**用户提交过仓库*/
@TableField(exist=false)
privateListcontributions=newArrayList<>();

/**存放一些乱七八糟的数据,当然contributions字段也放在这里*/
@TableField(exist=false)
privateMapmetadata=newHashMap<>();
}

dao类

/**
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
@Mapper
publicinterfaceAbacDaoextendsBaseMapper<Abac>{

/**获取abacId关联权限*/
@Select("SELECTp.permission
"+
"FROMabac_permissionapLEFTJOINpermissionpONp.id=ap.permission_id
"+
"WHEREap.abac_id=#{abacId}")
ListselectPermissions(LongabacId);

}

/**
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
@Mapper
publicinterfaceUserDaoextendsBaseMapper<User>{

/**获取用户的仓库信息*/
@Select("SELECTrepositoryFROMuser_contributionWHEREuser_id=#{userId}")
ListselectRepository(@Param("userId")LonguserId);
}

service类

/**
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
@Service
@RequiredArgsConstructor
publicclassAbacService{
privatefinalAbacDaoabacDao;

/**获取abac表达式详细信息列表*/
publicListgetAll(){
Listabacs=abacDao.selectList(null);
for(Abacabac:abacs){
Listpermissions=abacDao.selectPermissions(abac.getId());
abac.setPermissions(permissions);
}
returnabacs;
}
}


/**
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
@Service
@RequiredArgsConstructor
publicclassUserService{
privatefinalUserDaouserDao;

/**根据userId获取用户详细信息*/
publicUserget(LonguserId){
Useruser=userDao.selectById(userId);
Listrepository=userDao.selectRepository(userId);
user.setContributions(repository);
returnuser;
}
}

security上下文

/**
*自定义用户元数据用于获取一些实体的属性、操作类型、相关的环境
*
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
publicinterfaceMetadataCustomizer{

/**自定义用户元数据*/
voidcustomize(Useruser);
}

/**
*解析abac表达式
*
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
@Component
publicclassSecurityContext{
/**SpEL表达式解析器*/
privatefinalExpressionParserexpressionParser=newSpelExpressionParser();

/**
*解析abac表达式
*@paramuser用户详细信息
*@paramabacsabac表达式详细信息集合
*@returnexpressions集合,将这个结果集存放到SpringSecurity或者ApacheAPISIX的userDetail上下文中
*/
publicListrbacPermissions(Useruser,Listabacs){
returnthis.rbacPermissions(user,abacs,Collections.emptyList());
}

/**
*解析abac表达式
*@paramuser用户详细信息
*@paramabacsabac表达式详细信息集合
*@parammetadataCustomizers自定义用户元数据用于获取一些实体的属性、操作类型、相关的环境
*@returnexpressions集合,将这个结果集存放到SpringSecurity或者ApacheAPISIX的userDetail上下文中
*/
publicListrbacPermissions(Useruser,Listabacs,ListmetadataCustomizers){
//处理自定义元数据
metadataCustomizers.forEach(metadataCustomizer->metadataCustomizer.customize(user));

Listexpressions=newArrayList<>();
for(Abacabac:abacs){
//解析表达式的求值器
Expressionexpression=expressionParser.parseExpression(abac.getExpression());
//创建环境上下文
EvaluationContextcontext=newStandardEvaluationContext(user);
//获取expression的结果
if(expression.getValue(context,boolean.class)){
expressions.addAll(abac.getPermissions());
}
}
returnexpressions;
}

}

测试类

/**
*@authorchangjinwei(魏昌进)
*@since2022/11/26
*/
@SpringBootTest
classAbacApplicationTests{

@Autowired
privateUserServiceuserService;

@Autowired
privateAbacServiceabacService;

@Autowired
privateSecurityContextsecurityContext;

/**获取不同用户的abac权限*/
@Test
voidtestRbac(){
Useruser=userService.get(1L);
Listrbac=abacService.getAll();
Listpermissions=securityContext.rbacPermissions(user,rbac);
System.out.println(permissions);


user=userService.get(2L);
permissions=securityContext.rbacPermissions(user,rbac);
System.out.println(permissions);

user=userService.get(3L);
permissions=securityContext.rbacPermissions(user,rbac);
System.out.println(permissions);

}

/**
*获取自定义权限
*/
@Test
voidtestMetadataCustomizer(){
Useruser=userService.get(1L);
Listrbac=abacService.getAll();

Listpermissions=securityContext.rbacPermissions(user,rbac);
System.out.println(permissions);

permissions=securityContext.rbacPermissions(user,rbac,getMetadataCustomizer());
System.out.println(permissions);
}

/**模拟网络ip*/
privateListgetMetadataCustomizer(){
returnnewArrayList(){{
add(user->user.getMetadata().put("ip","192.168.0.1"));
}};
}
}
DELETEFROMuser;
INSERTINTOuser(id,name,age,email)
VALUES(1,'魏昌进',26,'mail@wcj.plus'),
(2,'test',1,'mail1@wcj.plus'),
(3,'admin',1,'mail2@wcj.plus');

DELETEFROMuser_contribution;
INSERTINTOuser_contribution(id,user_id,repository)
VALUES(1,1,'galaxy-sea/spring-cloud-apisix'),
(2,2,'spring-cloud/spring-cloud-commons'),
(3,2,'spring-cloud/spring-cloud-openfeign'),
(4,2,'alibaba/spring-cloud-alibaba'),
(5,2,'Tencent/spring-cloud-tencent'),
(6,2,'apache/apisix-docker');

DELETEFROMpermission;
INSERTINTOpermission(id,permission)
VALUES(1,'githubmerge'),
(2,'githubclose'),
(3,'githubopen'),
(4,'githubcomment');


DELETEFROMabac;
INSERTINTOabac(id,expression)
VALUES(1,'contributions.contains(''galaxy-sea/spring-cloud-apisix'')'),
(2,'name==''admin'''),
(3,'metadata.get(''ip'')==''192.168.0.1''');

DELETEFROMabac_permission;
INSERTINTOabac_permission(id,abac_id,permission_id)
VALUES(1,1,1),

(2,2,1),
(3,2,2),
(4,2,3),
(5,2,4),

(6,3,1),
(7,3,2),
(8,3,3),
(9,3,4);

Spring Security 和 Apache Shiro整合

Spring Security只需要修改拦截器即可在获取到UserDetailsSecurityContext#rbacPermissions转换为GrantedAuthority即可

/**
*这里是伪代码,展示一下大概逻辑
*
*@authorchangjinwei(魏昌进)
*@since2022/04/29
*/
publicclassIamOncePerRequestFilterimplementsOncePerRequestFilter{

@Autowired
privateSecurityContextsecurityContext;

@Autowired
privateAbacServiceabacService;

@Autowired
privateListmetadataCustomizers;

@Autowired
publicvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain){
UserDetailsuser=toUser();
Listpermissions=securityContext.rbacPermissions(user,abacService.getAll(),metadataCustomizers);
ListabacAuthority=permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
user.getAuthorities().addAll(abacAuthority);
}
}

项目源码:

  • https://github.com/galaxy-sea/galaxy-blogs/tree/master/code/abac

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编码
    +关注

    关注

    6

    文章

    942

    浏览量

    54826
  • 权限系统
    +关注

    关注

    0

    文章

    6

    浏览量

    5955
  • 阿里云
    +关注

    关注

    3

    文章

    956

    浏览量

    43038

原文标题:复杂场景下的权限系统该怎么玩?ABAC权限模型帮你搞定它!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    linux权限管理详解

    权限:在计算机系统中,权限是指某个计算机用户具有使用软件资源的权利。
    的头像 发表于 12-25 09:43 81次阅读

    搞懂Linux权限管理,提升系统安全性与稳定性

    linux安全上下文与特殊权限 5. 文件系统访问控制列表facl 6. sudo 7. 管理命令 1.权限简介 文件的权限主要针对三类对象进行定义:   owner 属主 u gr
    的头像 发表于 11-22 10:31 148次阅读
    搞懂Linux<b class='flag-5'>权限</b>管理,提升<b class='flag-5'>系统</b>安全性与稳定性

    汽车雷达回波发生器的技术原理和应用场景

    的波束并向前传播,以模拟真实的雷达工作环境。应用场景 自动驾驶系统的开发和测试:在自动驾驶系统的开发和测试过程中,汽车电子雷达回波发生器可以模拟各种交通场景
    发表于 11-15 14:06

    Linux用户身份与进程权限详解

    在学习 Linux 系统权限相关的主题时,我们首先关注的基本都是文件的 ugo 权限。ugo 权限信息是文件的属性,它指明了用户与文件之间的关系。但是真正操作文件的却是进程,也就是说用
    的头像 发表于 10-23 11:41 349次阅读
    Linux用户身份与进程<b class='flag-5'>权限</b>详解

    复杂电磁环境模拟系统设计方案

    是能够模拟真实战场或特定测试场景复杂电磁环境,包括各种通信信号、雷达信号、干扰信号、噪声等,以评估电子设备的性能和稳定性。 智慧华盛恒辉系统组成 1. 信号生成单元 功能:根据预设
    的头像 发表于 07-17 17:06 431次阅读

    鸿蒙原生应用元服务开发-位置服务申请权限

    申请位置权限开发指导 场景概述 应用在使用位置服务系统能力前,需要检查是否已经获取用户授权访问设备位置信息。如未获得授权,可以向用户申请需要的位置权限
    发表于 06-18 15:27

    鸿蒙原生应用元服务开发-位置服务获取设备信息开发

    ,并针对使用场景做了适当的优化处理,应用可以直接匹配使用,简化开发复杂度。系统当前支持场景如下表所示。 定位场景类型说明 . 导航
    发表于 06-14 14:46

    鸿蒙原生应用元服务-访问控制(权限)开发应用权限列表二

    接口,用于系统应用完成口令输入框绘制场景权限级别 :system_basic 授权方式 :system_grant ACL使能 :FALSE
    发表于 04-24 15:43

    鸿蒙原生应用元服务-访问控制(权限)开发应用权限列表一

    授权方式 :system_grant ACL使能 :TRUE ohos.permission.SET_TIME 允许应用修改系统时间。 权限级别 :system_basic 授权方式
    发表于 04-23 14:33

    鸿蒙原生应用元服务-访问控制(权限)开发校验环节

    一、场景介绍 应用在提供对外功能服务接口时,可以根据接口涉数据的敏感程度或所涉能力的安全威胁影响,在系统定义的权限列表中权限定义列表选择合适的权限
    发表于 04-22 17:52

    鸿蒙原生应用元服务-访问控制(权限)开发概念和使用基本原则

    ATM (AccessTokenManager) 是HarmonyOS上基于AccessToken构建的统一的应用权限管理能力。 默认情况,应用只能访问有限的系统资源。但某些情况
    发表于 04-18 15:39

    鸿蒙原生应用元服务-访问控制(权限)开发等级和类型

    的接口,系统弹框由用户授权,用户结合应用运行场景的上下文,识别出应用申请相应敏感权限的合理性,从而做出正确的选择。 即使用户向应用授予过请求的权限,应用在调用受此
    发表于 04-17 15:29

    鸿蒙原生应用元服务-访问控制(权限)开发场景权限声明

    ** 一、 场景介绍** 应用的APL(Ability Privilege Level)等级分为normal、system_basic和system_core三个等级,默认情况,应用的APL等级
    发表于 04-16 14:40

    一分钟了解鸿蒙OS 应用权限管理

    HarmonyOS 中所有的应用均在应用沙盒内运行。默认情况,应用只能访问有限的系统资源,系统负责管理应用对资源的访问权限。 应用权限管理
    的头像 发表于 01-26 15:23 816次阅读

    前端大仓monorepo权限设计思路和实现方案

    在 GitLab 未支持文件目录权限设置之前,对于文件目录权限的控制主要依赖 Git 的钩子函数,在代码提交的时候,对暂存区的变更文件进行识别并做文件权限校验,流程设计也不怎么复杂,只
    的头像 发表于 01-12 09:52 723次阅读
    前端大仓monorepo<b class='flag-5'>权限</b>设计思路和实现方案