简单的权限设计

Posted guoDaXia的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单的权限设计相关的知识,希望对你有一定的参考价值。

原来做OA的时候一直不知道权限怎么写。

 

受到原来粗粒度权限控制的观念影响,所以将问题复杂化了让我不知道怎么下笔。

 

原先的粗粒度控制权限:

书写一个过滤器,过滤器的映射范围即权限控制的范围。
游客只能访问公共的部分jsp或者controller。
我的例子的权限分为3个地方:
游客、用户、管理员。
所以写一个或者两个过滤器。在访问指定内容前进行session或者application中的身份信息验证,然后通过与否。
过滤器控制多个jsp主要是通过jsp的命名规范和文件夹规范,利用通配符*实现。控制多个controller主要是通过namespace的一些前缀来实现的。

 

现在的简单权限控制:

以现在的项目为例子:

现在项目的所有jsp都在web-info目录下。这个说明了什么?我们不能直接通过jsp的链接地址访问jsp而是用过controller进行重定向或者转发才能进入对应的地方。

我们一直想到的权限不足无法访问甚至看到模块这是怎么实现的呢?

  我们将每一个可分配的权限细化成功能。功能有几个主要字段:ID,对应url(就是controller的url)。每个用户所拥有的权限就是登陆之后自动分配的所有功能。如何实现控制?就是使用前端页面的列表或者按钮等使用el显示功能内容,所以我们就将权限控制的那一块有点感觉了。

 

每一个用户有不同的权限?  功能表和用户表

  用户太多了,好多用户的拥有的权限一样,所以我们可以抽象出角色表。用户属于一个角色,角色有特定功能。

三个表?

  三个表之间的联系需要中间表维持。所以最基础的权限控制表就出来了,5表。

 

5表我想已经可以猜出来了。2个关系表。

 

深入点的文章:

通用权限管理设计 之 数据库结构设计

一,前言 

权限管理系统的应用者应该有三种不同性质上的使用,

A,使用权限

B,分配权限

C,授权权限 

本文只从《使用权限》和《分配权限》这两种应用层面分析,暂时不考虑《授权权限》这种。

二,初步分析

用户和角色 

说到权限管理,首先应该想到,当然要设计一个用户表,一个权限表。这样就决定了一个人有什么样的权限。

做着做着就会发现这样设计太过繁琐,如果公司里面所有员工都有这样的权限呢,每一个人都要配置?那是一件很痛苦的事情。因此再添加一个角色表,把某些人归为一类,然后再把权限分配给角色。角色属下的用户也就拥有了权限。

用户、角色之间的关系是一个用户可以对应多个角色,一个角色可以对应多个用户。多对多关系。

所以需要一个中间表,相信大家都很熟悉,自然不会有疑问。

应用场景 

有了用户和角色以后,就需要设计应用场景,比如一个应用程序有几大模块(系统模块、项目管理模块、销售模块),

类似这样的模块就是一种应用场景,常见的还有 菜单 、 操作 等等。

假设现在我们设计好了,应用场景包括 模块、菜单、和操作,那么应该有以下六种关系

  1. 一个用户可以对应多个模块,一个模块可以对应多个用户。多对多关系。
  2. 一个用户可以对应多个菜单,一个菜单可以对应多个用户。多对多关系。
  3. 一个用户可以对应多个操作,一个操作可以对应多个用户。多对多关系。
  4. 一个角色可以对应多个模块,一个模块可以对应多个角色。多对多关系。 
  5. 一个角色可以对应多个菜单,一个菜单可以对应多个角色。多对多关系。  
  6. 一个角色可以对应多个操作,一个操作可以对应多个角色。多对多关系。

于是建立六张表来维护这六种关系。

这样设计看起来没什么问题。是的,如果没有加入新的关系的话,这样是已经可以满足大部分的需求了。可是如果就是如果,新的关系(需求)往往会加入到系统进来。这个时候就需要再建立一个新的表。系统的复杂度也随着增加。

可以看出,这样的设计有几个问题:

 

  1. 数据表设计太复杂
  2. 应对系统方案过于固定

三,把问题简单化

 

 不同的应用场合,你可能会想出不同的需求,提了一个新的需求以后,可能会发现原来的设计没方法实现了,于是还要添加一个新的表。这也是上面所提到的问题。 

 其实不必想得那么复杂,权限可以简单描述为:

某某主体 在 某某领域 有 某某权限 

1,主体可以是用户,可以是角色,也可以是一个部门

2, 领域可以是一个模块,可以是一个页面,也可以是页面上的按钮

3, 权限可以是“可见”,可以是“只读”,也可以是“可用”(如按钮可以点击)

其实就是Who、What、How的问题

 

因此上面所提到的六张表其实可以设计一张表:

 

 

 

四,实例说明

 

下面用一个例子做设计说明。“用户、角色在页面上的是使用权限”

