超全面的权限系统设计方案!(万能通用)

前言

权限管理是所有后台系统的都会涉及的一个重要组成部分,主要目的是对不同的人访问资源进行权限的控制,避免因权限控制缺失或操作不当引发的风险问题,如操作错误,隐私数据泄露等问题。

目前在公司负责权限这块, 所以对权限这块的设计比较熟悉, 公司采用微服务架构, 权限系统自然就独立出来了, 其他业务系统包括商品中心, 订单中心, 用户中心, 仓库系统, 小程序, 多个 APP 等十几个系统和终端

◆ 1. 权限模型

迄今为止最为普及的权限设计模型是 RBAC 模型, 基于角色的访问控制(Role-Based Access Control)

1.1 RBAC0 模型

RBAC0 模型如下:

这是权限最基础也是最核心的模型, 它包括用户 / 角色 / 权限, 其中用户和角色是多对多的关系, 角色和权限也是多对多的关系。

用户是发起操作的主体, 按类型分可分为 2B 和 2C 用户, 可以是后台管理系统的用户, 可以是 OA 系统的内部员工, 也可以是面向 C 端的用户, 比如阿里云的用户。

角色起到了桥梁的作用, 连接了用户和权限的关系, 每个角色可以关联多个权限, 同时一个用户关联多个角色, 那么这个用户就有了多个角色的多个权限。

有人会问了为什么用户不直接关联权限呢?在用户基数小的系统, 比如 20 个人的小系统,管理员可以直接把用户和权限关联,工作量并不大,选择一个用户勾选下需要的权限就完事了。

但是在实际企业系统中,用户基数比较大, 其中很多人的权限都是一样的,就是个普通访问权限,如果管理员给 100 人甚至更多授权, 工作量巨大。

这就引入了 "角色 (Role)" 概念, 一个角色可以与多个用户关联, 管理员只需要把该角色赋予用户, 那么用户就有了该角色下的所有权限, 这样设计既提升了效率, 也有很大的拓展性。

  • 权限:

是用户可以访问的资源, 包括页面权限, 操作权限, 数据权限:

  • 页面权限:

即用户登录系统可以看到的页面, 由菜单来控制, 菜单包括一级菜单和二级菜单, 只要用户有一级和二级菜单的权限, 那么用户就可以访问页面

  • 操作权限:

即页面的功能按钮,包括查看, 新增, 修改, 删除, 审核等,用户点击删除按钮时,后台会校验用户角色下的所有权限是否包含该删除权限。如果是, 就可以进行下一步操作, 反之提示无权限。

有的系统要求 "可见即可操作", 意思是如果页面上能够看到操作按钮, 那么用户就可以操作, 要实现此需求, 这里就需要前端来配合, 前端开发把用户的权限信息缓存, 在页面判断用户是否包含此权限, 如果有, 就显示该按钮, 如果没有, 就隐藏该按钮。

某种程度上提升了用户体验, 但是在实际场景可自行选择是否需要这样做

  • 数据权限:

数据权限就是用户在同一页面看到的数据是不同的,比如财务部只能看到其部门下的用户数据,采购部只看采购部的数据。

在一些大型的公司,全国有很多城市和分公司,比如杭州用户登录系统只能看到杭州的数据,上海用户只能看到上海的数据,解决方案一般是把数据和具体的组织架构关联起来。

举个例子, 再给用户授权的时候, 用户选择某个角色同时绑定组织如财务部或者合肥分公司, 那么该用户就有了该角色下财务部或合肥分公司下的的数据权限。

以上是 RBAC 的核心设计及模型分析, 此模型也叫做 RBAC0, 而基于核心概念之上, RBAC 还提供了扩展模式。包括 RBAC1,RBAC2,RBAC3 模型。下面介绍这三种类型

1.2 RBAC1 模型

此模型引入了角色继承 (Hierarchical Role) 概念,即角色具有上下级的关系,角色间的继承关系可分为一般继承关系和受限继承关系。

一般继承关系仅要求角色继承关系是一个绝对偏序关系,允许角色间的多继承。

而受限继承关系则进一步要求角色继承关系是一个树结构,实现角色间的单继承。这种设计可以给角色分组和分层,一定程度简化了权限管理工作。

1.3 RBAC2 模型

基于核心模型的基础上,进行了角色的约束控制,RBAC2 模型中添加了责任分离关系。

其规定了权限被赋予角色时,或角色被赋予用户时,以及当用户在某一时刻激活一个角色时所应遵循的强制性规则。

