如何设计一个通用的权限管理系统

news/2024/5/2 6:06:59/文章来源:https://blog.csdn.net/qq_42003636/article/details/129184535

一个系统,如果没有安全控制,是十分危险的,一般安全控制包括身份认证和权限管理。用户访问时,首先需要查看此用户是否是合法用户,然后检查此用户可以对那些资源进行何种操作,最终做到安全访问。身份认证的方式有很多种,最简单的就是直接用户名密码,还有业内比较通用的方式CAS方式登陆等;授权的框架也很多,比如OAuth2,Shiro等。本文首先会讲解一下CAS的概念,以及基于角色的权限管理模型(RBAC)的概念,接着进行数据表的设计,最后讲解如何利用Shiro进行权限管理。

一、CAS身份认证


集中式认证服务(英语:Central Authentication Service,缩写CAS)是一种针对万维网的单点登录协议。它的目的是允许一个用户访问多个应用程序,而只需提供一次凭证。

1.1、名词概念

CAS的核心就是其Ticket,及其在Ticket之上的一系列处理操作。CAS的主要票据有TGT、ST、PGT、PGTIOU、PT,其中TGT、ST是CAS1.0(基础模式)协议中就有的票据,PGT、PGTIOU、PT是CAS2.0(代理模式)协议中有的票据。这些票据谁生成得了?肯定是有相关服务的,主要服务有:KDC,AS,TGS。两者媒介肯定也是有的:TGC。

1.1.1、CAS的Ticket

1)TGT(Ticket Granting Tieckt)

TGT是CAS(具体为 KDC 的 AS 发放)为用户签发的登录票据,拥有了TGT,用户就可以证明自己在CAS成功登录过。TGT封装了Cookie值以及此Cookie值对应的用户信息。用户在CAS认证成功后,CAS生成cookie,写入浏览器,同时生成一个TGT对象,放入自己的缓存,TGT对象的ID就是cookie的值。当HTTP再次请求到来时,如果传过来的有CAS生成的cookie,则CAS以此cookie值为key查询缓存中有无TGT ,如果有的话,则说明用户之前登录过,如果没有,则用户需要重新登录。

简而言之,即获取这样一张票据后,以后申请各种其他服务票据 (ST) 便不必再向 KDC 提交身份认证信息 ( 准确术语是 Credentials) 。

2)ST(Service ticket)

ST是CAS(由 KDC 的 TGS 发放)为用户签发的访问某一service的票据。

用户访问service时,service发现用户没有ST,则要求用户去CAS获取ST。用户向CAS发出获取ST的请求,如果用户的请求中包含cookie,则CAS会以此cookie值为key查询缓存中有无TGT,如果存在TGT,则用此TGT签发一个ST,返回给用户。用户凭借ST去访问service,service拿ST去CAS验证,验证通过后,允许用户访问资源。

任何一台 Workstation 都需要拥有一张有效的 Service Ticket 才能访问域内部的应用 (Applications) 。如果能正确接收 Service Ticket ,说明在 CASClient-CASServer 之间的信任关系已经被正确建立起来。

3)PGT(Proxy Granting Ticket)

Proxy Service的代理凭据。用户通过CAS成功登录某一Proxy Service后,CAS生成一个PGT对象,缓存在CAS本地,同时将PGT的值(一个UUID字符串)回传给Proxy Service,并保存在Proxy Service里。Proxy Service拿到PGT后,就可以为Target Service(back-end service)做代理,为其申请PT。

4)PT(Proxy Ticket)

PT是用户访问Target Service(back-end service)的票据。如果用户访问的是一个Web应用,则Web应用会要求浏览器提供ST,浏览器就会用cookie去CAS获取一个ST,然后就可以访问这个Web应用了。如果用户访问的不是一个Web应用,而是一个C/S结构的应用,因为C/S结构的应用得不到cookie,所以用户不能自己去CAS获取ST,而是通过访问proxy service的接口,凭借proxy service的PGT去获取一个PT,然后才能访问到此应用。

TGT、ST、PGT、PT之间关系的总结

