查看原文
其他

有赞移动权限体系建设

技术琐话 2021-08-08

The following article is from 有赞coder Author 有赞技术


一、背景

权限管理是一个几乎所有大中型 B 端系统都会涉及的重要组成部分,其目的是对整个系统进行权限控制,避免造成误操作及数据泄露等风险问题。在充分调研了商家的经营需求后,传统的老板、店长、收银员等角色不足以覆盖商家角色场景。因此,在原有权限系统的基础上,增加了商家自主定义员工权限的能力,满足其细粒度管控员工权限的诉求。任何一家使用有赞开店软件经营店铺的商家,不仅能给员工赋予默认的角色,也可以实时给员工开放特定的权限、修改权限,以此保障店铺运营的安全、健康。

二、权限与权限管理

2.1 名词定义

在讲述有赞使用的权限模型之前,先介绍一下权限相关的基本概念:

  • 权限:用户可操作行为的最小单位。

  • 用户:每个用户都有唯一标识,并被授予一个或多个角色。

  • 角色:由不同的权限组合而成,最终分配给具体用户。

  • 权限管理:控制用户的权限,只能访问授权内容。

2.2 模型选择

  • ACL(Access Control List):基于用户级别的权限控制。

    将系统的各种权限直接授予具体的用户。抽象来说,为每个用户维护了单独的权

    列表,当需要分配权限、收回权限时,需要修改对应用户的权限信息。

  • RBAC(Role Base Access Control):基于角色级别的权限控制。

    与 ACL 对比,RBAC不用给用户单个分配权限,权限与用户之前通过角色关联。

    通过给不同的角色分配不同的权限,只需要将用户指向对应的角色就会有对应的

    权限。分配权限、收回权限只需要通过修改用户的角色即可。

  • ABAC(Attribute Base Access Control):基于属性级别的权限控制。

    不同于常见的将用户通过某种方式直接关联到权限的方式,ABAC 是通过动态计

    算一个或一组属性来是否满足某种条件来进行权限判断。

属性一般分为四类:用户属性(自然人属性,如年龄、性别等),环境属性(物理环境,如时间、地点、气候),操作属性(读、写)和对象属性(操作对象,如资金、某张图片、某个特定的页面,又称资源属性)。

因此理论上能够实现灵活的权限控制、将在权限与用户之前通过一组或多组属性实现关联,几乎能满足所有类型的需求。

基于线下经营的物理场景,有赞需要研发一套更灵活的权限管理系统,能将商家的权限需求,具象为多个不同的、支持商家自由勾选、定制的角色。从灵活性层面看,不需要对每个员工逐一做个性化定制,只需要对某一类员工做权限个性化定制(包含默认权限授权)。在权衡了权限的灵活性需求、管理维护难度、性能瓶颈之后,有赞最终选择RBAC 模型研发权限管理系统。

2.3 权限管控

抽象来看权限体系可以分为如下两类:功能权限 与 数据权限 两部分。

  • 功能权限指的是在系统中的功能可否使用,通常我们将功能权限分为查看、编辑、

    删除等,同时编辑、删除权限又包含了查看。通过小的权限点拆分更精细的赋予

    了员工能否进入某个页面查看信息、编辑信息的能力。

  • 数据权限指数据中存在的数据是否能查看,是一个更细粒度的权限。比如一个

    页面,不同角色查看不同的数据就需要通过数据权限控制。

管理对象维度又可以分为:店铺能力员工能力。

  • 店铺能力店铺维度的权限,比如有赞的商业化插件,可以通过店铺能力去体现。

  • 员工能力赋予员工的权限,比如收银开单、资金管理等。

  • 店铺能力优先级绝对高于员工能力,所有场景的权限判断,店铺能力必须先于员

    工能力。简单地说,店铺能力决定了“店铺能做什么”,员工能力决定了“用户能

    做什么”

三. 权限系统1.0(SAM)

SAM平台是之前在使用的权限系统,使用的权限模型是 RBAC 模型。它的核心思路是将所有的权限结果抽象成一个 64 位的 long 型。使用方在查询某个权限点时,需将权限点与后端返回的权限集做一个位运算。

后端返回的权限数据如图所示:

//权限点{ "menuId": 113101101, "menuName": "网店查看", "mapBizPerms": { "retail": [0, 0, 1125899906842624] } }//员工个人能力{ "retail": [4611686018427387903, -144115188075855873, -3]}