责任分离包括静态责任分离和动态责任分离。主要包括以下约束:

  • 互斥角色:

同一用户只能分配到一组互斥角色集合中至多一个角色,支持责任分离的原则。

互斥角色是指各自权限互相制约的两个角色。比如财务部有会计和审核员两个角色, 他们是互斥角色, 那么用户不能同时拥有这两个角色, 体现了职责分离原则

  • 基数约束:

一个角色被分配的用户数量受限;一个用户可拥有的角色数目受限;同样一个角色对应的访问权限数目也应受限,以控制高级权限在系统中的分配

  • 先决条件角色:

即用户想获得某上级角色, 必须先获得其下一级的角色

1.4 RBAC3 模型

即最全面的权限管理, 它是基于 RBAC0,将 RBAC1 和 RBAC2 进行了整合。

1.5 用户组

当平台用户基数增大,角色类型增多时,而且有一部分人具有相同的属性,比如财务部的所有员工,如果直接给用户分配角色,管理员的工作量就会很大。

如果把相同属性的用户归类到某用户组,那么管理员直接给用户组分配角色,用户组里的每个用户即可拥有该角色,以后其他用户加入用户组后,即可自动获取用户组的所有角色,退出用户组,同时也撤销了用户组下的角色,无须管理员手动管理角色。

根据用户组是否有上下级关系, 可以分为有上下级的用户组和普通用户组:

  • 具有上下级关系的用户组:

最典型的例子就是部门和职位,可能多数人没有把部门职位和用户组关联起来吧。

当然用户组是可以拓展的,部门和职位常用于内部的管理系统,如果是面向 C 端的系统。

比如淘宝网的商家,商家自身也有一套组织架构,比如采购部,销售部,客服部,后勤部等,有些人拥有客服权限,有些人拥有上架权限等等,所以用户组是可以拓展的

  • 普通用户组:

即没有上下级关系,和组织架构,职位都没有关系,也就是说可以跨部门,跨职位。

举个例子,某电商后台管理系统,有拼团活动管理角色,我们可以设置一个拼团用户组,该组可以包括研发部的后台开发人员,运营部的运营人员,采购部的人员等等。

每个公司都会涉及到到组织和职位, 下面就重点介绍这两个。

1.5.1 组织

常见的组织架构如下图:

我们可以把组织与角色进行关联,用户加入组织后,就会自动获得该组织的全部角色,无须管理员手动授予,大大减少工作量,同时用户在调岗时,只需调整组织,角色即可批量调整。

组织的另外一个作用是控制数据权限, 把角色关联到组织, 那么该角色只能看到该组织下的数据权限。

1.5.2 职位

假设财务部的职位如下图:

每个组织部门下都会有多个职位,比如财务部有总监,会计,出纳等职位,虽然都在同一部门,但是每个职位的权限是不同的,职位高的拥有更多的权限。

总监拥有所有权限,会计和出纳拥有部分权限。特殊情况下, 一个人可能身兼多职。

1.6 含有组织 / 职位 / 用户组的模型

根据以上场景, 新的权限模型就可以设计出来了, 如下图:

根据系统的复杂度不同, 其中的多对多关系和一对一关系可能会有变化

1、在单系统且用户类型单一的情况下,用户和组织是一对一关系,组织和职位是一对多关系,用户和职位是一对一关系,组织和角色是一对一关系,职位和角色是一对一关系,用户和用户组是多对对关系,用户组和角色是一对一关系,当然这些关系也可以根据具体业务进行调整。

模型设计并不是死的, 如果小系统不需要用户组, 这块是可以去掉的。

2、分布式系统且用户类型单一的情况下,到这里权限系统就会变得很复杂,这里就要引入了一个 "系统" 概念。

3、此时系统架构是个分布式系统,权限系统独立出来,负责所有的系统的权限控制,其他业务系统比如商品中心,订单中心,用户中心,每个系统都有自己的角色和权限,那么权限系统就可以配置其他系统的角色和权限。

4、分布式系统且用户类型多个的情况下,比如淘宝网,它的用户类型包括内部用户,商家,普通用户,内部用户登录多个后台管理系统,商家登录商家中心,这些做权限控制,如果你作为架构师,该如何来设计呢? 大神可以在评论区留言交流哦!

◆ 2. 授权流程

授权即给用户授予角色, 按流程可分为手动授权和审批授权。权限中心可同时配置这两种, 可提高授权的灵活性。

  • 手动授权:

管理员登录权限中心为用户授权,根据在哪个页面授权分为两种方式:给用户添加角色,给角色添加用户。