1:ST是TGT签发的。用户在CAS上认证成功后,CAS生成TGT,用TGT签发一个ST,ST的ticketGrantingTicket属性值是TGT对象,然后把ST的值redirect到客户应用。

2:PGT是ST签发的。用户凭借ST去访问Proxy service,Proxy service去CAS验证ST(同时传递PgtUrl参数给CAS),如果ST验证成功,则CAS用ST签发一个PGT,PGT对象里的ticketGrantingTicket是签发ST的TGT对象。

3:PT是PGT签发的。Proxy service代理back-end service去CAS获取PT的时候,CAS根据传来的pgt参数,获取到PGT对象,然后调用其grantServiceTicket方法,生成一个PT对象。

1.1.2、CAS的服务

CAS的主要服务有:

  • KDC(Key Distribution Center );

  • AS(Authentication Service)它索取 Crendential,发放 TGT;

  • TGS(Ticket Granting Service),索取 TGT ,发放 ST。

1.1.3、CAS的媒介

TGC(Ticket Granting Cookie)

存放用户身份认证凭证的cookie,在浏览器和CAS Server间通讯时使用,并且只能基于安全通道传输(Https),是CAS Server用来明确用户身份的凭证。

1.2、CAS工作原理

CAS的单点登录的认证过程,所用应用服务器受到应用请求后,检查ST和TGT,如果没有或不对,转到CAS认证服务器登录页面,通过安全认证后得到ST和TGT,再重新定向到相关应用服务器,在回话生命周期之内如果再定向到别的应用,将出示ST和TGT进行认证,注意,取得TGT的过程是通过SSL安全协议的。

从网上找了一个比较专业又比较详细的CAS工作原理流程图:

专业版可能比较晦涩难懂,来个通俗版。通俗形象地说就是:相当于用户要去游乐场,首先要在门口检查用户的身份 ( 即 CHECK 用户的 ID 和 PASS), 如果用户通过验证,游乐场的门卫 (AS) 即提供给用户一张门卡 (TGT)。

这张卡片的用处就是告诉游乐场的各个场所,用户是通过正门进来,而不是后门偷爬进来的,并且也是获取进入场所一把钥匙。

现在用户有张卡,但是这对用户来不重要,因为用户来游乐场不是为了拿这张卡的而是为了游览游乐项目,这时用户摩天楼,并想游玩。

这时摩天轮的服务员 (client) 拦下用户,向用户要求摩天轮的 (ST) 票据,用户说用户只有一个门卡 (TGT), 那用户只要把 TGT 放在一旁的票据授权机 (TGS) 上刷一下。

票据授权机 (TGS) 就根据用户现在所在的摩天轮,给用户一张摩天轮的票据 (ST), 这样用户有了摩天轮的票据,现在用户可以畅通无阻的进入摩天轮里游玩了。

当然如果用户玩完摩天轮后,想去游乐园的咖啡厅休息下,那用户一样只要带着那张门卡 (TGT). 到相应的咖啡厅的票据授权机 (TGS) 刷一下,得到咖啡厅的票据 (ST) 就可以进入咖啡厅

当用户离开游乐场后,想用这张 TGT 去刷打的回家的费用,对不起,用户的 TGT 已经过期了,在用户离开游乐场那刻开始,用户的 TGT 就已经销毁了 。

二、基于角色的权限管理模型


在业界接受度较高的权限模型是RBAC(Role-Based Access Control),基本的概念是将“角色”这个概念赋予用户,在系统中用户通过分配角色从而获得相应的权限,一个用户可以有多个角色,一个角色可以有多个权限,从而实现权限的灵活配置。

2.1、基本的RBAC模型

最基本的RBAC模型,就是由“用户”,“角色”以及“权限”这三个主体组成,一个用户可以有多个角色,一个角色可以有多个权限,他们之间的关系可以是多对一关系,也可以是多对多关系。

2.2、引入用户组的RBAC模型