详细设计:

1,把菜单的配置放在数据库上,每一个菜单对于一个唯一的编码MenuNo,每一个“叶节点”的菜单项对于一个页面(url)。

2,把按钮的配置放在数据库上,并归属于一个菜单项上(其实就是挂在某一个页面上)。应该一个页面可能会有几个按钮组,比如说有两个列表,这两个列表都需要有“增加、修改、删除”。所以需要增加一个按钮分组的字段来区分。

3,把菜单权限分配给用户/角色,PrivilegeMaster为"User"或"Role",PrivilegeMasterValue为UserID或RoleID,PrivilegeAccess为“Menu",PrivilegeAccessValue为MenuNo,PrivilegeOperation为"enabled"

4,把按钮权限分配给用户/角色,PrivilegeMaster为"User"或"Role",PrivilegeMasterValue为UserID或RoleID,PrivilegeAccess为“Button",PrivilegeAccessValue为BtnID,PrivilegeOperation为"enabled"

5,如果需要禁止单个用户的权限,PrivilegeOperation 设置为"disabled"。

如果不清楚的可以看下图:

 

 数据库设计:

 

 

四,结语

说了这么多,其实我推荐的只是Privilege的表设计。这个表是who、what、how问题原型的设计。不仅扩展性、灵活性都很好,而且将复杂的权限管理系统浓缩成一句话。

 而PrivilegeOperation不仅仅只是使用和禁止两种,包括分配权限、授权权限,都可以用这个字段定义。只是这无疑加大了应用程序的设计难度,但是对于表设计可以不做出任何的修改就可以完成,可以看出其灵活性。 

 

前言

 

前一篇文章《通用权限管理设计 之 数据库设计方案》介绍了【主体】- 【领域】 - 【权限】( who、what、how问题原型 ) 的设计思想

 

本文将对这种设计思想作进一步的扩展,介绍数据权限的设计方案。

权限控制可以理解,分为这几种 :

【功能权限】:能做什么的问题,如增加产品。
【数据权限】:能看到哪些数据的问题,如查看本人的所有订单。
【字段权限】:能看到哪些信息的问题,如供应商账户,看不到角色、 部门等信息。

上面提到的那种设计就是【功能权限】,这种设计有一定的局限性,对于主体,只能明确地指定。对于不明确的,在这里可能就没办法处理。比如下面这几种情况:

数据仅当前部门及上级可见
数据仅当前用户(本人)可见

类似这样的就需要用到上面提的数据权限。

上一篇文章我用一个表五个字段完成了【功能权限】的设计思路
本文我将介绍如何利用一个表两个字段完成这个【数据权限】的设计思路

初步分析

【数据权限】是在【功能权限】的基础上面进一步的扩展,比如可以查看订单属于【功能权限】的范围,但是可以查看哪些订单就是【数据权限】的工作了。

在设计中,我们规定好如果没有设置了数据权限规则,那么视为允许查看全部的数据。

几个概念
【资源】:数据权限的控制对象,业务系统中的各种资源。比如订单单据、销售单等。属于上面提到的【领域】中的一种
【主体】:用户、部门、角色等。
【条件规则】:用于检索数据的条件定义
【数据规则】:用于【数据权限】的条件规则 

 
应用场景
1,订单,可以由本人查看 
2,销售单,可以由本人或上级领导查看 
3,销售单,销售人员可以查看自己的,销售经理只查看 销售金额大于100,000的。

我们能想到直接的方法,在访问数据的入口加入SQL Where条件来实现,组织sql语句:

1,where UserID = {CurrentUserID}
2,where UserID = {CurrentUserID} or {CurrentUserID} in (领导)
3,where UserID = {CurrentUserID} or ({CurrentUserID} in (销售经理) and 销售金额 > 100000)

这些一个一个的"条件",本文简单理解为一个【数据规则】,通常会与原来我们前台的业务过滤条件合并再检索出数据。

这是一种最直接的实现方式,在【资源】上面加一个【数据规则】(比如上面的三点)。这样设计就是

  【资源】 - 【数据规则】

我又觉得不同的人应该对应不同的规则,那么也可以理解为,一个用户对应不同的角色,每一个角色有不一样的【数据规则】,那么设计就变成

  【资源】 - 【主体】 - 【数据规则】

根据提供者的不同,准备不同的权限应对策略。

这里可以简单地介绍一下,这个方案至少需要2张表,一个是  【资源,主体,规则关系表】、一个是【数据规则表】

关系表不能直接保存角色,因为你不确定什么时候业务需要按照【部门】或者【分公司】来定义数据规则

于是可以用  Master、MasterKey  类似这样的两个字段来确定一个【主体】

用XML方式的话是这样配置的(放在数据库也类似):