给用户添加角色就是在用户管理页面,点击某个用户去授予角色,可以一次为用户添加多个角色;给角色添加用户就是在角色管理页面,点击某个角色,选择多个用户,实现了给批量用户授予角色的目的。

  • 审批授权:

即用户申请某个职位角色,那么用户通过 OA 流程申请该角色,然后由上级审批,该用户即可拥有该角色,不需要系统管理员手动授予。

◆ 3. 表结构

有了上述的权限模型, 设计表结构就不难了, 下面是多系统下的表结构, 简单设计下, 主要提供思路:

◆ 4. 权限框架

  • Apache Shrio

  • Spring Security

在项目中可以采用其中一种框架, 它们的优缺点以及如何使用会在后面的文章中详细介绍。

◆ 5. 结语

权限系统可以说是整个系统中最基础,同时也可以很复杂的,在实际项目中,会遇到多个系统,多个用户类型,多个使用场景,这就需要具体问题具体分析,但最核心的 RBAC 模型是不变的, 我们可以在其基础上进行扩展来满足需求。

 

  • 7
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
针对 OA 系统的特点,权限说明: 权限系统中,权限通过模块 +动作来产生,模块就是整个系统中的一个子模块,可能对应一个 菜单,动作也就是整个模块中(在B/S 系统中也就是一个页面的所有操作,比如“ 浏览、添 加、修改、删除” 等)。将模块与之组合可以产生此模块下的所有权限权限组 为了更方便的权限的管理,另将一个模块下的所有权限组合一起,组成一个“ 权限组” ,也就 是一个模块管理权限,包括所有基本权限操作。比如一个权限组(用户管理),包括用户的 浏览、添加、删除、修改、审核等操作权限,一个权限组也是一个权限。 角色 权限的集合,角色与角色之间属于平级关系,可以将基本权限权限组添加到一个角色中, 用于方便权限的分配。 用户组 将某一类型的人、具有相同特征人组合一起的集合体。通过对组授予权限(角色),快速使 一类人具有相同的权限,来简化对用户授予权限的繁琐性、耗时性。用户组的划分,可以按 职位、项目或其它来实现。用户可以属于某一个组或多个组。 通过给某个人赋予权限,有4 种方式( 参考飞思办公系统) A . 通过职位 a) 在职位中,职位成员的权限继承当前所在职位的权限,对于下级职位拥有的权限不可继 承。 b) 实例中:如前台这个职位,对于考勤查询有权限,则可以通过对前台这个职位设置考勤 查询的浏览权,使他们有使用这个对象的权限,然后再设置个,考勤查询权(当然也可以不 设置,默认能进此模块的就能查询),则所有前台人员都拥有考勤查询的权利。 B. 通过项目 a) 在项目中,项目成员的权限来自于所在项目的权限,他们同样不能继承下级项目的权限, 而对于项目组长,他对项目有全权,对下级项目也一样。 b) 实例中:在项目中,项目成员可以对项目中上传文档,查看本项目的文档,可以通过对项 目设置一个对于本项目的浏览权来实现进口,这样每个成员能访问这个项目了,再加上项目 文档的上传权和查看文档权即可。 c) 对于组长,因为可以赋予组长一个组长权(组长权是个特殊的权限,它包含其他各种权 限的一个权限包),所有组长对于本项目有全权,则项目组长可以对于项目文档查看,审批, 删除,恢复等,这些权限对于本项目的下级项目依然有效。 C. 通过角色 a) 角色中的成员继承角色的权限,角色与角色没有上下级关系,他们是平行的。通过角色 赋予权限,是指没办法按职位或项目的分类来赋予权限的另一种方式,如:系统管理员,资 料备份员… b) 实例中:对于本系统中,全体人员应该默认都有的模块,如我的邮件,我的文档,我的 日志,我的考勤……,这些模块系统成员都应该有的,我们建立一个角色为系统默认角色, 把所有默认访问的模块的浏览权加入到里面去,则系统成员都能访问这些模块。 D. 直接指定 a) 直接指定是通过对某个人具体指定一项权限,使其有使用这个权限的能力。直接指定是 角色指定的一个简化版,为了是在建立像某个项目的组长这种角色时,省略创建角色这一个 步骤,使角色不至于过多。 b) 实例中:指定某个项目的组长,把组长权指定给某个人。 针对职位、项目组: 如果用添加新员工,员工调换职位、项目组,满足了员工会自动继承所在职位、项目组的权 限,不需要重新分配权限的功能。 用户管理 用户可以属于某一个或多个用户组,可以通过对用户组授权,来对组中的所有用户进行权限 的授予。一个用户可以属于多个项目组,或担任多个职位。 授权管理 将一个基本权限或角色授予用户或用户组,使用户或用户组拥有授予权限的字符串,如果角 色、职位、项目中存在相同的基本权限,则取其中的一 个;如脱离角色、职位、项目组, 只是取消用户或用户组的中此角色、职位、项目组所授予的权限。用户所拥有的权限是所有 途径授予权限的集合。管理员用户可以 查看每个用户的最终权限列表。
### 回答1: A*算法是一种常用于寻找最短路径的算法。下面给出一个万能通用的Matlab代码实现: ```matlab function shortestPath = A_star(startNode, goalNode, adjacencyMatrix, heuristic) numNodes = size(adjacencyMatrix, 1); % 获取节点数量 openSet = startNode; % 初始化开放集合 gScore = Inf(numNodes, 1); % 初始化gScore gScore(startNode) = 0; % 设置起始节点的gScore为0 fScore = Inf(numNodes, 1); % 初始化fScore fScore(startNode) = heuristic(startNode, goalNode); % 设置起始节点的fScore为启发式函数值 cameFrom = zeros(numNodes, 1); % 初始化cameFrom数组 while ~isempty(openSet) [~, current] = min(fScore(openSet)); % 从开放集合中选择fScore最小的节点作为当前节点 if current == goalNode shortestPath = reconstructPath(cameFrom, current); % 如果当前节点为目标节点,则重构路径并返回 return; end openSet(openSet == current) = []; % 从开放集合中移除当前节点 neighbors = find(adjacencyMatrix(current,:) > 0); % 获取当前节点的邻居节点 for i = 1:length(neighbors) neighbor = neighbors(i); tentativeGScore = gScore(current) + adjacencyMatrix(current, neighbor); % 从当前节点到邻居节点的距离 if tentativeGScore < gScore(neighbor) cameFrom(neighbor) = current; % 更新邻居节点的cameFrom gScore(neighbor) = tentativeGScore; % 更新邻居节点的gScore fScore(neighbor) = gScore(neighbor) + heuristic(neighbor, goalNode); % 更新邻居节点的fScore if ~ismember(neighbor, openSet) openSet(end+1) = neighbor; % 如果邻居节点不在开放集合中,则将其加入 end end end end error("No path found."); % 如果无法到达目标节点,则抛出错误 end function path = reconstructPath(cameFrom, current) path = current; while cameFrom(current) ~= 0 current = cameFrom(current); path = [current; path]; % 在路径之前添加上一个节点 end end ``` 上述代码实现了A*算法的最短路径搜索。其中,`startNode`表示起始节点的索引,`goalNode`表示目标节点的索引,`adjacencyMatrix`表示节点间的邻接矩阵,`heuristic`表示启发式函数。函数输出为一个表示最短路径的向量,即从起始节点到目标节点的节点索引序列。 这个代码适用于任何可以表示为图的最短路径问题,只需提供相应的起始节点、目标节点、邻接矩阵和启发式函数即可。 ### 回答2: A*算法是一种用于寻找最短路径的启发式搜索算法,在许多应用中被广泛使用。下面我将给出一个使用MATLAB实现的通用A*算法代码: ```matlab function path = AStar(start, goal, heuristic, cost) openSet = start; % 初始化openSet队列,包含起点 closedSet = []; % 初始化closedSet队列,不包含任何节点 gScore = inf(size(cost)); % 初始化gScore为无穷大 gScore(start) = 0; % 起点的gScore为0 fScore = inf(size(cost)); % 初始化fScore为无穷大 fScore(start) = heuristic(start, goal); % 起点的fScore为启发式函数的值 while ~isempty(openSet) [~, current] = min(fScore(openSet)); % 在openSet中找到fScore最小的节点作为当前节点 current = openSet(current); if current == goal % 当前节点为目标节点 path = reconstructPath(cameFrom, current); return; end openSet(openSet == current) = []; % 将当前节点从openSet中删除 closedSet = [closedSet, current]; % 添加当前节点到closedSet for neighbor = find(cost(current, :)) % 遍历当前节点的邻居节点 if ismember(neighbor, closedSet) % 跳过已经在closedSet中的节点 continue; end tentativeGScore = gScore(current) + cost(current, neighbor); % 计算从起点到邻居节点的临时gScore if ~ismember(neighbor, openSet) % 如果邻居节点不在openSet中 openSet = [openSet, neighbor]; % 将邻居节点添加到openSet elseif tentativeGScore >= gScore(neighbor) % 如果临时gScore不小于邻居节点的gScore continue; % 跳过当前循环 end cameFrom(neighbor) = current; % 更新邻居节点的cameFrom指针 gScore(neighbor) = tentativeGScore; % 更新邻居节点的gScore fScore(neighbor) = gScore(neighbor) + heuristic(neighbor, goal); % 更新邻居节点的fScore end end error("No path found!"); % 如果循环结束时仍未找到路径,抛出错误异常 end function path = reconstructPath(cameFrom, current) path(1) = current; % 从目标节点出发 while cameFrom(current) ~= 0 % 当前节点存在指向的前驱节点 current = cameFrom(current); % 回溯节点 path = [current, path]; % 添加到路径中 end end ``` 上述代码实现了一个基本的A*算法,其中使用了启发式函数(heuristic)和代价函数(cost)来评估节点的值。在代码中,我们首先初始化openSet队列,然后在每一次迭代中选择fScore最小的节点作为当前节点,然后通过遍历当前节点的邻居节点来更新节点的gScore和fScore。最后,如果找到了路径,我们通过回溯(reconstructPath)来生成最短路径。 这段代码可以根据具体的应用场景进行一定的修改和优化。 ### 回答3: A*算法是一种常用的图搜索算法,可以用于求解最短路径问题。下面是一个用MATLAB实现的通用的A*算法代码: ```matlab function path = Astar(graph, start, goal) % 初始化起点和终点节点 startNode = Node(start, [], 0, heuristic(start, goal)); goalNode = Node(goal, [], inf, inf); % 初始化开启列表和关闭列表 openList = PriorityList(); closeList = {}; % 将起点节点加入开启列表 openList.add(startNode); % 当开启列表不为空时,继续搜索 while ~openList.isEmpty() % 从开启列表中获取最佳节点 currentNode = openList.removeFirst(); % 将当前节点加入关闭列表 closeList{end+1} = currentNode; % 如果当前节点是目标节点,则找到最短路径 if currentNode == goalNode path = reconstructPath(currentNode); return; end % 获取当前节点的所有相邻节点 neighbors = getNeighbors(graph, currentNode); for i = 1:length(neighbors) neighbor = neighbors(i); % 跳过已经在关闭列表中的节点 if isNodeInList(neighbor, closeList) continue; end % 计算从起点到这个相邻节点的已知最短路径 tentative_gScore = currentNode.gScore + getDistance(currentNode, neighbor); % 判断这个相邻节点是否已经在开启列表 if ~openList.contains(neighbor) openList.add(neighbor); elseif tentative_gScore >= neighbor.gScore continue; end % 更新相邻节点的父节点和路径代价 neighbor.parent = currentNode; neighbor.gScore = tentative_gScore; neighbor.fScore = neighbor.gScore + heuristic(neighbor.position, goal); end end % 若无法找到最短路径,返回空 path = []; end function path = reconstructPath(lastNode) path = []; while ~isempty(lastNode) path = [lastNode.position path]; lastNode = lastNode.parent; end end function neighbors = getNeighbors(graph, node) neighbors = []; for i = 1:size(graph, 1) if graph(node.position, i) ~= 0 neighbors = [neighbors Node(i)]; end end end function distance = getDistance(nodeA, nodeB) distance = graph(nodeA.position, nodeB.position); end function h = heuristic(position, goal) h = abs(position - goal); end classdef Node < handle properties position parent gScore fScore end methods function obj = Node(position, parent, gScore, fScore) obj.position = position; obj.parent = parent; obj.gScore = gScore; obj.fScore = fScore; end function result = eq(obj, other) result = obj.position == other.position; end end end classdef PriorityList < handle properties list end methods function obj = PriorityList() obj.list = {}; end function add(obj, item) for i = 1:length(obj.list) if item.fScore < obj.list{i}.fScore obj.list = [obj.list(1:i-1) item obj.list(i:end)]; return; end end obj.list{end+1} = item; end function removeFirst(obj) if ~isempty(obj.list) obj.list = obj.list(2:end); end end function result = isEmpty(obj) result = isempty(obj.list); end function result = contains(obj, item) result = false; for i = 1:length(obj.list) if obj.list{i} == item result = true; return; end end end end end ``` 以上代码实现了A*算法的一个通用版本,可以通过修改图的邻接矩阵、起点和终点来解决不同的最短路径问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值