如果用户数量比较庞大,新增一个角色时,需要为大量用户都重新分配一遍新的角色,工程量巨大,此时可以引入用户组的概念。如果部分用户的使用场景是相对一致和基础的,可以把这些用户打包成一个组,基于这个组的对象进行角色和权限的赋予。最终用户拥有的所有权限 = 用户个人拥有的权限+该用户所在用户组拥有的权限。

2.3、角色分级的RBAC模型

在一些业务场景中,上层角色需要继承下层角色的全部权限,此时则需要使用角色继承的RBAC模型。此时除了对角色进行定义,还需要管理角色间的关系,通过关系来体现角色的层级关系,从而达到继承权限的效果。角色的继承关系主有两种:树形图和有向无环图。

继承关系常常来源于公司团队的组织架构,此时常常将角色与组织结构进行关联达到继承角色模型的效果。

2.4、角色限制的RBAC模型

在一些产品或系统中,部分角色可能是需要隔离的、不允许被同时赋予一个人的,比如不能既是运动员又是裁判员。因此,有些角色存在互拆关系。此外,限制还可能是数量上的,比如某个产品组中有且只有一个管理员,不允许删除或再分配其他管理员。

根据不同的业务需求,限制的形式很多,需要注意的是不能仅仅依赖后段限制,而是要在前端展示清晰的规则和恰当的限制,避免用户出错。

2.5、权限管理的基本元素

权限管理的基本元素为:用户,角色,资源,操作,权限。

1、用户 应用系统的具体操作者,用户可以自己拥有权限信息,可以归属于0~n个角色,可属于0~n个组。他的权限集是自身具有的权限、所属的各角色具有的权限、所属的各组具有的权限的合集。它与权限、角色、组之间的关系都是n对n的关系。

2、用户组(可选) 为了更好地管理用户,对用户进行分组归类,简称为用户分组。组也具有上下级关系,可以形成树状视图。在实际情况中,我们知道,组也可以具有自己的角色信息、权限信息。

3、角色 为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。

4、资源 权限管理的一个单元实体对象,我们广义的称之为资源,可以是一个人,也可以是一个产品,一个文件,一个页面 等等。5、操作 对资源进行的实际操作,比如读、写、编辑等等。

6、权限 资源+操作,构成一个权限控制点。

对象间的关系包括:

  • 是否关系

  • 继承关系

  • 限制关系(互斥、范围限制、边界限制、字段限制)

三、数据表设计


按照RBAC模型,数据库可以这样设计:

1、产品表(t_product_info)

字段名称

字段

类型

备注

产品Id

pro_id

int(11)

自增

产品名称(英)

name_en

varchar(50)

not null

产品名层(中)

name_ch

varchar(50)

not null

创建人

creator

varchar(50)

not null

所属人

owner

varchar(50)

not null

描述

description

varchar(255)

创建时间

create_time

timestamp

not null

2、产品成员表(t_product_member)

字段名称

字段

类型

备注

记录ID

id

int(11)

自增

产品ID

pro_id

int(11)

fk:t_produck_info

成员ID

member_id

int(11)

not null

创建时间

create_time

timestamp

not null

3、用户信息表(t_user_info)

字段名称

字段

类型

备注

用户ID

user_id

int(11)

not null

英文名

nike_name

varchar(10)

not null

中文名

real_name

varchar(10)

not null

创建时间

create_time

timestamp

not null

更新时间

update_time

timestamp

not null

4、用户角色表(t_user_role)

字段名称

字段

类型

备注

记录ID

id

int(11)

自增

用户ID

user_id

int(11)

not null

角色ID

role_id

varchar(50)

not null

创建时间

create_time

timestamp

not null

5、角色表(t_role)

字段名称

字段

类型

备注

记录ID

id

int(11)

自增

角色ID

role_id

varchar(50)

not null,比如:A~USER

角色名称

role_name

varchar(50)

not null

对象

object

varchar(50)

not null

对象ID

object_id

varchar(50)

not null

角色备注

comment

varchar(255)

创建时间

create_time

timestamp

not null

6、基础角色表(t_role_base)

字段名称

字段

类型

备注

记录ID

id

int(11)

自增

角色ID

role_id