复制代码
<?xml version="1.0" encoding="utf-8"?>
<settings>
<rule view="订单" role="销售人员">
销售员 = {CurrentUserID}
</rule>
<rule view="订单" role="总销售经理">
销售金额 > 100000
</rule>
<rule view="订单" role="区域销售经理">
销售金额 > 100000 and 区域 = {当前用户所属区域}
</rule>
</settings>
复制代码

对于这种方式有兴趣的朋友也可以试一下,两种方式的【数据规则】是一样的,但是本文没有采用第二种设计方式,因为它多了一层处理逻辑,我以为应该设计越简单越好,就采用第一种方式:

  【资源】 - 【数据规则】

当然,上面是用SQL的方式来确定条件规则的,我们当然不会这么做。SQL虽然灵活,但是我们很难去维护,也不知道SQL在我们的界面UI上面如何体现。难不成直接用一个文本框来显示。这样对应一个开发人员来说不是问题,可是对应系统管理员,很容易出问题。所以我们需要有另一方式来确定这一规则,并最终可以转换成我们的SQL语句。

我们的设计关键在于如何规范好这些【数据规则】 ,这个规则必须是对前端友好的,而且是对后台友好的,JSON显然是很好的方式。

 规则说明:

1,数据权限规则总是:{属性 条件 允许值}

2,数据权限规则可以合并。比如 ( {当前用户 属于 销售人员} and {订单销售员 等于  当前用户} )   Or {当前用户  属于 销售经理}

3,最终我们会用JSON格式

在检索数据时会先判断有没有注册了某某【资源】的【条件规则】,如果有,那么加载这个【条件规则】并合并到我们前台的【搜索条件】(你的业务界面应该有一个搜索框吧)

 如下图定义了客户信息的搜索框,我们搜索客户ID包括AN,我们组织成的规则将会是:

{"rules":[{"field":"CustomerID","op":"like","value":"AN","type":"string"}],"op":"and"}

为了更好地理解【数据规则】,这里介绍一下【通用查询机制】 

【通用查询机制】

权限控制总离不开一些条件的限制(比如查看当前部门的单据),如果没有完善的通用查询规则机制,那么在做权限条件过滤的时候你会觉得很别扭。这里介绍一个通用查询方案,然后再介绍如何实现【数据规则】。

 

早些时候我写过一篇关于ligerGrid结合.net设计通用处理类的文章《 jQuery liger ui ligerGrid 打造通用的分页排序查询表格(提供下载) 》。里面提到的过滤信息是直接的SQL语句。这是不可靠,而且不安全的。
在前端传输给后台的过滤信息不应该是直接的SQL,而应该是一组过滤规则。在ligerui V1.1.8 已经加入了一个条件过滤器插件,这个插件组成的规则数据才是我受推荐的:
比如如下

 

{"rules":
[
{"field":"OrderDate","op":"less","value":"2012-01-01"},
{"field":"CustomerID","op":"equal","value":"VINET"}
]
,"op":"and"}

 

规则描述:
查找顾客VINET所有订单时间小于2011-01-01的单据


这样的数据是安全的,而且是通用的(你甚至可以再加一个OR子查询)。无论是在前端还是后台,无论你使用什么样的组件,都可以很好地利用。

通用后台的翻译,就可以生成这样SQL的参数:

 

复制代码
Text:
([OrderDate] < @p1 and [CustomerID] = @p2)
Parameters:
p1:2012-01-01
p2:VINET
复制代码

 

下面来点复杂的:查找 顾客VINET或者TOMSP,所有订单时间小于2011-01-01的单据

 

