下面我要说的是MIS系统权限管理的数据库设计及实现,当然,这些思路也可以推广开来应用,比如说在BBS中用来管理不同级别的用户权限。
权限设计通常包括数据库设计、应用程序接口(API)设计、程序实现三个部分。
这三个部分相互依存,密不可分,要实现完善的权限管理体系,必须考虑到每一个环节可行性与复杂程度甚至执行效率。
我们将权限分类,首先是针对数据存取的权限,通常有录入、浏览、修改、删除四种,其次是功能,它可以包括例如统计等所有非直接数据存取操作,另外,我们还可能对一些关键数据表某些字段的存取进行限制。除此,我想不出还有另外种类的权限类别。
完善的权限设计应该具有充分的可扩展性,也就是说,系统增加了新的其它功能不应该对整个权限管理体系带来较大的变化,要达到这个目的,首先是数据库设计合理,其次是应用程序接口规范。
我们先讨论数据库设计。通常我们使用关系数据库,这里不讨论基于Lotus产品的权限管理。
权限表及相关内容大体可以用六个表来描述,如下:
1 角色(即用户组)表:包括三个字段,ID,角色名,对该角色的描述;
2 用户表:包括三个或以上字段,ID,用户名,对该用户的描述,其它(如地址、电话等信息);
3 角色-用户对应表:该表记录用户与角色之间的对应关系,一个用户可以隶属于多个角色,一个角色组也可拥有多个用户。包括三个字段,ID,角色ID,用户ID;
4 限制内容列表:该表记录所有需要加以权限区分限制的数据表、功能和字段等内容及其描述,包括三个字段,ID,名称,描述;
5 权限列表:该表记录所有要加以控制的权限,如录入、修改、删除、执行等,也包括三个字段,ID,名称,描述;
6 权限-角色-用户对应表:一般情况下,我们对角色/用户所拥有的权限做如下规定,角色拥有明令允许的权限,其它一律禁止,用户继承所属角色的全部权限,在 此范围内的权限除明令禁止外全部允许,范围外权限除明令允许外全部禁止。该表的设计是权限管理的重点,设计的思路也很多,可以说各有千秋,不能生搬硬套说 某种方法好。对此,我的看法是就个人情况,找自己觉得合适能解决问题的用。
先说第一种也是最容易理解的方法,设计五个字段:ID,限制内容ID,权限ID,角色/用户类型(布尔型字段,用来描述一条记录记录的是角色权限还是用户权限),角色/用户ID,权限类型(布尔型字段,用来描述一条记录表示允许还是禁止)
好了,有这六个表,根据表六,我们就可以知道某个角色/用户到底拥有/禁止某种权限。
或者说,这么设计已经足够了,我们完全实现了所需要的功能:可以对角色和用户分别进行权限定制,也具有相当的可扩展性,比如说增加了新功能,我们只 需要添加一条或者几条记录就可以,同时应用程序接口也无须改动,具有相当的可行性。但是,在程序实现的过程中,我们发现,使用这种方法并不是十分科学,例 如浏览某个用户所拥有的权限时,需要对数据库进行多次(甚至是递归)查询,极不方便。于是我们需要想其它的办法。使用过Unix系统的人们都知道, Unix文件系统将对文件的操作权限分为三种:读、写和执行,分别用1、2、4三个代码标识,对用户同时具有读写权限的文件被记录为3,即1+2。我们也 可以用类似的办法来解决这个问题。初步的想法是修改权限列表,加入一个字段:标识码,例如,我们可以将录入权限标识为1,浏览权限标识为2,修改权限标识 为4,删除权限标识为8,执行权限标识为16,这样,我们通过权限累加的办法就可以轻易的将原本要分为几条记录描述的权限放在一起了,例如,假定某用户 ID为1,库存表对应的限制内容ID为2,同时规定角色类型为0、用户类型为1,我们就可以将该用户具有录入、浏览、修改、删除库存表的权限描述为: 2,15,1,1。
确实很简单,不是吗?甚至还有更过激的办法,将限制内容列表也加上一列,定义好标识码,这样,我们甚至可以用简单的一条记录描述某个用户具有的对全 部内容所具有的全部权限了。当然,这样做的前提是限制内容数量比较小,不然,呵呵,2的n次方递增起来可是数量惊人,不容易解析的。
从表面上看,上述方法足以达到实现功能、简化数据库设计及实现的复杂度这个目的,但这样做有个弊端,我们所涉及的权限列表不是相互独立而是互相依赖 的,比如说修改权限,其实是包含浏览权限的,例如,我们可能只是简单的设置用户对库存表存取的权限值为录入+修改+删除(1+4+8=13),但事实上, 该用户具有(1+2+4+8=15)的权限,也就是说,在这种方案中,13=15。于是当我们调用API询问某用户是否具有浏览权限时,就必须判断该用户 是否具有对该数据表的修改权限,因此,如果不能在程序中固化权限之间的包含关系,就不能利用应用程序接口简单的做出判断。但这与我们的目的“充分的可扩展 性”矛盾。
这个问题如何解决?我想到了另外一种设置标识码的方法,那就是利用素数。我们不妨将录入、浏览、修改、删除、执行的基本标志码定为 2,3,5,7,11,当遇到权限互相包含的时候,我们将它的标识码设定为两个(或多个)基本标志码的乘积,例如,可以将“修改”功能的标志码定为3*5 =15,然后将所有的权限相乘,就得到了我们需要的最终权限标识值。这样,我们在询问用户是否具有某项权限的时候,只需要将最终的值分解成质因子,例如, 我们可以定义一个用户具有录入+修改+删除库存表的权限为 2*15*7=2*3*5*7,即表示,该用户具有了对库存表录入+浏览+修改+删除权限。
当然,对权限列表我们使用上述方法的前提是权限列表记录条数不会太多并且关系不是十分复杂,否则,光是解析权限代码就要机器忽悠半宿:)
我希望以上的分析是正确且有效的(事实上,我也用这些的方法在不止一套系统中实现),但无论如何,我觉得如此实现权限管理,只是考虑了数据库设计和 应用程序接口两部分内容,对于实现,还是显得很费劲。因此,我恳请有过类似设计、实现经验的同志们提出建设性的意见和修改建议。
另外,关于数据库设计的思路还有使用二维表的,这将在以后的时间里讨论,关于应用程序接口的设计和实现我也将在利用另外篇幅和大家共同探讨,代码将用类C语法实现(我不喜欢pascal,抱歉)
欢迎朋友们和我联系,mailto:[email protected],也欢迎访问我和另外一位朋友共同建设的网站:http://www.91search.com,那里将有一个音乐搜索的工具软件提供下载。
========================================
关于权限包容关系通过角色和权限掩码来实现。
///
/// 权限保护类型枚举类型。
///
public enum ProtectEnum
{
/// 撤回权限保护类型
RevokeProtect = 0,
/// 授予权限保护类型
GrantProtect = 1,
/// 拒绝权限保护类型
DenyProtect = 2
}
///
/// 系统固定用户或角色枚举类型。
///
///
/// 管理员角色:16399 = 100000000001111
/// 所有者角色:16385 = 100000000000001
/// 只读者角色:16386 = 100000000000010
/// 安全员角色:16388 = 100000000000100
/// 配置员角色:16392 = 100000000001000
///
public enum FixedRoleEnum
{
///系统管理员固定用户
Administrator = 1,
///系统管理员固定角色
Administrators = 16399,
///所有者固定角色(具有读写操作之权限)
Authors = 16385,
///只读者固定角色(具有只读操作之权限)
Readers = 16386,
///系统安全管理员固定角色
Security = 16388,
///系统设置管理员固定角色
Setting = 16392
}
///
/// 系统权限枚举类型。
///
public enum PermissionEnum
{
/// “读取”权限
FetchPermission = 1,
/// “新增”权限
AddNewPermission = 2,
/// “更新”权限
UpdatePermission = 4,
/// “删除”权限
DeletePermission = 8,
/// “打印”权限
PrintPermission = 16,
/// 系统保留,应用于流程处理
FlowPermission = 1024,
/// 系统保留,应用于流程处理
VoidPermission = 2048
}
如果用户“Popeye”对“销售出仓单[2009]”系统对象具有读写(读取+修改+删除+新增)权限:(权限表定义如下TPermission)
FormID UID Permission
======= ==== ==========
2009 Popeye 1+2+4+8=15
***** 上面系统定义的默认权限肯定是不够系统使用的,那么还有一些权限(例如:报关系统中的“计算差异表”“制造申报单”等权限,就由系统再定义),其实不用太 担心会不够用的,因为在一个Form中不可能会出现所有权限情况,所以,系统自定义的权限掩码可重复使用在不同的表单中。*****
??建议不要把角色和用户分开两张表来存储(可参考MS-SQL Server中的sys_users表),因为在后面的权限定义表需要引用这个表的UID(其可为用户或角色,SQL中是使用UID的数值范围来区别用户 与角色的,建议也如此。),版主说的角色与用户分开对待权限设置,这点我不赞成。因为角色只不过是一种用户组,其应该享用用户的权限定义范围,在其下属的 角色成员(注意角色成员不同于用户或角色哦,其可以为角色也可以为用户)均默认继承其定义的权限,除非角色成员重新指派其上级角色定义的权限字。下面给出 我的相关表定义:
TUser(用户或角色表)
===================
(PK)UID int NOT NULL(主键)
Name nvarchar(50) NOT NULL(唯一性约束)
FullName nvarchar(100) NULL
Description nvarchar(255) NULL
MasterNo varchar(25) NULL(注:该字段对应为员工表中的员工编号,通过该字段就可以关联知道该用户或角色所属的员工了,在企业管理系统中很有用啊!)
TMember(用户与角色关系表)
=========================
(*PK)RoleID int NOT NULL
(*PK)UserID int NOT NULL
TPermission(用户权限表)
=======================
(*PK)FormID int NOT NULL(表示系统中各个模块或表单的唯一编号)
(*PK)UserID int NOT NULL(用户或角色编号)
Protect bit NOT NULL(1:表示显示授予权限;0:表示显示拒绝权限)
Permission int NOT NULL(权限掩码和)
***** 如果哪位兄弟有意研究权限与流程定制方面的东东,相信找偶是没错的了!!!呵呵~~~ 老板,给分啊~~~~~×××××
==========================================
以上的方法与我做的项目的方法基本一致,现摘一部分的表结构,以供大家参考
create table t_workelement /*工作元素表*/
(
code varchar(20) not null, /*元素的代码,唯一*/
name varchar(50) not null UNIQUE,/*元素的名称,唯一*/
type int not null, /*类型 0-单据操作 1-报表操作 2-功能操作*/
bcode varchar(20) null, /*对应操作的单据/报表/功能的代码*/
style int null, /*单据:类型 0-查看 1-新增 2-修改 3-删除*/
/*报表:无*/
/*功能:无*/
term ntext null, /*单据:查看/修改/删除时要符合的条件,如"{$承揽合同.编号}=12 {$承揽合同.名称}<>'afd'"*/
primary key(code)
)
go
drop table t_role
go
create table t_role /*角色表*/
(
name varchar(30) not null,
category varchar(50) null,
remark varchar(100) null,
primary key(name)
)
go
drop table t_roleelement
go
create table t_roleelement /*角色元素操作表*/
(
rname varchar(30) not null, /*角色名称*/
ecode varchar(20) not null, /*元素的代码*/
primary key(rname,ecode)
)
go
drop table t_users
go
create table t_users /*用户表*/
(
name varchar(20) not null, /*用户的名称*/
dcode varchar(20) not null, /*所属的部门*/
category varchar(50) null, /*用户的类别*/
pswd varchar(15) null, /*密码*/
primary key(name)
)
go
/*插入系统管理员*/
INSERT INTO t_users
(
name,
dcode,
category,
pswd
)
VALUES
(
'Admini',
'system',
'超级用户',
''
)
go
drop table t_userrole
go
create table t_userrole /*用户角色表*/
(
uname varchar(20) not null, /*用户名称*/
rname varchar(30) not null, /*角色的名称*/
primary key(uname,rname)
)
go
INSERT INTO t_userrole
(
uname,
rname
)
VALUES
(
'Admini',
'系统管理员'
)
go
drop table t_dept
go
create table t_dept /*部门表*/
(
code varchar(20) not null, /*部门的代码*/
name varchar(50) not null UNIQUE,/*部门的名称*/
type varchar(10) null, /*部门的类别 行政 仓库 车间*/
subtype varchar(16) null, /*子类别 成品仓库 原料仓库自定义*/
primary key(code)
)
go
/*插入系统管理部*/
INSERT INTO t_dept
(
code,
name,
type
)
VALUES
(
'system',
'系统管理部',
'行政'
)
go
续:关于权限系统的设计
jackyz Dec 7, 2002 1:00 AM
source:http://www.jdon.com/jive/article.jsp?forum=46&thread=4110&message=438816
Note:
首先,向版主致歉。这原是关于权限系统设计问题的回帖,本不应该另开新贴。而在下的另开新贴,一方面是因为本人的观点中与很多人 的观点差别较大,另一方面也担心其会被“埋没”在原贴的大量回帖中。此外,本贴的内容整理与编排耗费了我周末接近一整个晚上的时间,包含了我对近期项目中 权限系统的思考与总结,希望引来大家足够的目光和指导。请版主体谅。
权限系统,其重要性当然不言自明。看见大家的方案,有相当多优秀的创意与经验,我的项目也有所涉及,以下说明的是我最近在一个企业 EJB 项目中初步实现的方案(以及设计考量),望与诸位共商榷。
前言:
权限往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“who 对 what(which) 进行 how 的操作”的逻辑表达式是否为真。
针对不同的应用,需要根据项目的实际情况和具体架构,在维护性、灵活性、完整性等N多个方案之间比较权衡,选择符合的方案。
目标:
这个权限系统的设计,我主要考虑了这么几个目标:
直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要,系统不辞劳苦的实现了组的继承,除了功能的必须,更主要的就是因为它足够直观。
简 单,包括概念数量上的简单和意义上的简单还有功能上的简单。想用一个权限系统解决所有的权限问题是不现实的。设计中将常常变化的“定制”特点比较强的部分 判断为业务逻辑,而将常常相同的“通用”特点比较强的部分判断为权限逻辑就是基于这样的思路。此外,同时具备 Role 和 Group 的系统难于理解,权衡之下,摒弃了 Role 概念。
扩展,我在以前的项目中也实现过基于 Role 概念的权限系统,但效果不太理想。之所以在这里摒弃 Role 的概念,另一个原因就是因为它不易扩展。通常 Role 的设计方式意味着预先已经定好了一组权限,这样的“预先设计”,常常会鼓励程序员 hardcode 这些权限相关的部分,而,如果这么做的话,当需要重新定义 Role 时,扩展就会变得极为困难。而采用可继承的 Group 概念在支持权限以组方式定义的同时有效避免了重定义时在扩展上的困难。
名词:
下面两个名词极其重要,是整个设计问题边界定义的关键,或许我的理解与通常的理解不同,在此有必要特别澄清。
粗粒度:表示类别级,即,仅考虑对象的类别,不考虑对象的某个特定实例。比方,用户管理中,创建、删除,对所有的用户都一视同仁,并不区分操作的具体对象实例。
细粒度:表示实例级,即,需要考虑具体对象的实例,当然,细粒度是在考虑粗粒度的对象类别之后才再考虑特定实例。比方,合同管理中,列表、删除,需要区分该合同实例是否为当前用户所创建。
原则:
权 限逻辑配合业务逻辑。即,权限系统以为业务逻辑提供服务为目标。纯粹纸面意义的极其复杂和精巧的权限系统,这里不作讨论。相当多细粒度的权限问题因其极其 独特而不具通用意义,它们也能被理解为是“业务逻辑”的一部分。比方,要求:“合同资源只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能 够浏览”。这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题。在这里我认为它是业务逻辑问题,在整个权限系统的架构设计之中不予过多考 虑。当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。即,设计原则归结为:“系统只提供粗粒度的权限, 细粒度的权限被认为是业务逻辑的职责”。
需要再次强调的是,这里表述的权限系统仅是一个“不完全”的权限系统,即,它不提供所有关于权限 的问题的解决方法。它提供一个基础,并解决那些具有“共性”的(或者说粗粒度的)部分。在这个基础之上,根据“业务逻辑”的独特权限需求,编码实现剩余部 分(或者说细粒度的)部分,才算完整。回到权限的问题公式,我的设计仅解决了 who + what + how 的问题,which 的权限问题留给业务逻辑解决。
概念:
User:用户。解决 who 的问题。
Group:组。权限分配的单位与载体。权限不考虑分配给特定的用户。组可以包括组(以实现权限的继承)。
Operate:操作。表明对 what 的 how 操作。
说明:
User
与大家的都一样,没什么好说的。
Group
与 大家的类似,所不同的是,Group 要实现继承。即,在创建时必须要指定该 Group 的 Parent 是什么 Group 。在粗粒度控制上,可以认为,只要某用户直接或者间接的属于某个 Group 那么它就具备这个 Group 的所有操作许可。细粒度控制上,在业务逻辑的判断中,User 仅应关注其直接属于的 Group ,用来判断是否“同组”,间接的 Group 对权限的控制意义不大,试设想存在一个 All User 的 Group 是所有 Group 的祖先,这样的情形下,判断的结果不具备实际意义。
User 与 Group 是多对多的关系。即,一个 User 可以属于多个 Group 之中,一个 Group 可以包括多个 User 。
子 Group 与 父 Group 是多对一的关系。即,一个子 Group 只能有一个父 Group ,一个父 Group 可以包括多个子 Group 。
Operate
某种意义上类似于大家的 Resource + Privilege 概念,但,这里的 Resource 仅包括 Resource Type 不表示 Resource Instance。Operate 概念上与大家的观点区别比较,后面有详细的解释。
Group 与 Operate 是多对多的关系。
各概念的关系图示如下:
User
|*
|
|* 1
Group---+
|* |* |
| +---+
|*
Operate
解释:
Operate 的定义包括了 Resource Type 和 Method 概念。即, what 和 how 的概念。之所以将 what 和 how 绑定在一起作为一个 Operate 概念而不是分开建模再建立关联,这是因为很多的 how 对于某 what 才有意义。比方,发布操作对新闻对象才有意义,对用户对象则没有意义。
how 本身的意义也有所不同,这里并非仅定义类 UNIX 的 RWX 三种操作,这样的定义对于文件系统是合理的,但,对于其他的应用领域或许就不是那么足够了。具体来说,对于每一个 what 可以定义 N 种操作。比方,对于合同这类对象,可以定义创建操作、提交操作、检查冲突操作等。可以认为,how 概念对应于每一个商业方法。
其中,与 具体用户身份相关的操作既可以定义在操作的业务逻辑之中,也可以定义在操作级别。比方,创建者的浏览视图与普通用户的浏览视图要求内容不同。你既可以在外 部定义两个操作方法,也可以在一个操作方法的内部根据具体逻辑进行处理。具体应用哪一种方式应依据实际情况进行处理。
这样的架构,应能在易于理解和管理的情况下,满足绝大部分粗粒度权限控制的功能需要。但是,除了粗粒度权限,无可否认,系统中必然还会包括无数对具体 Instance 的细粒度权限。这些问题,被留给业务逻辑来解决,这样的考虑基于以下两点。
一 方面,细粒度的权限判断必须要在资源上建模权限分配的支持信息才可能得以实现。比方,如果要求创建者和普通用户看到不同的信息内容,那么,资源本身应该有 其创建者的信息。如同 Unix 的每一个文件(资源),都定义了对 Owner, Group, All 的不同操作属性。
另一方面, 细粒度的权限常常具有相当大的业务逻辑相关性。对不同的业务逻辑,常常意味着完全不同的权限判定原则和策略。相比之下,粗粒度的权限更具通用性,将其实现 为一个架构,更有重用价值;而将细粒度的权限判断实现为一个架构级别的东西就显得繁琐,而且不是那么的有必要,用定制的代码来实现就更简洁,更灵活。