摘要:本文通过示例和概念解释,阐述了企业信息化软件中权限设计的一般方法,帮助架构师能够快速的建立客户所需的安全模型。

企业信息化软件的权限设计一直是个非常棘手的工作,因为在企业信息化软件中,需求总是千奇百怪,很难有一个统一的方案,能够既满足复杂的权限需求,又能够保证程序的高性能和易维护性。

现状

在设计权限系统时,一般有两种取向,一种是试图做一种“全面解决方案”,例如可以对单条的文档进行独立的权限设置,或者设计出让人眼花缭乱的设置界 面;另外一种倾向是定制化的权限系统,根据不同客户的管理思路,建立特有的权限模型。例如有些企业希望以管辖范围划分权限,而有些希望以单据的客户级别划 分。

第一种方案问题在于使用了抽象化的概念描述权限,从而使用户操作复杂烦琐,而且必然带来程序执行的缓慢。第二种方案用户理解方便,执行效率高,但定制化的成本高昂,且无法处理非常复杂的权限需求。

在本篇文章中,我将描述偏向第二种方案的设计,但更强调定制化需求抽象化出来的理论,以及反过来根据此理论简化权限模型的设计。

从一个例子开始

首先从一个生活中简单的例子开始,我们将从生活中学习到如何设计权限。

一家运动俱乐部,可以购买两种会员卡,一个是金卡,一个是银卡,金卡呢,当然是可以玩高级的设备,还有个VIP包厢,至于银卡你就只能到大厅玩了。

现在某人购买了一张金卡,跑到VIP包厢门口,对门口的服务员说:俺要进去,服务员检查了金卡,想了想可不可以去包厢(亿分之一秒完成此思考),通过,进去吧。

好了,我们开始“理论化”一下这个故事。

资源:是指需要保护的内容,在这个例子中,大厅和包厢就是两种资源,资源包含一些属性,以便判断是否可以使用的依据,例如大厅和包厢都包含一个级别的属性,这些属性本身并不直接描述使用权限。在ERP中,例如订单、客户等等都是资源。

证书:有的称为凭证,是指可以被管理方承认的凭证,在这个俱乐部中,金卡和银卡都是被承认的证书,当然,别的俱乐部的金卡是不被承认的(除非业务更复杂些),在ERP中,常见的证书有:角色、登录人Id、所在部门、职位等等都可以作为证书。

主体:指试图访问资源的发起人,在这个例子里,那个客户就是一个主体,一个主体可以拥有一个或多个的证书,在ERP中,一个登录人员就可能拥有多个角色证书。

授权:是指资源的管理方事先定义的一组可访问的规则,例如刚才说的金卡可以玩VIP包厢就是一个由俱乐部经理事先定义的规则。一份授权应该描述为:

主体必须具有哪些证书,才能对哪些资源执行哪些动作。

在上面的话中,有一个词:动作,在这个例子中动作指:用。正如你所想像的,俱乐部经理是没有授权金卡客户可以砸包厢。J

在ERP软件中,我们经常看见的权限设置界面就是典型的授权定义过程。

最终,当某个主体试图访问某些资源时,管理方根据主体持有的证书,在授权定义中最终枚举出可访问的资源。

ERP中运用这些概念

确定主体

通常在ERP中主体的确认非常的简单,都是当前登录的用户,一般都设计成ERP独立的验证过程,在.NET中需要自己定义 System.Security.Principal.IPrincipal接口的实现类来描述主体,如果程序很简单你可以直接使用 System.Security.Principal.GenericPrincipal。

某些ERP软件集成Windows验证,也支持ERP软件的自身验证,一般我们会在ERP自己的账户中定义一个映射的Windows账户,当使用集成验证时,通过当前的Windows账户找到ERP的账户,最终以ERP的账户统一作为主体。

还有一种情况就是后台自动服务,例如某些大型的ERP会包含自己的后台服务,他定期执行诸如报表生成的操作,这种程序可以定义诸如LocalService这样的特殊账户作为主体。你可以在Windows的服务中看见这种例子。

在.NET中,主体包含了标识对象的引用,但没有直接包含证书的定义。

下面是在.NET中设置标识对象的简单例子:

Console.WriteLine("Login...");

Console.Write("user name:");

string userName = Console.ReadLine();

GenericIdentity currentUser = new GenericIdentity(userName);

GenericIdentity是.NET内置的一个简单标识对象,.NET中关于主体还提供了WindowsPrincipal,以及Web中的System.Web.Security.RolePrincipal。

