对于此项目的权限管理。在前面阅读中间件源码时候提到了这个。经过这几周的研究,逐渐理解了此项目的权限管理系统。
1. 基础知识
在讲解本项目的权限管理系统前,可以先了解下业界主要有的方案:ACL,DAC,MAC,RBAC,ABAC
1.1 ACL,DAC,MAC
ACL是最简单的权限控制模型,里边主要是记录 某个用户
可以对 某个资源
做 某个操作
,如
Tony /manager/list GET
这个意思为名叫Tony
的用户可以对路径为/manager/list
的api执行GET
操作。 这里可以将用户称之为subject
,资源称之为object
,动作称之为action
对于DAC模型,是基于ACL的。与ACL的区别是subject
可以将自己拥有的权限赋予给其他subject。如上方的例子,Tony可以将这个接口的权限赋予给其他用户。
MAC模型的话,也是基于ACL的。与ACL区别是安全性更高,是双向授权。在指定subject 可以对object 执行 action操作的同时,必须也要指定对应的object可以被subject执行action操作。用上方例子来说就是这样的
Tony /manager/list GET # tony可以对/manager/list接口执行get请求
/manager/list Tony GET # /manager/list接口的get方法可以被tony执行
只有同时满足了这两点,这个授权策略才算成功。
1.2 RBAC,ABAC
RBAC权限模型应该是当前最普及的,本项目使用的就是RBAC策略 (ps: 我感觉是伪RBAC,原因在后边解释)。
这个与上方提到的ACL策略相比,新增了个一个 role(角色)的概念。在ACL中的subject一般来说都是用户。在RBAC中subject一般指的是role。比如说我们有个工作平台,平台上有很多个方向的事务处理。 对于每个方向可能有对应的方向管理员,而对于整个平台也可能有整个平台的管理员。而且这里的“管理员”也不只是一个人。我们只需要指定某个角色有什么权限即可。然后再指定哪个用户是哪个角色。
policy:
Manage /manager/list GET
role:
tony Manage
marry Manage
如上方,就代表tony和marry都有对/manager/list
接口的 GET
执行权限。
对于ABAC模型就更为复杂,相比于RBAC又增加了属性的概念,这样的话粒度比RBAC更细。如某个服务器的8000-9000端口可以被部门为xx部的Manage角色执行xx操作。具体可以参考其他博文详细了解。
2. gin-vue-admin的权限系统
此项目中使用的权限管理模型是上方介绍的RBAC,但上方也说过,此项目的RBAC在我理解看来是伪RBAC。具体可以一步一步来看。
2.1 项目代码
func CasbinHandler() gin.HandlerFunc {
return func(c *gin.Context) {
waitUse, _ := utils.GetClaims(c)
// 获取请求的PATH
obj := c.Request.URL.Path
// 获取请求方法
act := c.Request.Method
// 获取用户的角色
sub := waitUse.AuthorityId
e := casbinService.Casbin()
// 判断策略中是否存在
success, _ := e.Enforce(sub, obj, act)
if global.GVA_CONFIG.System.Env == "develop" || success {
c.Next()
} else {
response.FailWithDetailed(gin.H{}, "权限不足", c)
c.Abort()
return
}
}
}
这个是项目中casbin中间件的代码。在这个中间中主要的运行流程为获取到请求的PATH,Method和用户角色。 casbin拿到这三个数据后去数据库中查询是否满足权限要求,若满足则执行c.Next()下一个中间件,若不满足则直接返回并提示权限不足。
总体流程看起来并不复杂,可以点进去casbinService.Casbin() 中查看如何使用casbin的。
这里主要理解的是中间的一大串字符串
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
request_definition
表示访问请求,下面的r = sub, obj, act
分别表示用户,资源,动作
policy_definition
表示策略,这里的p = sub, obj, act
表示的意思与上方的r的一致。
然后先跳过中间两个,直接看最下面的mathers
:
m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
,从文本描述大概可知。当请求的用户(sub)和行为(act)与策略中的一致,并且url两者路径相匹配(keyMatch2)则表示验证通过。注:对于keyMatch2()
官方是如下描述的:
所以只要是访问p.obj所表示url的资源,均可以通过这个keyMatch2的验证。
现在回过头来看上面的两个,对于role_definition
,表示角色的定义,即RBAC权限模型里的role
,后边的g = _, _
表示角色继承关系的前项和后项,即前项继承后项角色的权限。这里看起来会有点生硬,我直接拿官网给的例子:
p, data2_admin, data2, read
g, alice, data2_admin
第1行代表data2_admin可以使用read操作访问data2
第2行代表alice是data2_admin角色中的一员,则alice就可以使用read操作访问data2。
这里的规则语法,我描述的可能简单了些,具体可参考 官方文档。
对于具体的的规则配置,可查看数据库中的casbin_rule
表
这里可以以第一行来举例,意思为 888 可以给 /base/login
发送POST
请求。在用户使用过程中,如果888
给这个接口发送POST请求时,就会触发casbin中间件来验证是否有这个权限
2.2 伪RBAC?
拿上方的例子来说,这里的 888
并不是实际的用户名,在项目中可以称之为AuthorityId
,可以理解为角色id。每个用户可以有多个角色。对于我认为的理想的RBAC应该是这样的,假设有一个场景:
用户小明拥有两个角色:管理员、普通用户, 有个API为user/:resource
来获取某个用户的详细信息,方法为GET
那么作为小明这个用户在输入的入参应该是 userId
、Api
、Type
RBAC内部会通过对应的userId来找到小明对应的角色,然后查看拥有的角色是否有对应的权限。
而该项目的RBAC实际上是这样的:小明在前端主动选择使用哪个角色,然后传给后端的入参是AuthorityId
、Api
、Type
,假如小明自己选择了 普通用户角色 访问user/:resource
这个接口就会提示没有权限。
前端通过点击切换角色后,会请求后端/user/setUserAuthority
的api
然后后端收到请求后,会去数据库中把当前用户使用的AuthorityId更换为用户选择的id,并重新生成token(这是因为token中包含了AuthorityId信息,所以需要重新生成),详情请看下方代码
从我的理解来看,对于RBAC里,发来的请求中sub应该为用户,而此项目的sub直接就为角色,这样就跳过了RBAC里的用户转角色的步骤。所以,如果只从Casbin中的记录来看,该项目使用的权限系统倒更像是ACL,只是基于ACL做了比较多的前置操作,但核心还是ACL。
当然,这样做肯定有这样做的原因。刚才我们是从纯后端的角度来看权限管理,但如果我们从前端角度来看,就会发现这样做的好处了,这个留给以后阅读前端代码时候再说。