内容简介:mongodb 是一款基于 文档 结构的 nosql 数据库,目前社区比较火,在文档的存储灵活性有着天然的优势(同集合可以任何形式的行数据,当然我们不会这样去烂用这种灵活性 :) ),且有不俗的性能表现,以及副本集的高可用!此款权限粒度存在范围与个体的概念,可以理解为 功能性权限 和 数据归属性权限。数据权限是功能权限的再次细分,作为子集。下面,我会一点点剖析,下面的案例会给出所有表结构,和关键操作的 sql 语句(基于 golang 语言的 (mgo mongodb driver))和设计图。当然这可能
基于 mongodb 设计灵活后台管理权限
mongodb 是一款基于 文档 结构的 nosql 数据库,目前社区比较火,在文档的存储灵活性有着天然的优势(同集合可以任何形式的行数据,当然我们不会这样去烂用这种灵活性 :) ),且有不俗的性能表现,以及副本集的高可用!
此款权限粒度存在范围与个体的概念,可以理解为 功能性权限 和 数据归属性权限。数据权限是功能权限的再次细分,作为子集。下面,我会一点点剖析,下面的案例会给出所有表结构,和关键操作的 sql 语句(基于 golang 语言的 (mgo mongodb driver))和设计图。当然这可能并不是最佳实践,只是我自己设计的一点心得,已便解决实际问题。
背景
之前在做一款早期 serverless 架构的雏形(现在只能算模版系统 :) )后台管理系统的时候,公司的人员结构很稳定,又为了快速内部上线提供使用,我们想到一款最简单的基于部门实现泛权限的功能权限管理,意思就是,这个部门下面的组拥有这个组创建的所有查看权限,这个组拥有的功能权限由角色定义,如果为资深开发者角色,那么管理员会分配 模版编辑权限,模版清缓存/发布等一套可以从代码web ide 上编辑到外网访问发布的整个流程权限,初级可能只有编辑权限等。
但是我们很快发现问题,公司虽然有统一的权限系统,但是控制粒度太粗,所以开始我们是把人员的权限放入到自己的库里维护,时隔几月,随着公司的大举扩张,人员结构,部门等发生翻天覆地的变化,人事变更意味着之前基于部门设计的权限变成了脏数据,部门之间的数据没法自动实现访问,只能通过我们简单的数据权限操作机制,让某一人拥有多个部门的权限,这样处理因为不方便的原因可能存在越权处理,因为在 A 部门你拥有发布权限, 在 B 部门我只想让你有查询功能等,这样有一段时间我们就陷入了不断为人调整权限的售后工作!而且在跨部门处理问题都会面临不方便。
结构图
思考
当时我们面临一个元素种类比较多的后台系统,且权限组合种类比较多,那么怎样能够设计一款能够自由组合,能够自驱玩转权限,权限的下方不再由单人控制,以及在做工作交接的时候权限与资源能够一键交接。所以列了如下几点特性。
- 权限控制下放,开发人员不再费力维护权限。
- 权限有角色管理控制,有数据权限的细分概念。
- 权限以及资源能够一键转让。
- 权限基于人为个体,细分到,某个人下的某条数据。
- 权限可以临时组成,不同开发人员,可以同时拥有一套或多套权限,方便跨部门协作。
- 使用要简单,与用户基本信息隔离
- 性能要出众。可以做成 web 服务 / sdk
实践
接下来就是进行表结构的梳理,所有结构都会给出 最简洁关键字段及注解
要想细分到数据权限,那么人/组必须要有一个元素与资源进行绑定,在此我们用 signkey 作为一种签名,某个人拥有这个签名就有对这个资源有一定的操作权限。一定要有中间签名的纽带来建立联系,不然数据量的存储,转让,变动都会出现问题。 如果说把资源属于哪些人来处理的话,可能一个字段就有很多工号来识别。
所以我们权限表里面的 user 信息表这样设计
type UserDoc struct { Name string // 姓名 UserId string // 工号 唯一标识 SignKey map[string]string // 签名 key是签名 24字符id mongodb _id (可换成任意 唯一 字符串),value是签名的描述。 }
可以看到一个人自己可以创建多个私有 签名
GroupName+UserId 作联合唯一索引
那么我们的角色权限是怎么设计的呢?一个角色拥有多种职能,我们把每一种职能看作一个接口实现了不同的方法,那么角色权限,就是由一个或多个接口实现的组合,接口实现又体现在 url + method 上面,然而问题是,有些接口涉及到操作数据,对数据有写操作,有的接口,只是单纯的某一次事件,不涉及到任何数据查询和变更。那么在这里就会划分此接口是否开启对数据做认证。通过不同的接口实现组合,这样就拥有了角色权限的维护。
type RoleDoc struct { RoleName string // 角色名称 Desc string // 角色描述 IsDefault bool // 是否为默认角色,就是用户登陆进来自带的角色权限 UserIds []string // 角色下面有哪些用户, 这里是一个优化点,可以使用外键表关联。 视系统大小可以调整 PathsDataVerify map[string]bool // 该接口是否开启数据验证权限,用于 sql 快速查询数据 PathMethods []PathMethod // 该角色有哪些功能即 API 的组合, 详细数据,是一份冗余数据,方便配置时使用 通过方法把详细数据转化为快速查询数据 Typ int // 角色类型,用于做特权处理, 例如 超级管理员角色,无需验证权限 则 type = 0 } // restful API 风格设计 一个 path 有多个 method, // method 使用 int 代替 GET=1 POST=2 PUT=4 DELETE=8 HEAD=16... 可以用到 mongodb 位运算 // 如果一个 path 的 get post 方法都赋予了这个角色, 则 MethodInt=3 type PathMethod struct { Path string // 请求路径 MethodInt int // 方法的 int 和 9 代表 GET 和 DELETE 方法 }
上述表结构 PathsDataVerify key="1_/template" value=true 因为 1 表示 GET 所以这种表示描述就为 模版的查询方法,需要开启数据认证权限
roleName+Typ 作联合唯一索引
有了 Path 与角色的关联,就自然会有 Path 的详情,以方便管理,作为组成角色的权限的源,同时可以设置该接口是否开启数据验证权限。
type RouterDoc struct { Path string // 请求路径 /template Desc string // 路径实现功能的大类, 例如 模块的 CRUD MethodMap map[string]MethoedDetail // key 方法 具体表现 "1" "2" .. } type MethoedDetail struct { DataVerify bool // 该方法是否开启 数据验证 Desc string // 模版的删除 则 MethodMap key = 8 }
通过上面的简单表述,就可以建立 url + method 等于方法的实现,可以很方便的进行权限管理
Path 作唯一索引
那么我们的数据权限验证又是怎么实现的呢。它与角色权限又是什么样的关系。与人又怎么绑定的,注解如下
type SignDoc struct { SignKey string // 签名, 某人的私有签名 + 用户唯一标识 可以理解成 这个人拥有这个签名的哪些数据权限 CreateUserId string // 签名的创建者 UserId string // 这个成员也拥有这个签名的一部分/全部权限, 相当于创建者把自己的签名共享出,用于多人使用 RouterMap map[string]int // key=Path value=Method $bitsAllSet 计算 {"/template":14} 表述 这个人拥有此签名下面的模版数据 增/改/删的权限 }
SignKey+UserId 作联合唯一索引
这样创建者可分配的权限为自己的角色权限的子集,可以把一个私有key 暴露成公共的授权个他人,那么他人就对此签名下面所有的资源,都有相应你配置的权限,RouterMap 代表他拥有哪些权限,这样你就可以把自己的签名,分给不同的人,不同的人,拥有对此签名不同的权限。完全实现了,自己的数据自己做主管理。用于多种协调工作的场景。比如临时有一个修改活动逻辑的任务,有3个人做,那么他们组负责人可以创建一个公共的 key ,赋予这三个人,用于此次任务的所有权限操作。
所有的表结构都注解了,那么它们是怎么关联协作的,下面我们将从一个用户的角度使用权限系统里面所涉及的 sql 语句做简单解析,以便熟悉整个请求的流转。
配置权限流程
初始化路由 Method 描述以及是否开启数据权限 ---> 创建角色 ---> 把才创建好的路由与创建的角色进行绑定 ---> 为角色添加用户 ---> 角色创建自己的签名 ---> 每一次请求携带上签名 ---> 创建资源的时候携带上此签名与资源绑定
查看个人自己拥有什么权限
// userid 查询 RoleDoc 然后把所有返回的 PathMethods 做聚合 // 通过 PathMethods 也可以结合 RouterDoc 查询具体的数据验证权限
查看别人授予的权限
// userid 查询 SignDoc 然后对返回,做聚合即可
在权限的配置和管理上面,没有任何复杂的 sql ,当然在实际项目当中有很多的 sql 组合来满足不同的配置方式。总体下来是很方便的。
复杂分配举例,以对资源的 CRUD 为例
- u1 u2 u3, u1 拥有 u2 签名 u2-s-1 下的 ceph资源读取权限, u2 拥有 u1-s-3 下的 template CRU 权限,u3-s-1 下的ceph CU 权限。 u3拥有 u1-s-3 所有数据的 R 权限
数据储存表现
// RoleDoc [ {"userId":"u1", "signKey":{"u1-s-3":"ceph kv manager"}}, {"userId":"u2", "signKey":{"u2-s-1":"template manager"}}, {"userId":"u3", "signKey":{"u3-s-1":"template manager"}} ]
// RouterDoc [ {"path":"/ceph","methodMap":{ "1":{"dataVerify":true,"desc":"query ceph value"}, "2":{"dataVerify":true,"desc":"create ceph value"}, "4":{"dataVerify":true,"desc":"update ceph value"}, "8":{"dataVerify":true,"desc":"delete ceph value"}, } }, {"path":"/template","methodMap":{ "1":{"dataVerify":true,"desc":"query template value"}, "2":{"dataVerify":true,"desc":"create template value"}, "4":{"dataVerify":true,"desc":"update template value"}, "8":{"dataVerify":true,"desc":"delete template value"}, } } ]
// RoleDoc 此角色没有删除权限 [ {"reoleName":"ceph&template manager","userIds":["u1","u2","u3"], "pathMethods":[ {"path":"/ceph","methodInt":7}, {"path":"/template","methodInt":7} ]} ]
// SignDoc 签名数据权限关联 [ {"signKey":"u1-s-3","createUserId":"u1","userId":"u2","routerMap":{"/template":7}}, {"signKey":"u3-s-1","createUserId":"u3","userId":"u2","routerMap":{"/ceph":6}}, {"signKey":"u2-s-1","createUserId":"u2","userId":"u1","routerMap":{"/ceph":1}}, {"signKey":"u1-s-3","createUserId":"u1","userId":"u3","routerMap":{"/template":1,"/ceph":1}} ]
当数据量特大的嵌套文档,都可以外键单独行存储。
验证权限
用户(统一单点)登陆后 ---> 判断是否为初始用户(默认角色) ---> 返回角色类型,拥有哪些权限(前端可以根据拥有的权限对 ui 进行渲染控制)
用户请求 携带 userid + signkey 请求获取 path + method (路由如果支持 动态路由, 用 httprouter 解析成后台配置的路由)
使用 userid + path + method 查询 roleDoc 表 验证是否存在角色权限(如果角色为超级管理员,则后面无需验证)
根据角色权限 PathsDataVerify 字段 value 值可以判断是否验证数据权限。
如果需要判断数据权限,则 携带 userid + signkey + path + method 查询 signDoc 表,是否存在 有权限则把相应的 权限信息写入 context 供后续方法使用
如果是查询接口 用户无需携带 signkey , 需要查询的 signkey 由权限处理中间方法计算组成 signkey 数组 , mongodb 使用 $in 查询数据
signkey 数组: 自己的私有 key , 别人授权的数据权限 key 通过 userid + path + method 查询到所有授权的 signDoc 把里面的 signkey 放到数组里
总结
这一套权限验证是解决之前固有依赖部门设计的权限而提出的,此权限不存在与其他的依赖,只取决与签名与人与路由之间的绑定关系。可以任意组合!
数据权限是角色权限的子集,必须要满足角色权限。比如 u1 没有删除 /template 的角色权限, u2 授权 u1 拥有 u2-s-1 的 /template 的删除权限,也是不可以的。
通过这套权限把之前的数据进行了拆分,结构便于理解。当然此套表结构也是适用于其他数据库的。如果对性能有要求是可以用 redis 对权限做缓存的。当然目前我们基于200 + 用户, 200+ 路由配置,权限过滤和所有的管理结构都是在 20ms 下的。对于后台系统是可以接受的。
设计不好,仅供参考
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- vue后台管理系统权限控制思考与实践
- 谷歌网站爆XSS漏洞可窃取后台权限
- 从0到1搭建element后台框架之权限篇
- 后台权限管理系统 FEBS 新增 Spring Security 版
- 后台权限管理系统 FEBS 新增 Spring Security 版
- QueryPHP 1.0.0-alpha.2 发布,新增 IViewUI 通用后台权限系统
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。