定义可用的证书

要在ERP运用这些概念,你首先需要归纳主体所有可用的证书,即可以被管理方承认的凭证种类。例如在下面的例子中:

财务经理可以查看所有员工的工资

在这个例子中,“财务经理”,这个角色就是一个被认可的证书,即角色是一种证书。

每个员工可以查看自己的工资

此例中,“员工”,这个人员档案也是一种被认可的证书,有些软件将登录的操作员档案和员工档案分离,那么此操作员映射的员工就是此操作员的证书。因此,员工也是一种证书。

区域经理可以查看他分管区域的销售单

区域经理在登录时,他所分管的区域定义也将成为他的证书,这是一种典型的与特定业务挂钩的证书。所以根据不同的业务需求,可能会定义除角色和员工之外的更多证书,他们都将在用户登录后,寄生在主体对象上一起放入会话中。

当然,最常见的证书还是角色,作为习惯,或者说作为好的设计,我们都会设计以下特殊的角色:

角色

描述

Everyone

表示任何人,不管你是否有账户,不需要指定都属于此角色;

Guest

表示任何尚未登录的人,在登录后自动丢失这个角色证书;

User

表示普通用户,没有任何强制性规则,一般会在建立一个新用户时自动附加;

Administrator

好东西,开发人员都懂

Backup Operator

很多设计人员忽略了他,其实这个角色很适合IT维护人员。

在.NET中,证书是放在主体中一起存放的,例如下面的例子中描述了如何将users和administrators两个角色放在主体中。

GenericPrincipal p = new GenericPrincipal(

currentUser,

new string[] { "users", "administrators" });

System.Threading.Thread.CurrentPrincipal = p;

一般的,在ERP中,你需要自己定义主体对象,他是符合IPrincipal接口的对象,以便按照你自己的方式存储各种类型的证书。

授权

授权至少包含以下信息:

l 此授权要求的证书,可以是一个或多个,通常要求的证书是一个角色证书;

l 此授权被授予到哪些实体上,这属于安全策略的一部分,例如仅应用于订单,但也可以是一个模块,例如可以访问整个销售系统;

l 此授权被授予的动作,仍然属于安全策略的一部分,动作一般包括可见、只读、创建、更新、删除和生效等。

l 最后就是安全策略的特定化条件,例如:仅是自己的订单,表示:where OrderSheet.SheetId = @CurrentUserId;

可以看出,授权包含两个重要的部分:所需证书和安全策略。

授权中第一个部分是所需证书,下面的界面清楚的表明了授权和证书的关系:

授权的第二个部分包括了功能的安全策略和数据的安全策略,功能安全策略指对“功能”这个资源的访问授权,数据安全策略指对数据这个资源的访问授权。

上图表示了在这个授权中关于功能的安全策略定义。

关于数据的安全策略要看具体的业务实体设计,例如,订单实体支持客户字段,所以就能够支持客户级别的安全授权方式,而员工档案就没有客户字段,也就不可能支持客户级别的安全授权方式了。

一般的,常见的数据授权方式有:

l 实体包含所有者(Owner)属性,“所有者”是一个员工档案(或者是映射一个员工档案),表示此记录的持有人,与记录的数据本身没有直接关系,可以参考Microsoft CRM。

l 实体包含员工属性,和所有者相似,但和实际数据有关,例如工资单,权限是跟此工资单的工资人有关,比较多见于HR系统和办公自动化系统;

l 实体包含客户属性,按照客户的所属地区或者级别授权,多见于CRM或进销存系统;

l 实体包含所属部门属性,和所有者概念很相似,不同的是一个挂靠员工,一个挂靠部门,例如一个发货单包含发货仓库属性,一个生产计划单包含一个车间属性。多见于ERP系统。

下面的界面是按照管辖范围定义的数据授权。

一个授权只能是一种授权方式,一般我们在创建授权时就已经决定了。

提示:最佳的数据授权方式是动态数据的授权方式,而不是具体数据的授权,例如上图的:操作员直接管辖范围,这种授权使管理员不用为每个经理单独设置授权,大大减少管理的工作量。

一个系统中允许添加很多的授权。很多软件错误的将授权定义到角色上,这将大大限制授权定义的灵活性。这个问题大家可以参考Windows的设计,你注意到了吗?Windows的角色(用户组)编辑界面中是没有任何权限设置的部分。

.NET提供了描述安全策略的接口:System.Security.IPermission,这个接口定义了安全策略的以下行为:

方法

解释

Copy

创建并返回当前权限的相同副本

Demand

断言,如果不满足安全要求,则会在运行时引发 SecurityException

Intersect

创建并返回一个权限,该权限是当前权限和指定权限的交集

IsSubsetOf

确定当前权限是否为指定权限的子集

Union

创建一个权限,该权限是当前权限与指定权限的并集

这个接口还派生自System.Security.ISecurityEncodable,他定义使权限对象状态与 XML 元素表示形式进行相互转换的方法。

要实现这个接口老实说不是那么容易的事情,但更不幸的是这个东西根据不同业务需求还要定义不同的实现,看看.NET自己实现的类你就知道这个就是“工作量”了。

注意:恕我愚钝,我没有在.NET中表示授权的对象,只有他的子部分:安全策略。所以我是自己实现这个类的。

安全检查

做了这么多,终究是为了最后一个事情:安全检查。

首先在登录时,我们需要在主体上记录所有可用的证书。

当某个方法被调用时,方法内部首先就需要做安全检查,比较简单的安全检查是:断言,他的结果只有:通过或者不通过。如果不通过,一般会抛出异常。

这种检查一般适用于:

l 检查创建文档的权限;

l 检查对某个具体文档的操作;

另外一种是对数据的过滤,这种操作没有事先确定的数据,例如浏览就是典型的数据过滤方式的安全检查。

这两种方式的流程如下:

l 过滤出所有可用的授权,即当前用户拥有授权中要求的证书,即为可用授权;

l 再次过滤,保留当前操作的功能和动作在授权范围内的;

l 如果有任何一个安全策略表明为“最大集”,就认为最大权限;

l 将剩余授权中安全策略按照授权方式产生并集,每种授权方式形成一个并集;

l 如果不存在任何有效的授权,表示没有权限;

两种安全检查的最后一步流程不同,如果是断言方式,则:

l 将当前的资源对象传入安全策略中,检查此资源对象是否在安全策略范围之内,例如下面描述了一个订单在读取时的数据资源属性:

属性名

数据Id

00002

所有者

User001

对应客户分管地区

001

如果定义的安全策略中描述为:处理自己的管辖的地区,而当前用户管辖的就是001,则通过。

如果是数据过滤方式,则

l 将所有的并集以OR的方式拼接到执行的SQL中。

关于拒绝功能

在上面的所有授权定义中,都是正向授权,也有一种反向授权,即拒绝功能,这个功能可以参看NTFS系统的权限设计。

反向授权就是将正向授权的内容又“踢”出去,这在处理一些特殊的权限非常有用,例如原本授权财务可以编制本公司的所有工资单,可现在请过来的经理不归他管,这样就需要做一个正向授权:财务角色可以编辑本公司的工资单,然后做一个反向授权,加入那个经理。

关于ShareTo功能

我们可以看到,授权被定义在一个独立的文档中,但是在遇到那种规律性非常差的业务需求时,这种方式遇到了挑战。

因为缺乏规律性,所以不得不制作大量的独立授权,独立授权即那种单独给某个角色甚至某个用户的授权,随着授权的增多,程序变得缓慢无比,而且,由于授权是个敏感数据,所以只能由管理员制作,这势必增加了系统管理的工作量。

作为补充,有些系统设计了ShareTo功能,他的特点是授权被挂接到某个具体条目,例如下图是一个关于Account的ShareTo的数据库设计。

第一条ShareTo记录表明:Account中,记录编号为069111…的数据,允许User1读取。

这种设计实际上是在统一的授权外,又为这个档案单独建立了一个授权模型,将过多的独立授权集中到某几个档案上,好处是显而易见的,牺牲自己,解放大家吗。

使用ShareTo功能,你必须谨记以下几点:

l ShareTo功能带来灵活性,但同时带来了运行缓慢、管理无序;

l ShareTo必须依托在某个记录上,所以是没有Create这个动作的;

l 设计ShareTo功能必须考虑再次ShareTo问题,一般的我们都会在ShareTo中增加“再授权”动作。

总结

应该记住,世界上没有包治百病的灵丹妙药,任何一种权限设计模型都必须在性能、灵活性和可用性做出平衡。

在抉择使用那种安全模型时,经常犯的错误是“夸大”客户的需求和数据量,从而总是试图给客户一个大而全的权限系统,其实合适就好。

posted on 2011-07-20 18:16  代岳强  阅读(19277)  评论(3编辑  收藏  举报