varchar(50)

not null,比如:A~USER

角色名称

role_name

varchar(50)

not null

角色备注

comment

varchar(255)

权限

permission

varchar(255)

not null

创建时间

create_time

timestamp

not null

7、角色权限表(t_role_permission)

字段名称

字段

类型

备注

记录ID

id

int(11)

自增

角色ID

role_id

varchar(50)

fk:t_role->role_id

权限

permission

varchar(255)

not null

基础角色ID

role_base_id

varchar(50)

fk:t_role_base->role_id

创建时间

create_time

timestamp

not null

8、用户组表(t_user_group,可选)

字段名称

字段

类型

备注

组ID

id

int(11)

自增

组名称

group_name

varchar(50)

not null

组描述

group_desc

varchar(255)

not null

创建时间

create_time

timestamp

not null

9、组角色表(t_user_group_role,可选)

字段名称

字段

类型

备注

记录ID

id

int(11)

自增

组ID

role_id

varchar(50)

not null

角色ID

role_id

varchar(255)

not null

创建时间

create_time

timestamp

not null

10、用户权限表(t_user_permission,可选)

字段名称

字段

类型

备注

记录ID

id

int(11)

自增

用户ID

role_id

varchar(50)

not null

权限

permission

varchar(255)

not null

创建时间

create_time

timestamp

not null

四、角色及权限点设计


权限控制的整个过程可以描述为:“谁”对“什么”进行什么”操作",从而,引出我们需要做的工作有:角色设计,资源定义,以及对资源的操作定义。再详细描述下,鉴权就是根据用户身份(角色)获得其对那些资源,可以进行什么操作,其中对资源的操作做为一个独立的权限体。

4.1、定义系统中的用户角色

一般是采用“通用角色+实例角色”的模式,实例角色可继承通用角色,从而拥有通用角色的权限。

常见的通用角色定义:ADMIN、MANAGER、MEMBER、GUEST 常见角色权限分配:1)SUPER_ADMIN,具有系统一切权限 1)产品ADMIN,具有当前产品所有权限;2)产品MANAGER,不具备删除权限,可修改,添加成员等 3)产品MEMEBER,可查看,修改信息,不可添加成员;4)产品GUEST,只可查看

实例角色:实例角色一般可以这样定义:“资源点+通用角色+资源ID”

注:其中资源可能是产品,可能是页面,也可能是菜单等

4.2、定义系统中的资源以及操作

一般系统中的最常见资源就是:产品(P) 一般对资源的主要操作包括:增加(CREATE)、删除(DELETE)、修改(EDIT)、查看(VIEW)

当然,系统中的资源肯定不止产品,同时产品这个粒度有些太粗,还可以更细化控制,当然一切都根据实际业务需求情况定义相应的资源点和操作。

4.3、权限体策略

权限控制策略采用五元组,如下:

资源:操作:实例:BU:密级

其中,资源可以是人,也可以是一个需求,一个文件等等;操作为对资源的操作;实例极为产品ID或者用户ID;BU,密级用于控制不同BU,不同保密模块的可见性

五、身份认证加权限管理实施


JAVA可以采用SHIRO框架,一个最简单的一个Shiro应用:1)应用代码通过subject授权,而subject又委托给SecurityManager;2)我们需要给Shiro的security注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断;

Shiro的最主要要做的工作其实就是两个:身份认证和权限校验,下面分别进行介绍。

5.1、身份认证

通过前面的文章分析,我们知道自定义身份校验校验逻辑,只需要继承AuthenticatingRealm即可,Override如下接口进行身份认证:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){}

上面这个接口,参数AuthenticationToken,它可以自定义子类实现,框架提供的有:UsernamePasswordToken,CasToken。

如果是采用用户名密码方式登陆,那么就构造一个UsernamePasswordToken,然后取数据查询用户名密码是否有效;如果是采用的CAS方式登陆,那么就通过ticket构造一个CasToken,然后与CAS服务交互验证ticket是否有效。如果验证通过会生成一个AuthenticationInfo。此时身份认证完成。