业务查询具体的权限时,需要对后端返回的权限数据与权限点进行计算:
SAM 系统在设计之初,业务语言趋近于实现语言。稳定运行后,各业务在实现需求、排查问题时遇到了以下三个问题:
  • 配置难: 权限需求实现频繁依赖后端资源。在日常开发中,如果要新增权限,首
    先需要业务方提供一个权限需求,而权限需求有由权限描述、角色类型、店铺类
    型组成,后端资源到位后,实现该需求、发布至相应的测试环境,同时将权限信
    息同步至端侧同学,端侧同学继续完成接下来的代码适配。这条链路流程多、沟
    通成本高、资源依赖重、排队耗时长。
  • 排查难:原来的设计,导致端上也存在权限计算,且计算方式依赖位运算,无法
    直接定位问题所在。在排查权限问题时,业务往往需要先进行复杂的计算,当问
    题定位在权限数据出错时,需要再次依赖后端资源,进行数据的修正和发布。
  • 扩展难:权限属于中台应用,承接了微商城、零售、教育等业务方,终端类型有
    pc、移动端 APP、小程序、deskTop 等。SAM 系统提供的能力比较单一,无法
    为各端提供可定制能力。原有的菜单功能是参考 pc 设计的,无法满足移动端页
    面级别的的多类型组件需求。

四.机遇与挑战

随着需求增长,权限侧的资源压力开始阻塞开发进程。而权限作为基础应用,应当降低对接成本,只提供能力,而不介入业务侧的配置。改造权限系统,让权限系统能高效吞吐需求、降低资源压力、新手能“一看就会”,成为权限系统的重构方向 :
  • 业务语言“说人话”:权限应当隔离业务语言与实现语言,对接方不再需要理解晦
    涩的权限业务语言,“权限由业务定义”。
  • 配置方式由原来的“工具”向“操作平台”改变:权限由原来的“开发工具”平台化,
    并向运营平台转变,将单侧的资源压力平衡到需求方。
  • 端侧升级架构,提供更丰富的能力:提供权限解析能力,解放业务在权限判断中
    的生产力。

五.权限系统2.0

权限团队在 SAM 系统基础上,隔离业务语言与实现语言、转变需求处理方式,支撑有赞 APP、PC、小程序、Desktop 服务:
  • rig 系统(Rig:北欧神话中彩虹桥的守护神):添加了权限表达层,以相对友好
    的方式呈现给业务方,替换以往将实现逻辑直接暴露给业务方的方式。
  • 权限配置平台:权限配置平台支持业务域配置不同店铺类型的权限,支持各端根
    据店铺类型、平台配置不同的菜单。需求方不再依赖后端资源,开发、产品或运
    营可直接根据权限需求、基于店铺类型、角色、不同的平台配置权限、菜单。
  • dynamicMenu:移动端组件动态化方案。需求方在配置平台增删、编辑APP的
    工作台菜单后,菜单内所有的组件经过权限校验后下发,且兼容零售移动的特殊
    业务模式(如离线模式)。

权限后台

rig-core:权限后台系统
职责:对接上层 rig-front,以及对接业务系统(用户管理)。对外提供菜单、API、角色、用户服务,主要有以下功能:
  • 菜单服务:获取渲染菜单。
  • API 服务:API 校验。
  • 角色服务:获取角色、同步角色、增删角色等。
  • 用户服务:用户新增、更新、删除、

权限中台

rig-front:中台系统
职责:对接上层业务方,提供 API 校验、菜单渲染、角色管理、权限管理、数据权限。
整个系统基于 spi 引擎实现业务的扩展,对于新的 namespace 或者在现有 namespace 有新的业务规则时需要开发对应的 spi 组件。

移动端

移动端权限主要提供基础的权限 API 校验, 视图能力。目前大多数场景都使用 API 校验能力,业务方根据权限做自己的处理。首页工作台使用了视图能力,根据权限渲染首页功能。

工作台架构

分为四个部分:
  • JS Engine:JS 实现的跨端计算引擎,提供统一的组件解析、权限和店铺能力解
    析 、移动端扩展能力解析。
  • WigetManager:负责管理移动端所有的 widget 管理、路由分发、widget 形
    态预处理。
  • UI 组件层:提供基础的组件如表单、网格、按钮等。
  • UI 展示层:业务根据配置,纵向展示对应的组件。

工作台实现

