用户权限系统设计方案
钟峰·2004年10月
[版本:1.0.0]
摘要
本文介绍一个应用于企业应用通用的用户权限系统的设计框架,其设计思想与主要文档来源自 SunWu Software Studio 的 iSecurityManager® 产品。本指南适用于体系结构设计人员和开发人员。
目录
简介
安全始终是可信赖的企业应用的基石。
在企业应用中,通常需要控制用户对业务操作的权限管理与控制。稍加分析不难发现这会涉及到这三个对象:用户/角色、动作/操作、授权状态,进一步分析,我们可发现“动作/操作”通常是针对某个特定的对象,譬如 『新增』动作可对应于『采购申请单』也可对应于『销售出库单』等,这些动作对应的对象我们将其称之为“应用模块”。
至此,用户权限系统中的基本逻辑显形:谁(用户/角色)对什么(应用模块)是否具有某项操作(动作)的授权(授权状态:授予-Grant、拒绝-Deny、继承-Revoke)。
用户与角色
使用权限的基本单位,角色是具有一组相同授权的用户的交集。用户与用户之间没有互相隶属的关系,它只可以隶属于角色,角色可以隶属于另一角色,并且可具有多重隶关系。
用户或角色通常具有以下属性:
- 编号,在系统中唯一。
- 名称,在系统中唯一。
- 用户口令(角色无此属性)
当然,在实际的商业应用中,可能还需要更多的属性来描述特定的业务需求。如在 iSecurityManager® 中用户和角色的信息就有如下:
有了用户和角色对象,还必须有一个描述他们之间隶属关系的对象,这样的对象我们称之为“成员关系(Member)”,通常它可能有如下属性:
- 用户编号
- 角色编号
该“用户编号”和“角色编号”组合唯一约束,这里的“用户编号”可能是一个用户对象的编号,也可能是一个角色对象的编号,而“角色编号”则始终只能对应一个角色对象的编号。一个成员关系对象表示某个用户或角色隶属于另个角色。在 iSecurityManager® 中可能有如下界面表示:
在 iSecurityManager® 中通过『成员设置』窗口来设置任何一个用户或角色(系统固定用户和角色除外)所隶属于的角色,也可以通过『角色属性』窗口来设置所属于当前角色的直接下级成员。
动作定义
在涉及数据库操作的应用中,四个基本操作是“查看(Select)”、“删除(Delete)”、“新增(Insert)”以及“更新(Update)”。在一个企业应用中当然还会有更多其他的操作,尽管这些操作可能最终都基于这四个基本操作,也可能是其他的非数据操作,而我们始终无法在刚开始就完全固定这些可能的操作,为此,必须能让系统开发人员根据每个具体的应用模块来定义其所属的特定的动作/操作。
动作/操作通常具有以下属性:
- 名称,在系统中唯一。
- 备注,描述动作/操作。
在 iSecurityManager® 中“动作定义”的信息就有如下:
事实上,有些动作是需要针对具体的数据实例的,譬如“查看”、“删除”和“更新”等,这种对数据实例级别的控制比针对“应用模块”级的控制要更精细。譬如,某个用户能进行对【采购申请单】的各项操作,他应该能查看他刚填写的单据并进行某些适当的修改,但是他却不应该看到其他人填写的数据。那么,这种粗粒度的权限设置显然无法控制数据级的访问,在 iSecurityManager® 中,专门使用“流程控制”的方案和技术来达致更细粒度的对数据实例级的访问和控制。
应用模块
应用模块通常是指对应于企业应用中的某种类型的业务单据,譬如 ERP 系统中的【采购申请单】、【销售出库单】、【客户基础资料】等,这些业务单据可能对应一或多张数据库表。如果从面向对象(Object Oriented)的视角去看,似乎可以将本文中所描述的这些概念理解成这样:“应用模块”是类,“动作/操作”则是类的方法,而“应用模块”对应的数据库表(表结构)则是类的属性,表中的这些数据/记录就是类的实例了。
在定义规划系统中的“应用模块”时,通常还需要指定某个“应用模块”可能具有的“动作/操作”,而这些“动作/操作”由『动作定义』中进行了定义。注意:应用模块是系统中的一种逻辑组织单位。
应用模块通常具有以下属性:
- 名称,在系统中唯一。
- 标题,描述动作/操作。
- 动作列表,表示该应用模块所可能具有的所有操作。
在 iSecurityManager® 中“应用模块”的信息就有如下:
授权
顾名思义,授权是指用户/角色能对哪些应用模块中的某些操作(动作)是否具有执行的许可。这里的“是否具有执行的许可”实际上指的是授权的三种状态:授予-Grant、拒绝-Deny、继承-Revoke。
- 授予:用户/角色对某个应用模块的某项操作具有执行的权力。
- 拒绝:用户/角色对某个应用模块的某项操作没有执行的权力。
- 继承:用户/角色对某个应用模块的某项操作是否具有执行的权力要取决于它的父角色的授权定义。
如果对某个角色授予某项权力,其下属的用户或角色是可以覆盖该项授权定义的。默认情况下,采用就近优先、拒绝优先的原则。
在 iSecurityManager® 中“授权设置”的信息如下所示:
总结
通常“应用模块”和“动作定义”是由系统开发商在系统设计、开发阶段就定义好了,在系统交付给用户使用后就不再对此更改了(当然这也不是绝对的,不是吗?)。
至此,有关用户权限的各项设置、定义都完成了,事实上,这并不会立即为你的应用系统带来任何安全保障,这只是一堆关于用户和授权定义的设置数据而已,还必须在应用系统中去应用这些设置数据并根据其定义的控制逻辑以对业务数据进行控制。如何利用这些用户授权数据来控制应用系统对业务数据的访问,有很多不同的解决方案,但是我认为建立一个中间数据存储层来统一所有客户端对数据源的存储访问是个不错的主意,并在这个数据访问层中应用安全设置来对业务数据的各种访问进行授权验证和控制,这样就可以统一各种客户端对数据存储的安全应用(事实上 iSecurityManager® 也正是如此处理这个问题的)。
链接资源
钟峰(Popeye Zhong)目前是 深圳恒泰丰科技公司 的.NET开发组的架构设计人员。他曾经使用 C 语言做过图形程序设计,在相当长的一段时期内从事 COM/COM+ 组件的开发和设计工作,并且短暂的做过 Lotus/Notes 和 Dialogic 语音卡程序的开发,从2003年初开始使用.NET这个充满趣味和挑战的开发平台。感兴趣的除了企业应用架构设计、组件开发、安全、图像处理外还对汽车和枪械模型有浓厚的兴趣。如果希望与他联系,可访问 http://blog.csdn.net/SW515 或者EMail SW515@21cn.com 。
相关文章:《安全之道:加密与数字签名》
[1] 企业应用:企业应用包括 工资单、患者记录、发货跟踪、成本分析、信誉评估、保险、供应链、生产管理、记帐、客户服务以及外币交易等。企业应用不包括车辆加油、文字处理、电梯控制、电话交换机、操作系统、编译器以及电子游戏等。【引用于《企业应用架构模式》Martin Fowler 著】
版权声明:CSDN是本Blog托管服务提供商。如本文牵涉版权问题,CSDN不承担相关责任,请版权拥有者直接与文章作者联系解决。
[点击此处收藏本文]
评论
1。操作是二进制代码表示的,你的系统中使用的数据长度是多少?所支持的操作数量是否受限于存储长度?
2。角色是固定分配的,势必存在独立的用户/操作/角色分配数据。对于定制的应用系统,是否意味着需要与iSecurityManager做数据接口?
3。采用单独的“流程控制”方案来控制“字段级”权限?愿闻其详。这是否意味着还有其他的权限控制数据和接口?
4。iSecurityManager以何种方式支持用户的程序开发?API?FrameWork?Lib?
我想,你说的其实是指控制访问入口的问题罢。通常,一个简单的处理方式是:在用户登录系统后,设置所有应用程序中所有的入口(包括菜单、工具条按钮、图像快捷按钮 等)。当然,为了安全起见,你可能还会在打开相关业务窗体的时候,再次验证该用户是否具有对该窗体的访问权限。以上这些处理方式,其实都必须依赖客户端是否进行了有效验证,假如,你的客户端程序错漏了对某个业务窗体的授权验证,那么这就出现了一个危险的安全漏洞,对此,文中的建议是还需要在中间层对特定业务模块进行授权检查,这可确保不会因为客户端的误漏而导致的非法访问。
最后,需要提醒的一点是客户端访问入口的控制,也许你的应用随时会增加不同的访问入口甚至是允许由最终用户来自定义的,那么,你就必须建立一套行之有效且统一的访问入口控制机制,只有这样,你的应用才不会随着时间的增长而变得越来越复杂和难以控制。
呵呵,我想 你看得蛮仔细的。
1、在 iSecurityManager® 里面,“动作/操作”是由开发用户自定义的,它并不是由二进制代码表示的,它由一条记录来表示。其里面的“数值”属性(字段)是 Int32(4 Byte) 类型,这个“数值”属性是为了在应用中更方便的使用该“动作/操作”而准备的,它并不是一个唯一的数值。譬如,你可以定义“创建”和“提交”这两个操作,其‘数值’属性都为64,只要他们不同时出现在一个业务窗体/模块中,那么这都不会影响你利用“数值”属性来定位“动作/操作”的意愿,当然,你也可以完全不去使用“动作/操作”中的“数值”属性。
2、你说的是用户或角色的授权罢?这是可以灵活定制的,不是固定不变的。在应用系统中,需要使用 iSecurityManager® 的开发接口来进行相关的权限配置访问。
3、事实上我说的是采用“流程控制”的方案来控制“记录”级的权限。“字段级”分为两种:一是该字段始终可由指定的用户/角色进行访问,而不管是哪条记录;二是该字段可由指定的用户/角色进行访问,但针对不同的记录又有不同的授权许可。第二种有些复杂,是“流程控制”方案中的一个特例,第一种情况有些像本文中所讨论的,你可利用本文中这种方案进行不同的扩展来达到。有关“流程控制”已经是另外一个话题了,在我们提供的安全方案中把它叫做 iFlowManager®,希望以后有机会我们再来讨论这个课题。
4、iSecurityManager® 是使用 .NET 开发的,目前支持 .NET/COM 的开发接口。它一套自己的 Framework for .NET。
另外我想问一下,能否针对某个用户进行个别授权?
没错,iSecurityManager 是面向应用程序级的用户权限管理系统,它当然能对用户进行个别授权,这和对角色授权是完全一样的概念。
对数据级的权限控制,事实上你也用它来做到,譬如 你将这里面的“应用模块”对应成你记录的唯一标识,那么对“应用模块”的授权不就成为了对记录的授权了吗?~ 不过,这样做有些弊端,至少会导致授权数据大量增加,并且不好控制,而且对于你的某些流动数据(即 在不同阶段/时段需要应用不同的 ACL(Access Control List) 的数据)是很难动态控制权限的,那么针对这些复杂应用情况,我们使用另外一套被我们称作是 iFlowManager 的方案,这两种方案并不是孤立的,而且互相关联彼此支援的。
至于你说的那种多级分类的情况,我认为这只是普通的记录级的权限控制的问题,也许你是想控制父类记录而自动应用到旗下的子分类记录,那么你如果能控制父类这条记录,下面子类是否需要怎样的控制就应该是由应用逻辑来决定的罢,这似乎不应该是权限系统该处理的范畴。我的意思是,细粒度的权限控制系统如果能控制到记录/数据级的访问就可以了,至于这些记录或数据之间的某些约束级联关系就最好另做处理,总之,这个问题只是一个系统逻辑划分的设计问题。
以前一般做权限控制,就是先把功能(比如“删除A类单据”是一个功能)划分好,编个号之类;然后建角色->给角色分配角色;->把角色分配给用户。
象这种处理对于控制功能上的权限还是基本满足的,但是对于控制内容上的就一直没办法做得很好,困难在于要控制的内容在代码实现的时候是不能比较好的分类和做定义,于是做的时候会遇到问题,比如对于 删除A类单据,还需要根据A类单据的某个属性X来判断:1st:用户是否有“删除A类单据”权限,2nd:用户对属性X=Y的A类单据是否有删除权限。
前面说在定义功能的时候,可以定义出一个功能是“删除A类单据”,甚至还可以定义“删除属性X=Y的A类单据”,但是对于单据X属性值可能是Y,也可能是Z等等,这(X的值域)是系统运转的时候才会浮出水面的。这种清况下功能划分就N困难,一直在挠头。做功能抽象划分越到后面越发现总是不够完善,N烦。
发现表达能力弱了很多,不知道大家能不能读明白我写的东西?
其实这还是那个权限控制粒度的老问题!
在这里我想表达一个观点的是,不要企图在权限控制系统中去定义针对特定具体业务逻辑的限制条件,因为你永远不知道明天会发生什么也总是预测不完客户的各种古怪需求。
当你企图自定义某种表达式来表述日益复杂的业务结合权限控制的逻辑,甚至你还希望这种表达式能应付可能对未知新的业务更改所带来的新问题,通过这种不断抽象不断叠加并充满矛盾的过程最后你会发现这些表达式的定义越来越复杂并难以被用户使用。所以我的忠告是使用插件模式来扩展权限与业务逻辑的结合,当然定义一套简单易行的表达式来连接权限系统与业务系统的逻辑也是有用的,但是必须保证这是易于使用并简单可靠,对于更复杂的逻辑控制就应该使用插件来解决了。
正如你所说得那样,单据X属性的域值是不可预知的,所以企图把这种逻辑假定在某个特定范围是极其困难的。
在很多企业应用中都需要针对特定的数据进行不同的流程处理(包括权限)的需求,譬如:ERP中的销售报价单,A销售员不能查看B销售员录入的对客户的产品报价单记录,甚至不同销售部门的销售人员的报价单记录需要走不同的审批流程,这通常都要涉及不同的权限控制。呵呵,这看起来似乎有些棘手,那么我们来大概看看 iFlowManager 是如何处理的吧。
在 iFlowManager 中,由流程节点和流程线组成一个流程图,一个流程图代表一个流程控制逻辑,而流程节点主要意义在于它所表达的权限控制定义和插件配置;流程线将这些零散的流程节点串连起来组合成一个流程图,流程线定义了一个通过列表和插件配置,即表明那些用户/角色能通过此流程线进入后继节点;流程引擎将会跟踪应用到指定流程的业务数据(记录),业务系统通过流程引擎API来过滤掉不符合流程定义权限的记录/数据。
有段时间没写blog了,发现表达能力又下降了不少,唉~
近来也在做关于权限设计方面的研究,甚感头痛...
设计简单了,控制粒度就不够.控制粒度上去了,又弄的乱七八糟,没点头绪.还望楼主多多指点.
我主要是想对表,行,列进行控制,表倒好弄,可行和列就不那么好弄了.
比如:A用户能查看其他用户输入的数据,只能修改自己的数据,而且不能删除.这种控制也挺麻烦.楼主又什么好的建议?
刚才看了一下《权限系统设计问题》[http://blog.csdn.net/dhlhh/archive/2004/10/20/144844.aspx]一文,里面的大概思路我认为是不错的,只是建议将字符类型的权限描述字段改为整型类型,这样在程序处理上更高效灵活些(见下面的范例)。
本文只是大概谈了一下粗粒度(表/模块级)的权限控制方案,对于细粒度(行/字段级)的数据控制解决办法,本人在回复中谈到了应用工作流来解决此问题的通用方案,有关工作流的内容相对复杂些,三言两语可能说不太明白,通常大家的应用需要更简单快速的解决办法,那么,我就来说说我在一个小项目中使用的办法吧,希望能对各位看官有所帮助。
用户表定义如下:(类SQL脚本)
TUser
(
UserName nvarchar(50), --主关键字
Password varbinary(128), --帐户口令
Permission int --权限字(二进制掩码)
)
业务表(譬如:销售订单)
TSalesOrder
(
OrderNo varchar(30), --主关键字(订单编号)
Creator nvarchar(50), --订单制单员(关联 TUser 表的 UserName 字段)
... 其他业务字段 ...
)
权限字枚举定义(C#)
[FlagsAttribute]
public enum Permission
{
Select = 1, //表示可“读取”所属于自己的数据的权力
Delete = 2, //表示可“删除”所属于自己的数据的权力
Insert = 4, //表示可“新增”所属于自己的数据的权力
Update = 8, //表示可“更改”所属于自己的数据的权力
AnySelect = 128, //表示可“读取”所属于其他用户的数据的权力
AnyDelete = 256, //表示可“删除”所属于其他用户的数据的权力
AnyInsert = 512, //表示可“新增”所属于其他用户的数据的权力
AnyUpdate = 1024 //表示可“更改”所属于其他用户的数据的权力
}
如果一个用户拥有修改属于自己数据的权力则该用户的 TUser.Permission 字段的值为:9(Select + Update),如果他还同时想拥有读取其他用户数据的权力,那么其 Permission 字段值应为:137(Select + Update + AnySelect),只需要使用这些枚举项定义的数值进行相加就可以得到你想要授权的范围。
那么如何判断某条记录是属于自己的还是别人的呢?以上面定义的“销售订单”表为例,只要判断 TSalesOrder.Creator 字段是否为当前登录用户的名称即可。具体处理方法可在数据层来做该数据检测过滤。下面给出一段中间数据层的读取“销售订单”方法的代码片断(C#)
public class SalesOrder
{
//获取或设置当前操作的用户名(登录用户名)
public string UserName
{
get{...}
set{...}
}
//获取或设置当前操作用户的权限字
public Permission UserPermission
{
get{...}
set{...}
}
//获取销售订单表
public DataSet Select()
{
//创建销售订单记录集对象(通常你可以使用其他更简便的方法得到这个数据集对象,譬如:使用XSD)
DataSet dataSet = new DataSet("SalesOrderDST");
//生成销售订单表
dataSet.Tables.Add("TSalesOrder");
//生成销售订单字段
dataSet.Tables["TSalesOrder"].Columns.Add("OrderNo", typeof(String));
dataSet.Tables["TSalesOrder"].Columns.Add("Creator", typeof(String));
//创建数据连接对象
using(SqlConnection connection = new SqlConnection(connectionString))
{
//创建命令对象
SqlCommand command = connection.CreateCommand();
command.CommandText = "Select * FROM dbo.TSalesOrder";
//打开数据连接
connection.Open();
//得到数据读取器对象
using(SqlDataReader reader = command.ExecuteReader())
{
int ordinalOrderNo = reader.GetOrdinal("OrderNo");
int ordinalCreator = reader.GetOrdinal("Creator");
string creator;
//依次读取数据库中该表的所有行
while(reader.Read())
{
//输入数据行所有者字段为空,则跳过
if(reader.IsDBNull(ordinalCreator))
continue;
//获得数据行所有者字段值
creator = reader.GetString(ordinalCreator);
//如果当前数据行所有者是当前登录用户,则必须判断当前登录用户的权限是否具有“读取”自己数据的权力;-或者-
//如果当前数据行所有者不是当前登录用户,则必须判断当前登录用户的权限是否具有“读取”他人数据的权力。
//如果权限验证通过则,将该行记录加入到要返回的 DataTable 对象中,否则跳过。
if((String.Compare(creator, UserName, true) == 0) && (UserPermission & Permission.Select) == Permission.Select) ||
(String.Compare(creator, UserName, true) != 0) && (UserPermission & Permission.AnySelect) == Permission.AnySelect))
dataSet.Tables["TSalesOrder"].Rows.Add(new object[]{reader.GetString(ordinalOrderNo), reader.GetString(ordinalCreator)});
}
}
//关闭数据读取器和数据连接对象
reader.Close();
connection.Close();
}
//返回销售订单记录集对象
return dataSet;
}
}
这个解决方法还是有些粗糙,并且不够通用,如果你想要一个更强大和通用的数据控制解决方案,那么,我还是那句老话,请使用工作流机制!!!
如果想让用户自己设置权限,并且具体什么样的权限也未知,还要把权限这块儿独立出来,可能就有点麻烦了. 我认为能不能实现还是个问题?
总感觉我们经理的思路有问题....N烦
如果想让用户自己设置权限,并且具体什么样的权限也未知,还要把权限这块儿独立出来,可能就有点麻烦了. 能不能实现还是个问题.
总感觉我们经理的思路有问题....N烦