- 作者:老汪软件技巧
- 发表时间:2024-12-31 00:11
- 浏览量:
前言
最近老板让我实现一个可以统一全部业务的权限系统,说实话,一开始我自信满满。但是不断深入研究后,心里就开始慌了,尤其是带入一个业务系统的实际需求,以及构建一个可拓展的一致且准确的授权系统时,就会发现这会有多么的复杂!但是毕竟东西都是人做的,慢慢搞总会搞出来的。我阅读了大量的文章,找了各种解决方案,在不断的研究以及试错之中不断的总结。本篇是我个人总结的,关于权限系统最重要的两个概念——权限模型和策略,并且还会着重介绍权限模型的新星——ReBAC。
权限模型
一提权限模型,接触过权限的开发者都不会陌生,RBAC,ABAC肯定都有所了解,什么ACL,MAC的相关概念肯定也都看过,本文主要是探讨几种能够实现通用的权限模型RBAC,ABAC,ReBAC。
三种权限模型介绍
目前可供我们选择的主要就是三种权限模型:
但是详细的介绍这三种模型并不是本文的重点如果需要查看相关的概念可以看一下这些文章:
三种权限模型对比的结论如下:
RBAC:传统的基于角色的权限控制
优点:
缺点:
ABAC:基于属性的权限控制
优点:
缺点:
ReBAC:基于关系的权限控制
优点:
缺点:
我的选择
目前我们需要的是一个通用的权限系统,需要精细化到具体的资源。那么首先RBAC就被排除在外了,因为传统的RBAC会产生角色爆炸问题,并且在处理复杂的权限传递时,其授权将会非常的复杂。
ABAC因为其实践的复杂度,较难的统一策略,较大的维护成本(各个业务需要变更属性时)也被我们排除在外,并且其在复杂场景下的性能问题,也是一个需要考虑的原因。相关内容可以看我上一篇文章《》
所以我们选择一个比较新的概念-ReBAC!
ReBAC是基于关系的,在实际业务中非常适合权限的传递(下文会举例子),并且其模型的定义也会比类似于ABAC的策略的学习成本低(下文也会举例子)。尤其是目前我们在做一个协同的APP,这将很好的帮我们管理如组织架构,文件,表单等内容的管理。
接下来,我们将引入第二个很重要的概念——策略或者说模型
策略 or 模型什么是策略?
我对于策略的定义是:定义访问控制规则和资源关系的结构
比如在实际业务中,如果权限由各个业务分散处理,那么业务系统就要为之定义一个if else语句,如下:
if(Object.permissionList.contain(user.permission)||
Object.permissionRole.contain(user.role)){
业务实际处理逻辑
} else{
无权访问
}
其实这个判断语句不太严格符合我对策略的定义,它只定义了访问控制规则。
它描述了user之间的关系:用户可以通过用户组(Role) 进行授权,从而获得权限。
但是这里并没有定义资源关系,实际的业务场景比这个要复杂得多。比如一个授权逻辑下,一个人对于一个文件夹有owner权限,我们将会自动拥有此文件夹下的所有文件owner的权限。类似这样的权限传递,除非我们直接进行一个赋予权限,类似于ACL表,否则会比较难直接处理。
如何定义一个策略|模型
这个问题可以转化为,如何定义一个资源的控制规则和资源关系。
策略要求:
支持复杂业务支持权限和资源分离,不维护业务数据。定义尽量简单调试要简单。策略管理要简单(权限系统应该维护所有业务系统的策略)。
大家可以立马想到:基于关系的权限模型可以很好的描述资源关系的结构以及权限传递。但是ReBAC中并没有一个策略的概念,但与之对应的,在ReBAC中的定义资源,用户组,关系的名词称之为模型。所以,我将ReBAC中的模型也理解为一种策略。
模型
上文提到,我将模型定义为一种策略,是因为他描述了访问控制规则和资源关系结构,大家可能看不懂这句话。下文,我将直接代入OpenFGA中模型定义的例子,让大家更容易理解ReBAC中模型的概念。
关于OpenFGA
OpenFGA是现在比较流行的ReBAC实践,受到Google Zanzibar的启发。它将强大的基于关系的访问控制 (ReBAC)和基于属性的访问控制 (ABAC)概念与特定于领域的语言相结合。
OpenFGA的一些概念
在开始下文的举例之前,你应该了解一些OpenFGA中的概念,具体的概念可以看一下官网的一些概念,或者查看我的翻译的文章,我的文章中也加入了一些个人理解以及示例。
以文档为例,我们定义一个模型
下面的例子,我是直接扒的OpenFGA官网的示例,加以解释,应该能比较浅显易懂的介绍模型如何实现!
官网示例的链接:openfga.dev/docs/modeli…
下面直接开始!
这个我认为比较浅显易懂,我们定义了一个资源document,一个用户类型的的 user。
user可以对document有owner,writer,commenter,viewer权限。其实也可以细分粒度为can_write, can_view, can_comment等,实际关系定义可以结合业务需要。
有了这样的模型,我们就可以直接授权某人对于某资源拥有对应的权限。
这里就是描述,user类型的用户beth对于document类型的id为2021-budget的文档有可评论的权限。这种授权基本就实现了用户对于资源的访问控制,类似于ACL表。
但是发现此模型有一个问题,作为文章的拥有者,那他应该也是文章的写作,评论,和查看者。于是我们需要修改模型:
但是由于我直接分享到个人,对于权限更好的管理,我们应该有一个用户组的概念,我们将权限分给用户组,用户组下的人就会自动用于对应的权限。
这里的domain可以理解为一个用户组,或者用户集。我们就可以很方便的将权限授予用户组,此用户组下成员自动拥有对应权限,类似于RBAC
基于这种授权,用户组xyz下的成员anne,beth,charles通过用户组的权限传播,自动拥有了viewer的权限。
假设我们还有一个需求,一个人对于某文档有了权限,那么就自动对于此文档下面的子文档有了权限,可以修改为如下。
首先我们需要加入一个文档的父子关系:
然后我们要做一个权限传播
我们的文章可以是公开的怎么办?它也是可以支持通配符,如user:*,user:* 很容易理解,就是user类型的所有人.
甚至可以通过关系实现黑名单和ip访问限制(IP访问限制不一定要像我这样,可以使用条件元组)
model
schema 1.1
type user
type document
relations
define owner: [user, domain#member] or owner from parent
define writer: [user, domain#member] or owner or writer from parent
define commenter: [user, domain#member] or writer or commenter from parent
define viewer: [user, user:*, domain#member] or commenter or viewer from parent
define parent: [document]
define approved_ip_range: [ip-address-range] // 新增:定义允许访问的IP范围
define blackList [user] //黑名单
type domain
relations
define member: [user]
type ip-address-range
relations
define approved_range: [user] // 新增:定义哪些用户可以通过特定IP访问资源
通过上述的例子,我们就实现了用户层级(用户,和用户组),资源层级(资源的父子关系),权限传播(不同权限的传播(write对view),父子资源的权限传播,用户之间的权限传播)。甚至于可以通过关系,你能够实现不同类型的资源之间的权限传递。比如一个人对某文档有了权限,就自动拥有文档对应的任务的权限。
其实本质上这种资源上的权限传递,反过来也是对于我们资源鉴权的多层判断。如果根据上面这个例子,我们需要判断
用户是否有资源的直接权限用户是否有某资源的间接权限(拥有writer,那么判断是否拥有viewer就是true)用户是否在有权限的某用户组中资源是否公开。
我们无需做这种判断,因为我们已经给予OpenFGA的模型,它会基于图的思想自动推导出是否存在用户到资源的路径。我们要做的就是给一个起始点user:userId,终点:document:documentId。
上文中我提到了ABAC的典型实践OPA,那么我们通过OPA将如何去定义一个类似的策略,实现同样的一个文档的鉴权功能。
以下是我用ChatGpt试图实现和这个功能差不多的OPA策略,以下是ChatGpt生成的内容:
package example
# 用户类型
type_user = "user"
# 文档类型
type_document = "document"
# 域类型
type_domain = "domain"
# IP地址范围类型
type_ip_range = "ip-address-range"
# 父文档-子文档关系
document[parent] = document {
document[parent] = input.document_parent
}
# 用户与域成员关系
domain[member] = user {
domain[member] = input.domain_member
}
#输入数据
document[owner] = user {
input.document_owner == user
}
document[writer] = user {
input.document_writer == user
}
document[commenter] = user {
input.document_commenter == user
}
document[viewer] = user {
input.document_viewer == user
}
# 权限传递:如果用户有写权限,则自动获得评论和查看权限
allow_permission_with_transitive_rights(user, doc, perm) {
# 如果用户有写权限,则自动获得评论和查看权限
perm == "writer"
allow_permission(user, doc, "writer")
allow_permission(user, doc, "commenter")
allow_permission(user, doc, "viewer")
}
# 权限检查,递归父文档的权限,考虑用户和域的关系
allow_permission(user, doc, perm) {
# 直接关系
document[perm][user] # 用户是否直接有该权限
# 如果没有直接权限,递归查找父文档的权限
not document[perm][user]
parent_doc := document[parent]
allow_permission(user, parent_doc, perm) # 递归父文档权限
}
# 检查用户是否属于某个域
user_belongs_to_domain(user, domain) {
domain[member][user]
}
# 权限传递的详细实现
allow_permission_for_user_in_domain(user, doc, perm) {
# 检查用户是否属于与文档相关的域
domain_membership := domain[member][user]
domain_membership
# 检查文档的权限
allow_permission(user, doc, perm)
# 权限传递
allow_permission_with_transitive_rights(user, doc, perm)
}
# IP访问控制,检查IP是否在允许的范围内
allow_ip_access(user, ip) {
ip_range := input.ip_range
ip_range[approved_range][user]
}
# 黑名单检查,判断用户是否在黑名单中
blacklist(user) {
input.blacklist[user]
}
# 综合权限检查
allow_access(user, doc, perm, ip) {
# 权限检查,包括父文档继承
allow_permission_for_user_in_domain(user, doc, perm)
# 黑名单检查
not blacklist(user)
# IP访问权限检查
allow_ip_access(user, ip)
}
其实我没有验证过这个策略,但是这个策略一看就不能实现我们上述的模型,即使我通过ChatGpt生成过多次,也无法提供一个相应功能的策略。因为它没有考虑到input的数据应该包含用户的用户组数据,资源的父层级数据,所以根本就不可能实现通过这些数据来判断。
如果读者能够给出一种相对应的OPA实现方式,我将感激不尽。
通过OpenFGA的模型与OPA的策略对比:
OpenFGA的层级关系直接,而OPA的层级关系以及权限传递复杂。OpenFGA对于业务人员更加的友好,低学习成本。OpenFGA利于调试(可以逐层使用listObjects),OPA基于Rego,递归权限时出问题不利于调试。结论
本文通过三种权限的对比,结合它们实际的应用框架,找到了适合我们权限系统的权限模型-ReBAC。我们可以通过ReBAC实现更加复杂的权限管理,并且提供给业务开发人员较低的学习成本。
后续也会探讨如何去落地ReBAC,以及OpenFGA相关原理的内容。