最简单的用户密码登陆身份校验代码 CAS方式验证首先得有CAS系统,这里就不说明CAS方式怎么验证了,说一下怎么用用户密码登陆进行身份校验,认证流程都一样

自定义一个AuthenticatingRealm:

public class MyRealm1 implements AuthenticatingRealm {@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String)token.getPrincipal();  //得到用户名String password = new String((char[])token.getCredentials()); //得到密码//取数据库中看用户名是否有效//checkUserInfo();//如果身份认证验证成功,返回一个AuthenticationInfo实现;return new SimpleAuthenticationInfo(username, password, getName());}
}

5.2、权限校验

权限校验主要要做的事情就是完成"从数据库中查出用户所拥有的所有权限是否包含当前待校验的权限"这么一个判断过程,因此主要要做的就是:1)从数据库中查出用户所拥有的所有权限;2)解析权限,看看是否包含待校验的权限。

1、第一步:获取用户权限信息 获取用户权限信息这个过程是在Realm中完成的,继承AuthorizingRealm,然后Override如下两个接口获取用户的权限信息:

//获取用户身份信息,Authorization前需要先获取用户身份信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){}//获取用户权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
}

权限信息查询过程一般为:1)从数据库中读区用户自身所配权限;2)从数据库中读取用户角色所用拥有的权限(角色包含实例角色和BASE角色) 3)用户最终的权限:用户自身权限+用户角色权限

2、第二步:权限校验

1)如果通过角色校验,则调用hasRole,与传入的角色比较即可;

2)如果通过权限体校验,则调用isPermitted(...),与传入的权限进行比较即可。

shiro内部逻辑如下:首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;接着调用Permission.implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73213.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

K8s调度器Scheduler

当创建k8s pod的时候调度器会决定pod在哪个node上被创建且运行,调度器给apiserver发出了一个创建pod的api请求,apiserver首先将pod的基本信息保存在etcd,apiserver又会把这些信息给到每个node上的kubelet进程,kubelet一直在监听这…

【python】anaconda 管理 python 环境

anaconda 管理虚拟环境anaconda 简介python 虚拟环境的安装查看当前 anaconda中所有的虚拟环境创建新的虚拟环境激活所创建的虚拟环境删除指定的虚拟环境退出当前虚拟环境查看当前虚拟环境中所有安装的库安装常用包pycharmpycharm 下环境配置pycharm 使用anaconda 简介 anacon…

springBoot使用ShardingJDBC实现分表

ShardingSphere的介绍 ShardingSphere是一款起源于当当网内部的应用框架。2015年在当当网内部诞 生,最初就叫ShardingJDBC。2016年的时候,由其中一个主要的开发人员张亮, 带入到京东数科,组件团队继续开发。在国内历经了当当网、…

LeetCode 622.设计循环队列

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里&a…

注意啦!如何通过广告吸引客户直接下单?

2023年跨境电商越来越突出,据业内相关人士称,在未来几年与跨境电商相关的政策仍会继续倾斜甚至加大力度,因此各行各业都响应政策,在新政策落实之前致力于平台的转型升级,做新时代创新型的高质量发展,其实细…

Linux下的命令执行绕过技巧合集(渗透测试专用)

一、通配符* 代表『0个到无穷多个』任意字符,包括空字符? 代表『一定有一个』任意字符[ ] 同样代表『一定有一个在括号内』的字符(非任意字符)。例如 [abcd] 代表『一定有一个字符, 可能是 a, b, c, d 这四个任何一个』[ - ]若有减号在中括号内时&#…

(考研湖科大教书匠计算机网络)第六章应用层-第五节:文件传送协议FTP

获取pdf:密码7281专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一:概述二:工作原理三:控制连接与数据连接本节对应视频如下 【计算机网络微课堂(有字幕无背景音乐版)】…

求职3个月,简历大多都石沉大海,一听是手工测试都纷纷摇头....太难了

距离被上家公司裁员已经过去了3个月了,3个月的求职经历真的让我痛不欲生,我也从中理解感叹到了很多,想写出来,告诫跟我一样的经历的人。 我今年26岁,大学是一所普通的大专,学的是机电专业,如何…