本文以工作台为例,介绍移动端实现原理。工作台的流程运行图如下:
零售 App 中工作台配置下发如图:
JS 引擎将原始数据筛选过滤,转换成两端用于 UI 渲染的模型。其中,menuItemType 映射成 WidgetType,用于区分 widget 类型,menuItemKey 映射成 widgetId,用于 widget 的唯一标识。由于 widget 存在于各个 module 中,所以通过路由获取 widget 实例。
@Nav("dynamicMenuWidget/UrgentNotice")class UrgentNoticeWidgetFragment : BaseRetailWidgetFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.home_widget_urgent_notice, container, false) } //数据获取成功回调 override fun loadSuccess(data: UrgentNoticeResponse?) {} //数据获取失败回调 override fun loadError(t: Throwable) {} //widget点击事件 override fun onWidgetClicked() {}}
Android 中使用 Fragment 作为 widget 的载体,通过路由库提供的方法可以非常方便的获取 Fragment 实例:
val fragment = Navigator.newInstallOrNull<BaseWidgetFragment>("dynamicMenuWidget/UrgentNotice")
一个页面会有多个 widget,考虑到 widget 刷新管理等。我们提供了一个 WidgetDelegate 用于托管页面的 widget:
class WidgetDelegate{ val widgetMap:HashMap<String,BaseWidgetFragment> val activity: Activity var rootWidgetId: String = "" val children = ArrayList<WidgetDelegate>() var parentDelegate: WidgetDelegate? = null constructor(activity: Activity, rootWidgetId: String, parentWidgetDelegate: WidgetDelegate? = null) {} constructor(fragment: BaseWidgetFragment) { /** * 加载widget * containerId:容器id * doAction: 业务方自定义处理数据 */ fun setupUI(fragmentManager: FragmentManager,@IdRes containerId: Int, beforeAction: (List<WidgetInfo>.() -> List<WidgetInfo>) = { this }) {} /** * widget刷新,用于下拉刷新等 */ fun reload() {} /** * 通过widgetId获取widget */ fun findWidgetById(widgetId: String): BaseWidgetFragment? {}}
在工作台实例化 WidgetDelegate,通过 setupUI 方法,即可完成 widget 的渲染和加载。delegate只会完成 rootWidgetId 的 children 的刷新,比如工作台的 delegate 处理紧急公告、实时概况、常用应用的渲染。收银开单属于常用应用的 children,所以常用应用也有一个 delegate,将工作台和常用应用的 delegate 关联起来即可。
布局方式根据传入的 container 决定,工作台为 LineearLayout,常用应用为 GridLayout。首页下拉刷新时,通过 reload 方法可对所有 widget 和 childrenDelegate 发起刷新的通知。
通常情况,我们点击一个按钮,比如收银开单,通过下发的url直接跳转到相应的页面即可。在实际的业务场景下,也需要满足各个业务方的需求。比如库存查询功能,有一个前置的接口校验,所以不能直接跳转。针对于这些情况,我们提供了一个全局事件监听的注册,当库存查询点击后,会查找有没有业务方注册的事件,如果有注册,会将事件交给业务方自行处理,实现如下:
//通过menuId注册点击事件DynamicMenuGlabalListenerManager.addOnWidgetClickedListener("widget_finc_stock_search"){context,widgetInfo -> service.query(){response-> if(response.success){ startActivity() }else{ showToast(response.msg) } }}
优势
  • 将组件测试与权限解耦,测试不再需要回归权限相关的用例。
  • 业务组件不需要鉴权,JSEngine 进行了严密的权限校验,没有数据越权风险。
  • 除了支持权限相关的所有配置、还支持版本控制、组件的随时上下架。
  • 移动端不同平台机型上,保证了内容的完全一致性。

六.价值回顾

1.权限需求解除后端资源依赖。
业务实现权限需求升级前后对比。升级后,不再依赖测试后端资源的投入。减少不必要的人力,整个流程也得到缩减:
2.问题排查效率提高100%
端侧同学排查修复问题效率升级前后对比:
3.线上问题大幅降低:2020 H2月均线上问题环比降低76%

七.结语

我们实现的不仅仅是商家的直接诉求,而是超越商家需求,提供了一整套完备的权限解决方案。既能满足当下店铺管理的需求,也能适应未来不同店铺形态、经营方式的发展变化。

往期推荐

技术琐话 


以分布式设计、架构、体系思想为基础,兼论研发相关的点点滴滴,不限于代码、质量体系和研发管理。本号由坐馆老司机技术团队维护。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存