{
"rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
"groups":[
{"rules":[{"field":"CustomerID","op":"equal","value":"VINET"}, {"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
],
"op":"and"
}

 翻译结果:

 

复制代码
Text:([OrderDate] < @p1 and ([CustomerID] = @p2 or [CustomerID] = @p3))
Parameters:
p1:2012-01-01
p2:VINET
p3:TOMSP
复制代码

 

这个过滤规则分为三个部分:【分组】、【规则】(字段、值、操作符)、【操作符】(and or),而自身就是一个分组。
这种简单的结构就可以满足全部的情况。

当然,上面提到的这些条件都是在前台定义(可能是用户在搜索框自己输入的)的,而在后台,我们可能会定义一下【隐藏条件】,比如说 【员工只能查看自己的】,要怎么做呢,其实很简单,只需要在后台接收到这个过滤条件(前台toJSON,后台解析JSON)以后,再加上一个过滤规则(隐藏条件):

 

{field:\'EmployeeID\',op:\'equal\',value:5}

 

可以将原来的过滤规则当做一个分组加入进行:

 

 

{op:\'and\',groups:[

 

{"rules":[{"field":"OrderDate","op":"less","value":"2012-01-01"}],
"groups":[
{"rules":[{"field":"CustomerID","op":"equal","value":"VINET"},{"field":"CustomerID","op":"equal","value":"TOMSP"}],"op":"or"}
],"op":"and"}

 

],rules:[{field:\'EmployeeID\',op:\'equal\',value:5}]
}

 

 

翻译如下:

 

复制代码
Text:
([EmployeeID] = @p1 and ([OrderDate] < @p2 and ([CustomerID] = @p3 or [CustomerID] = @p4)))
Parameters:
p1:5
p2:2012-01-01
p3:VINET
p4:TOMSP
复制代码



 这样的【条件规则】才是我们想要的,不仅在前端可以很好地解析,也可以在后台进行处理。在后台我们会定义跟这种数据结构对应的类,那么再定义一个翻译成SQL的类:

 

数据权限规则

说了这些,可以开始介绍如何实现【数据规则】了:

上面提到的【隐藏条件】,就是我介绍的【数据规则】
试想一些,这样 前台的过滤规则,再加上我们之间定义好的 【数据权限】控制 过滤条件。不就达到目的了吗。
先看看我们在数据库里保存的这些【数据规则】:

 看不明白?那来个清楚一点的:

规则描述

订单:【订单管理员和演示角色可以查看所有的】,【订单查看员】只能查看自己的

产品:【基础信息录入员和演示角色可以查看所有的】,【供应商】只能查看自己的

{CurrentEmployeeID}表示当前的员工。

实质上,我们还可以根据当前用户信息定义需要的参数,比如:

{CurrentUserID} 当前用户Id ,对应表【CF_User】

{CurrentRoleID} 当前角色Id ,对应表 【CF_Role】 

{CurrentDeptID} 当前用户部门Id,对应表【CF_Department】

{CurrentEmployeeID} 当前用户员工Id,对应表【Employees】(CF_User.EmployeeID)

{CurrentSupplierID} 当前用户供应商Id,对应表【Suppliers】(CF_User.SupplierID)

 在数据库中我们直接保存这些用户参数,在“翻译”规则成为SQL时,会替换掉:

比如查看订单,我们得到的SQL,可能是这样的:

复制代码
Text:     SELECT * FROM (SELECT TOP 20 * FROM (SELECT TOP 40 * FROM [Orders] WHERE ( 1=1  and ((@p1 in (@p2,@p3)) or (@p4 = @p5 and [EmployeeID] = @p6))) ORDER BY OrderID ASC) AS tmptableinner ORDER BY OrderID DESC) AS tmptableouter ORDER BY OrderID ASC     
Parameters:
@p1[Int32] = 7
@p2[Int32] = 2
@p3[Int32] = 6
@p4[Int32] = 7
@p5[Int32] = 7
@p6[Int32] = 1
复制代码

{CurrentRuleID} 替换为 7

{CurrentEmployeeID} 替换为1

 下图是我们设计【数据权限】的界面,可以选择所有的字段,包括几个用户信息:

这些字段不仅仅只是在文本框中输入值,那么可以自定义数据来源:

 

复制代码
var fieldEditors = {};
fieldEditors[\'Orders\'] = {
\'ShipCity\': { type: \'combobox\',
options: {
width: 200,
url: "../handler/select.ashx?view=Orders&idfield=ShipCity&textfield=ShipCity&distinct=true"
}
}
};
复制代码

效果界面:

 

 

实际应用

既然是数据权限控制,如果有一个统一的数据接收入口,我们倒是可以利用这个入口做一些工作。

比如【ligerRM权限管理系统】统一使用 grid.ashx 这个数据处理程序作为列表数据的接收入口。

有了统一的接口,方便做权限的控制,使用过 ligerGrid javascript表格,或者类似插件的朋友,应该比较清楚服务器的交互原理。 
在grid.ashx中,我们会通过 
【视图/表名 】、 【排序信息】、【分页信息】、【过滤信息】
这几个指标来获取指定的数据。


而在实际的业务中,可能会引入权限的控制。比如某某【资源】,只能由当前用户自身才能查看,或者只能由当前用户部门及上级部门才能查看。对于这些控制,我们采用对这些可能做权限控制的【资源】注册一组【条件规则】的方式来进行。

 我们将找到view定义好的【数据权限规则】,然后和用户在前台搜索框输入的【搜索规则】合并:

上面的代码就是数据条件合并的例子,这样便得到了我们最终需要的 过滤规则。

 

上面文章来自:http://www.cnblogs.com/leoxie2011/archive/2011/05/19/2050626.html

 

 复制到这里是怕主人删了。谢谢。

以上是关于简单的权限设计的主要内容,如果未能解决你的问题,请参考以下文章

如何修复“代码应明确检查权限是不是可用”错误

关于侧边导航菜单和片段的一般设计问题

教程4 - 验证和权限

gitlab 权限说明

Django REST框架--认证和权限

JavaScript 作用域 与 作用域链