Python自动化测试框架封装和调用

封装与调用函数与参数化前言 面实现了参数的关联,那种只是记流水账的完成功能,不便于维护,也没什么可读性,接下来这篇可以把每一个动作写成一个函数,这样更方便了。参数化的思维只需记住一点:不要写死 登录…

类与对象(this 关键字、构造器)

目录一、面向对象二、类与对象三、对象内存图四、成员变量和局部变量区别五、this关键字六、构造器/构造方法一、面向对象 一种编程思想:也就是说我们要以何种思路,解决问题,以何种形式组织代码 当解决一个问题的时候,面向对象会把事物抽象成…

分享app的测试技巧

前言 今天笔者想和大家来唠唠app测试,现在的app有非常的多,这些app都是需要经过测试之后才能发布到应用市场中,app已经成为了我们日常生活中不可或缺的一部分了,但它的功能必须强大,才能受到消费者的重视,…

已解决from cryptography.hazmat.backends import default_backend导包错误

已解决Python连接FTPS抛出异常:CryptographyDeprecationWarning: Python 3.6 is no longer supported by the Python core team. Therefore, support for it is deprecated in cryptography. The next release of cryptography (40.0) will be the last to support …

pyaudio声卡信息中hostApi是什么意思?

hostApi是声卡驱动协议,声卡驱动模式,有如下很多类。下面的类型是网上找的PortAudio的类,不不确定是不是python的。typedef enum PaHostApiTypeId{paInDevelopment0, /* use while developing support for a new host API */paDirectSound1,p…

深度学习之“制作自定义数据”--torch.utils.data.DataLoader重写构造方法。

深度学习之“制作自定义数据”–torch.utils.data.DataLoader重写构造方法。 前言: ​ 本文讲述重写torch.utils.data.DataLoader类的构造方法,对自定义图片制作类似MNIST数据集格式(image, label),用于自己的Pytorc…

推荐系统从入门到入门(3)——基于MapReuduce与Spark的分布式推荐系统构建

本系列博客总结了不同框架、不同算法、不同界面的推荐系统,完整阅读需要大量时间(又臭又长),建议根据目录选择需要的内容查看,欢迎讨论与指出问题。 目录 系列文章梗概 系列文章目录 三、MapReduce 1.MapReduce详…

【视频】海康摄像头、NVR网络协议简介

1、软硬件整体架构 2、涉及的网络协议 3、协议简介 3.1 海康私有协议 设备发现SADP:进行设备的发现、激活、修改网络参数、忘记密码等; SDK:4200、系统平台的接入前端设备,协议不对外开放,但对外提供接口库; ISAPI:Intelligent Security API(智能安全API),基于HTTP传输…

2023新的一年软件测试还值得学习吗?

最近因为疫情等各种原因,大厂裁员,失业等等频频受到关注。不解释,确实存在,各行各业都很难,但是,说软件测试行业不吃香,我还真不认同(不是为培训机构说好话,大环境不好&a…

Odoo丨Odoo框架源码研读三:异常处理与定制化开发

Odoo丨Odoo框架源码研读三:异常处理与定制化开发 Odoo源码研读的第三期内容:异常处理与定制化开发。 *异常处理* Odoo中的Exception是对Python内置异常做了继承和封装,设定了自己核心的几个Exception。 而对异常的处理和Python内置异常的…

Spring 之bean的生命周期

文章目录IOCBean的生命周期运行结果实例演示实体类实例化前后置代码初始化的前后置代码application.xml总结今天我们来聊一下Spring Bean的生命周期,这是一个非常重要的问题,Spring Bean的生命周期也是比较复杂的。IOC IOC,控制反转概念需要…

Flutter+【三棵树】

定义 在Flutter中和Widgets一起协同工作的还有另外两个伙伴:Elements和RenderObjects;由于它们都是有着树形结构,所以经常会称它们为三棵树。 这三棵树分别是:Widget、Element、RenderObject Widget树:寄存烘托内容…