置顶随笔
 前言:  
  权限往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“Who对What(Which)进行How的操作”的逻辑表达式是否为真。针对不同的应用,需要根据项目的实际情况和具体架构,在维护性、灵活性、完整性等N多个方案之间比较权衡,选择符合的方案。  
  目标:  
  直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要,系统不辞劳苦的实现了组的继承,除了功能的必须,更主要的就是因为它足够直观。  
  简单,包括概念数量上的简单和意义上的简单还有功能上的简单。想用一个权限系统解决所有的权限问题是不现实的。设计中将常常变化的“定制”特点比较强的部分判断为业务逻辑,而将常常相同的“通用”特点比较强的部分判断为权限逻辑就是基于这样的思路。  
  扩展,采用可继承在扩展上的困难。的Group概念在支持权限以组方式定义的同时有效避免了重定义时  
  现状:  
  对于在企业环境中的访问控制方法,一般有三种:  
  1.自主型访问控制方法。目前在我国的大多数的信息系统中的访问控制模块中基本是借助于自主型访问控制方法中的访问控制列表(ACLs)。  
  2.强制型访问控制方法。用于多层次安全级别的军事应用。  
  3.基于角色的访问控制方法(RBAC)。是目前公认的解决大型企业的统一资源访问控制的有效方法。其显著的两大特征是:1.减小授权管理的复杂性,降低管理开销。2.灵活地支持企业的安全策略,并对企业的变化有很大的伸缩性。  
  名词:  
  粗粒度:表示类别级,即仅考虑对象的类别(the   type   of   object),不考虑对象的某个特  
  定实例。比如,用户管理中,创建、删除,对所有的用户都一视同仁,并不区分操作的具体对象实例。  
  细粒度:表示实例级,即需要考虑具体对象的实例(the   instance   of   object),当然,细  
  粒度是在考虑粗粒度的对象类别之后才再考虑特定实例。比如,合同管理中,列表、删除,需要区分该合同实例是否为当前用户所创建。  
  原则:  
  权限逻辑配合业务逻辑。即权限系统以为业务逻辑提供服务为目标。相当多细粒度的权限问题因其极其独特而不具通用意义,它们也能被理解为是“业务逻辑”的一部分。比如,要求:“合同资源只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能够浏览”。这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题。在这里它是业务逻辑问题,在整个权限系统的架构设计之中不予过多考虑。当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。即,设计原则归结为:“系统只提供粗粒度的权限,细粒度的权限被认为是业务逻辑的职责”。  
  需要再次强调的是,这里表述的权限系统仅是一个“不完全”的权限系统,即,它不提供所有关于权限的问题的解决方法。它提供一个基础,并解决那些具有“共性”的(或者说粗粒度的)部分。在这个基础之上,根据“业务逻辑”的独特权限需求,编码实现剩余部分(或者说细粒度的)部分,才算完整。回到权限的问题公式,通用的设计仅解决了Who+What+How   的问题,其他的权限问题留给业务逻辑解决。  
  概念:  
  Who:权限的拥用者或主体(Principal、User、Group、Role、Actor等等)  
  What:权限针对的对象或资源(Resource、Class)。  
  How:具体的权限(Privilege,   正向授权与负向授权)。  
  Role:是角色,拥有一定数量的权限。  
  Operator:操作。表明对What的How   操作。  
  说明:  
  User:与   Role   相关,用户仅仅是纯粹的用户,权限是被分离出去了的。User是不能与   Privilege   直接相关的,User   要拥有对某种资源的权限,必须通过Role去关联。解决   Who   的问题。  
  Resource:就是系统的资源,比如部门新闻,文档等各种可以被提供给用户访问的对象。资源可以反向包含自身,即树状结构,每一个资源节点可以与若干指定权限类别相关可定义是否将其权限应用于子节点。  
  Privilege:是Resource   Related的权限。就是指,这个权限是绑定在特定的资源实例上的。比如说部门新闻的发布权限,叫做"部门新闻发布权限"。这就表明,该Privilege是一个发布权限,而且是针对部门新闻这种资源的一种发布权限。Privilege是由Creator在做开发时就确定的。权限,包括系统定义权限和用户自定义权限用户自定义权限之间可以指定排斥和包含关系(如:读取,修改,管理三个权限,管理   权限   包含   前两种权限)。Privilege   如"删除"   是一个抽象的名词,当它不与任何具体的   Object   或   Resource   绑定在一起时是没有任何意义的。拿新闻发布来说,发布是一种权限,但是只说发布它是毫无意义的。因为不知道发布可以操作的对象是什么。只有当发布与新闻结合在一起时,才会产生真正的   Privilege。这就是   Privilege   Instance。权限系统根据需求的不同可以延伸生很多不同的版本。  
  Role:是粗粒度和细粒度(业务逻辑)的接口,一个基于粗粒度控制的权限框架软件,对外的接口应该是Role,具体业务实现可以直接继承或拓展丰富Role的内容,Role不是如同User或Group的具体实体,它是接口概念,抽象的通称。  
  Group:用户组,权限分配的单位与载体。权限不考虑分配给特定的用户。组可以包括组(以实现权限的继承)。组可以包含用户,组内用户继承组的权限。Group要实现继承。即在创建时必须要指定该Group的Parent是什么Group。在粗粒度控制上,可以认为,只要某用户直接或者间接的属于某个Group那么它就具备这个Group的所有操作许可。细粒度控制上,在业务逻辑的判断中,User仅应关注其直接属于的Group,用来判断是否“同组”   。Group是可继承的,对于一个分级的权限实现,某个Group通过“继承”就已经直接获得了其父Group所拥有的所有“权限集合”,对这个Group而言,需要与权限建立直接关联的,仅是它比起其父Group需要“扩展”的那部分权限。子组继承父组的所有权限,规则来得更简单,同时意味着管理更容易。为了更进一步实现权限的继承,最直接的就是在Group上引入“父子关系”。  
  User与Group是多对多的关系。即一个User可以属于多个Group之中,一个Group可以包括多个User。子Group与父Group是多对一的关系。Operator某种意义上类似于Resource   +   Privilege概念,但这里的Resource仅包括Resource   Type不表示Resource   Instance。Group   可以直接映射组织结构,Role   可以直接映射组织结构中的业务角色,比较直观,而且也足够灵活。Role对系统的贡献实质上就是提供了一个比较粗颗粒的分配单位。  
  Group与Operator是多对多的关系。各概念的关系图示如下:  
  解释:  
  Operator的定义包括了Resource   Type和Method概念。即,What和How的概念。之所以将What和How绑定在一起作为一个Operator概念而不是分开建模再建立关联,这是因为很多的How对于某What才有意义。比如,发布操作对新闻对象才有意义,对用户对象则没有意义。  
  How本身的意义也有所不同,具体来说,对于每一个What可以定义N种操作。比如,对于合同这类对象,可以定义创建操作、提交操作、检查冲突操作等。可以认为,How概念对应于每一个商业方法。其中,与具体用户身份相关的操作既可以定义在操作的业务逻辑之中,也可以定义在操作级别。比如,创建者的浏览视图与普通用户的浏览视图要求内容不同。既可以在外部定义两个操作方法,也可以在一个操作方法的内部根据具体逻辑进行处理。具体应用哪一种方式应依据实际情况进行处理。
 
这样的架构,应能在易于理解和管理的情况下,满足绝大部分粗粒度权限控制的功能需要。但是除了粗粒度权限,系统中必然还会包括无数对具体Instance的细粒度权限。这些问题,被留给业务逻辑来解决,这样的考虑基于以下两点:  
  一方面,细粒度的权限判断必须要在资源上建模权限分配的支持信息才可能得以实现。比如,如果要求创建者和普通用户看到不同的信息内容,那么,资源本身应该有其创建者的信息。另一方面,细粒度的权限常常具有相当大的业务逻辑相关性。对不同的业务逻辑,常常意味着完全不同的权限判定原则和策略。相比之下,粗粒度的权限更具通用性,将其实现为一个架构,更有重用价值;而将细粒度的权限判断实现为一个架构级别的东西就显得繁琐,而且不是那么的有必要,用定制的代码来实现就更简洁,更灵活。  
  所以细粒度控制应该在底层解决,Resource在实例化的时候,必需指定Owner和GroupPrivilege在对Resource进行操作时也必然会确定约束类型:究竟是OwnerOK还是GroupOK还是AllOK。Group应和Role严格分离User和Group是多对多的关系,Group只用于对用户分类,不包含任何Role的意义;Role只授予User,而不是Group。如果用户需要还没有的多种Privilege的组合,必须新增Role。Privilege必须能够访问Resource,同时带User参数,这样权限控制就完备了。  
  思想:  
  权限系统的核心由以下三部分构成:1.创造权限,2.分配权限,3.使用权限,然后,系统各部分的主要参与者对照如下:1.创造权限   -   Creator创造,2.分配权限   -   Administrator   分配,3.使用权限   -   User:  
  1.   Creator   创造   Privilege,   Creator   在设计和实现系统时会划分,一个子系统或称为模块,应该有哪些权限。这里完成的是   Privilege   与   Resource   的对象声明,并没有真正将   Privilege   与具体Resource   实例联系在一起,形成Operator。  
  2.   Administrator   指定   Privilege   与   Resource   Instance   的关联。在这一步,   权限真正与资源实例联系到了一起,   产生了Operator(Privilege   Instance)。Administrator利用Operator这个基本元素,来创造他理想中的权限模型。如,创建角色,创建用户组,给用户组分配用户,将用户组与角色关联等等...这些操作都是由   Administrator   来完成的。  
  3.   User   使用   Administrator   分配给的权限去使用各个子系统。Administrator   是用户,在他的心目中有一个比较适合他管理和维护的权限模型。于是,程序员只要回答一个问题,就是什么权限可以访问什么资源,也就是前面说的   Operator。程序员提供   Operator   就意味着给系统穿上了盔甲。Administrator   就可以按照他的意愿来建立他所希望的权限框架可以自行增加,删除,管理Resource和Privilege之间关系。可以自行设定用户User和角色Role的对应关系。(如果将   Creator看作是   Basic   的发明者,   Administrator   就是   Basic   的使用者,他可以做一些脚本式的编程)   Operator是这个系统中最关键的部分,它是一个纽带,一个系在Programmer,Administrator,User之间的纽带。  
  用一个功能模块来举例子。  
  一.建立角色功能并做分配:  
  1.如果现在要做一个员工管理的模块(即Resources),这个模块有三个功能,分别是:增加,修改,删除。给这三个功能各自分配一个ID,这个ID叫做功能代号:  
  Emp_addEmp,Emp_deleteEmp,Emp_updateEmp。  
  2.建立一个角色(Role),把上面的功能代码加到这个角色拥有的权限中,并保存到数据库中。角色包括系统管理员,测试人员等。  
  3.建立一个员工的账号,并把一种或几种角色赋给这个员工。比如说这个员工既可以是公司管理人员,也可以是测试人员等。这样他登录到系统中将会只看到他拥有权限的那些模块。  
  二.把身份信息加到Session中。  
  登录时,先到数据库中查找是否存在这个员工,如果存在,再根据员工的sn查找员工的权限信息,把员工所有的权限信息都入到一个Hashmap中,比如就把上面的Emp_addEmp等放到这个Hashmap中。然后把Hashmap保存在一个UserInfoBean中。最后把这个UserInfoBean放到Session中,这样在整个程序的运行过程中,系统随时都可以取得这个用户的身份信息。  
  三.根据用户的权限做出不同的显示。  
  可以对比当前员工的权限和给这个菜单分配的“功能ID”判断当前用户是否有打开这个菜单的权限。例如:如果保存员工权限的Hashmap中没有这三个ID的任何一个,那这个菜单就不会显示,如果员工的Hashmap中有任何一个ID,那这个菜单都会显示。    
  对于一个新闻系统(Resouce),假设它有这样的功能(Privilege):查看,发布,删除,修改;假设对于删除,有"新闻系统管理者只能删除一月前发布的,而超级管理员可删除所有的这样的限制,这属于业务逻辑(Business   logic),而不属于用户权限范围。也就是说权限负责有没有删除的Permission,至于能删除哪些内容应该根据UserRole   or   UserGroup来决定(当然给UserRole   or   UserGroup分配权限时就应该包含上面两条业务逻辑)。  
  一个用户可以拥有多种角色,但同一时刻用户只能用一种角色进入系统。角色的划分方法可以根据实际情况划分,按部门或机构进行划分的,至于角色拥有多少权限,这就看系统管理员赋给他多少的权限了。用户—角色—权限的关键是角色。用户登录时是以用户和角色两种属性进行登录的(因为一个用户可以拥有多种角色,但同一时刻只能扮演一种角色),根据角色得到用户的权限,登录后进行初始化。这其中的技巧是同一时刻某一用户只能用一种角色进行登录。  
  针对不同的“角色”动态的建立不同的组,每个项目建立一个单独的Group,对于新的项目,建立新的   Group   即可。在权限判断部分,应在商业方法上予以控制。比如:不同用户的“操作能力”是不同的(粗粒度的控制应能满足要求),不同用户的“可视区域”是不同的(体现在对被操作的对象的权限数据,是否允许当前用户访问,这需要对业务数据建模的时候考虑权限控制需要)。   
 
扩展性:  
  有了用户/权限管理的基本框架,Who(User/Group)的概念是不会经常需要扩展的。变化的可能是系统中引入新的   What   (新的Resource类型)或者新的How(新的操作方式)。那在三个基本概念中,仅在Permission上进行扩展是不够的。这样的设计中Permission实质上解决了How   的问题,即表示了“怎样”的操作。那么这个“怎样”是在哪一个层次上的定义呢?将Permission定义在“商业方法”级别比较合适。比如,发布、购买、取消。每一个商业方法可以意味着用户进行的一个“动作”。定义在商业逻辑的层次上,一方面保证了数据访问代码的“纯洁性”,另一方面在功能上也是“足够”的。也就是说,对更低层次,能自由的访问数据,对更高层次,也能比较精细的控制权限。  
  确定了Permission定义的合适层次,更进一步,能够发现Permission实际上还隐含了What的概念。也就是说,对于What的How操作才会是一个完整的Operator。比如,“发布”操作,隐含了“信息”的“发布”概念,而对于“商品”而言发布操作是没有意义的。同样的,“购买”操作,隐含了“商品”的“购买”概念。这里的绑定还体现在大量通用的同名的操作上,比如,需要区分“商品的删除”与“信息的删除”这两个同名为“删除”的不同操作。  
  提供权限系统的扩展能力是在Operator   (Resource   +   Permission)的概念上进行扩展。Proxy   模式是一个非常合适的实现方式。实现大致如下:在业务逻辑层(EJB   Session   Facade   [Stateful   SessionBean]中),取得该商业方法的Methodname,再根据Classname和   Methodname   检索Operator   数据,然后依据这个Operator信息和Stateful中保存的User信息判断当前用户是否具备该方法的操作权限。  
  应用在   EJB   模式下,可以定义一个很明确的   Business层次,而一个Business   可能意味着不同的视图,当多个视图都对应于一个业务逻辑的时候,比如,Swing   Client以及   Jsp   Client   访问的是同一个   EJB   实现的   Business。在   Business   层上应用权限较能提供集中的控制能力。实际上,如果权限系统提供了查询能力,那么会发现,在视图层次已经可以不去理解权限,它只需要根据查询结果控制界面就可以了。  
  灵活性:  
  Group和Role,只是一种辅助实现的手段,不是必需的。如果系统的Role很多,逐个授权违背了“简单,方便”的目的,那就引入Group,将权限相同的Role组成一个Group进行集中授权。Role也一样,是某一类Operator的集合,是为了简化针对多个Operator的操作。  
  Role把具体的用户和组从权限中解放出来。一个用户可以承担不同的角色,从而实现授权的灵活性。当然,Group也可以实现类似的功能。但实际业务中,Group划分多以行政组织结构或业务功能划分;如果为了权限管理强行将一个用户加入不同的组,会导致管理的复杂性。  
  Domain的应用。为了授权更灵活,可以将Where或者Scope抽象出来,称之为Domain,真正的授权是在Domain的范围内进行,具体的Resource将分属于不同的Domain。比如:一个新闻机构有国内与国外两大分支,两大分支内又都有不同的资源(体育类、生活类、时事政治类)。假如所有国内新闻的权限规则都是一样的,所有国外新闻的权限规则也相同。则可以建立两个域,分别授权,然后只要将各类新闻与不同的域关联,受域上的权限控制,从而使之简化。  
  权限系统还应该考虑将功能性的授权与资源性的授权分开。很多系统都只有对系统中的数据(资源)的维护有权限控制,但没有对系统功能的权限控制。  
  权限系统最好是可以分层管理而不是集中管理。大多客户希望不同的部门能且仅能管理其部门内部的事务,而不是什么都需要一个集中的Administrator或Administrators组来管理。虽然你可以将不同部门的人都加入Administrators组,但他们的权限过大,可以管理整个系统资源而不是该部门资源。  
  正向授权与负向授权:正向授权在开始时假定主体没有任何权限,然后根据需要授予权限,适合于权限要求严格的系统。负向授权在开始时假定主体有所有权限,然后将某些特殊权限收回。  
  权限计算策略:系统中User,Group,Role都可以授权,权限可以有正负向之分,在计算用户的净权限时定义一套策略。  
  系统中应该有一个集中管理权限的AccessService,负责权限的维护(业务管理员、安全管理模块)与使用(最终用户、各功能模块),该AccessService在实现时要同时考虑一般权限与特殊权限。虽然在具体实现上可以有很多,比如用Proxy模式,但应该使这些Proxy依赖于AccessService。各模块功能中调用AccessService来检查是否有相应的权限。所以说,权限管理不是安全管理模块自己一个人的事情,而是与系统各功能模块都有关系。每个功能模块的开发人员都应该熟悉安全管理模块,当然,也要从业务上熟悉本模块的安全规则。  
  技术实现:  
  1.表单式认证,这是常用的,但用户到达一个不被授权访问的资源时,Web容器就发  
  出一个html页面,要求输入用户名和密码。    
  2.一个基于Servlet   Sign   in/Sign   out来集中处理所有的Request,缺点是必须由应用程序自己来处理。  
  3.用Filter防止用户访问一些未被授权的资源,Filter会截取所有Request/Response,  
  然后放置一个验证通过的标识在用户的Session中,然后Filter每次依靠这个标识来决定是否放行Response。  
  这个模式分为:  
  Gatekeeper   :采取Filter或统一Servlet的方式。  
  Authenticator:   在Web中使用JAAS自己来实现。  
  用户资格存储LDAP或数据库:  
  1.   Gatekeeper拦截检查每个到达受保护的资源。首先检查这个用户是否有已经创建  
  好的Login   Session,如果没有,Gatekeeper   检查是否有一个全局的和Authenticator相关的session?  
  2.   如果没有全局的session,这个用户被导向到Authenticator的Sign-on   页面,  
  要求提供用户名和密码。  
  3.   Authenticator接受用户名和密码,通过用户的资格系统验证用户。  
  4.   如果验证成功,Authenticator将创建一个全局Login   session,并且导向Gatekeeper  
  来为这个用户在他的web应用中创建一个Login   Session。  
  5.   Authenticator和Gatekeepers联合分享Cookie,或者使用Tokens在Query字符里。

但凡涉及多用户不同权限的网络或者单机程序,都会有权限管理的问题,比较突出的是MIS系统。    
   
  下面我要说的是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:berg@91search.com,也欢迎访问我和另外一位朋友共同建设的网站:http://www.91search.com,那里将有一个音乐搜索的工具软件提供下载。
posted @ 2008-01-22 00:55 周奇 阅读(626) | 评论 (2)编辑
  2008年7月15日

posted @ 2008-07-15 17:18 周奇 阅读(65) | 评论 (0)编辑
  2008年7月9日
QQ群46833951   .net(C#)之家
posted @ 2008-07-09 08:36 周奇 阅读(136) | 评论 (0)编辑
  2008年7月8日

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="zl_User.aspx.cs" Inherits="zl_User" %>

<%@ Register Src="control/Menu.ascx" TagName="Menu" TagPrefix="uc1" %>
<%@ Register Src="control/shop_sort.ascx" TagName="shop_sort" TagPrefix="uc2" %>
<%@ Register Src="control/header.ascx" TagName="header" TagPrefix="uc3" %>
<%@ Register Src="control/link.ascx" TagName="link" TagPrefix="uc4" %>
<%@ Register Src="control/Foot.ascx" TagName="Foot" TagPrefix="uc5" %>
<%@ Register Src="control/help.ascx" TagName="help" TagPrefix="uc6" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>用户注册</title>
    <script type="text/javascript" src="js/JScript.js"></script>

    <link href="images/css.css" rel="stylesheet" type="text/css" />
   
   
    <script type="text/javascript">
    function yanzheng()
    {
    var jsU_Name=document.getElementById("tbU_Name")
    if(jsU_Name.value=='')
    {
    alert("用户名不能为空!")
    return false;
    }
    var jsU_Psd=document.getElementById("tbU_Psd");
    if(jsU_Psd.value=='')
    {
    alert("密码不能为空!");
    return false;
    }
    var jsU_Email=document.getElementById("tbUn_Email");
    if(!checkMail(jsU_Email.value))
    {
    alert("Email格式不正确,请重新输入!");
    return false;
    }
    var jsU_RealName=document.getElementById("tbU_RealName");
    if(jsU_RealName.value=='')
    {
    alert("真实姓名不能为空!");
    return false;
    }
    var jsU_Address=document.getElementById("tbU_Address");
    if(jsU_Address.value=='')
    {
    alert("地址不能为空!");
    return false;
    }
    var jsU_Tel=document.getElementById("tbU_Tel");
    if(jsU_Tel.value=='')
    {
    alert("电话不能为空!");
    return false;
    }
    var jsU_Zip=document.getElementById("tbU_Zip");
    if(jsU_Zip.value=='')
    {
    alert("邮编不能为空!");
    return false;
    }
    return true;

    }
    </script>
</head>
<body class="body">
    <form id="form1" runat="server">
        &nbsp;<uc3:header ID="Header2" runat="server" />
        <table width="967" border="0" align="center" cellpadding="0" cellspacing="0">
            <tr>
                <td width="614" height="63" align="right">
                </td>
                <td width="353" valign="bottom">
                    <table width="25%" border="0" align="right" cellpadding="0" cellspacing="0">
                        <tr>
                            <td width="44%" align="center">
                                <img src="images/46.jpg" alt="" width="115" height="34" /></td>
                            <td width="56%" align="center">
                                <img src="images/47.jpg" alt="" width="115" height="34" /></td>
                        </tr>
                    </table>
                </td>
            </tr>
        </table>
        <table width="967" border="0" align="center" cellpadding="0" cellspacing="0">
            <tr>
                <td width="15">
                    <img src="images/zl_r11_c2.jpg" width="15" height="73" /></td>
                <td valign="top" background="images/zl_r11_c119.jpg">
                    <!--导航-->
                    <uc1:Menu ID="Menu1" runat="server" />
                    <!--搜索-->
                    <table width="100%" border="0" cellpadding="0" cellspacing="0" class="top2">
                        <tr>
                            <td width="26%">
                                <table width="92%" border="0" cellspacing="0" cellpadding="0">
                                    <tr>
                                        <td width="5%" align="right">
                                            <img src="images/30.jpg" width="10" height="22" /></td>
                                        <td width="62%" height="14" background="images/31.jpg">
                                            <input name="textfield3" type="text" class="bianx" size="19" /></td>
                                        <td width="8%">
                                            <img src="images/32.jpg" width="16" height="22" /></td>
                                        <td width="25%" align="center">
                                            <input type="image" name="imageField2" src="images/zl_r12_c38.jpg" /></td>
                                    </tr>
                                </table>
                            </td>
                            <td width="74%">
                                热门搜索:电磁炉 剃须刀 博朗 瑞士军刀 乐扣乐扣 欧姆龙 礼品 瑞士军刀 乐扣乐扣 欧姆龙 礼品 瑞士军刀</td>
                        </tr>
                    </table>
                </td>
                <td width="12">
                    <img src="images/zl_r11_c124.jpg" width="12" height="73" /></td>
            </tr>
        </table>
        <table width="967" border="0" align="center" cellpadding="0" cellspacing="0">
            <tr>
                <td valign="top" colspan="2">
                    &nbsp;
                    <table style="width: 800px">
                        <tr>
                            <td colspan="2">
                                <table align="center" border="0" cellpadding="0" cellspacing="0" width="90%">
                                    <tr>
                                        <td colspan="5">
                                            <span class="STYLE2" style="font-size: 10pt; color: #ed008c"><strong>新用户注册</strong></span>(必填)请填写您的信息&nbsp;</td>
                                    </tr>
                                </table>
                            </td>
                            <td style="width: 100px">
                            </td>
                        </tr>
                        <tr>
                            <td colspan="2">
                            </td>
                            <td style="width: 100px">
                            </td>
                        </tr>
                        <tr>
                            <td style="width: 100px">
                                用户帐户:</td>
                            <td colspan="2">
                                <asp:TextBox ID="tbU_Name" runat="server"></asp:TextBox><span style="color: #ff0000">*</span>必须由数字、字母或下划线组成,长度6-12位,不能用汉字。</td>
                        </tr>
                        <tr>
                            <td style="width: 100px">
                                用户密码:</td>
                            <td colspan="2">
                                <asp:TextBox ID="tbU_Psd" runat="server" TextMode="Password" Width="150px"></asp:TextBox><span
                                    style="color: #ff0000">*<span style="color: #000000">有数字、字母(区分大小写)或下划线组成,长度6-16位。</span></span></td>
                        </tr>
                        <tr>
                            <td style="width: 100px">
                                确认密码:</td>
                            <td colspan="2">
                                <asp:TextBox ID="TextBox3" runat="server" TextMode="Password" Width="150px"></asp:TextBox><span
                                    style="color: #ff0000">*<span style="color: #000000"> 请再输入一遍帐户密码 。</span></span></td>
                        </tr>
                        <tr>
                            <td style="width: 100px">
                                电子邮件:</td>
                            <td colspan="2">
                                <asp:TextBox ID="tbU_Email" runat="server"></asp:TextBox><span style="color: #ff0000">*<span
                                    style="color: #000000">邮箱用于找回密码。</span></span></td>
                        </tr>
                        <tr>
                            <td style="width: 100px; height: 26px;">
                                真实姓名:</td>
                            <td style="height: 26px;" colspan="2">
                                <asp:TextBox ID="tbU_RealName" runat="server"></asp:TextBox><span style="color: #ff0000">*<span
                                    class="STYLE5"><span style="color: #666666">您所填写的</span><strong><span style="color: #666666">姓名</span></strong><span
                                        style="color: #666666">务必要与您在身份证上的信息完全一致</span></span><span style="color: #000000">
                                            。</span></span></td>
                        </tr>
                        <tr>
                            <td colspan="2">
                                <table align="center" border="0" cellpadding="0" cellspacing="0" width="90%">
                                    <tr>
                                        <td colspan="2" style="height: 32px">
                                            <span class="STYLE1" style="color: #ff0000"><strong>以下是选填内容,请填写相关信息!</strong></span></td>
                                    </tr>
                                </table>
                            </td>
                            <td style="width: 100px">
                            </td>
                        </tr>
                        <tr>
                            <td style="width: 100px; height: 26px;">
                                用户电话:</td>
                            <td style="width: 100px; height: 26px;">
                                <asp:TextBox ID="tbU_Tel" runat="server"></asp:TextBox><span style="color: #ff0000"></span></td>
                            <td style="width: 100px; height: 26px;">
                            </td>
                        </tr>
                        <tr>
                            <td style="width: 100px">
                                用户地址:</td>
                            <td style="width: 100px">
                                <asp:TextBox ID="tbU_Address" runat="server"></asp:TextBox><span style="color: #ff0000"></span></td>
                            <td style="width: 100px">
                            </td>
                        </tr>
                        <tr>
                            <td style="width: 100px">
                                用户邮编:</td>
                            <td style="width: 100px">
                                <asp:TextBox ID="tbU_Zip" runat="server"></asp:TextBox><span style="color: #ff0000"></span></td>
                            <td style="width: 100px">
                            </td>
                        </tr>
                        <tr>
                            <td style="width: 100px">
                            </td>
                            <td style="width: 100px">
                                <asp:Button ID="Button1" OnClientClick="function yanzheng()" runat="server" Text="注册" OnClick="Button1_Click" />
                                <asp:Button ID="Button2" runat="server" Text="重置" OnClick="Button2_Click" /></td>
                            <td style="width: 100px">
                            </td>
                        </tr>
                       
                    </table>
                </td>
            </tr>
        </table>
        &nbsp;&nbsp;
        <uc4:link ID="Link2" runat="server" />
        &nbsp;
        <uc5:Foot ID="Foot2" runat="server" />
    </form>
</body>
</html>

posted @ 2008-07-08 13:49 周奇 阅读(91) | 评论 (2)编辑
谢谢
posted @ 2008-07-08 13:46 周奇 阅读(103) | 评论 (3)编辑
一个用户注册,输入用户名,在页面上点击按钮,提交到数据库中,判断这个用户名是否可用,然后在页面,弹出一条信息。数据库是SQL,请问这个怎么实现啊,帮帮忙了啊,谢谢

我加的验证咋老是不对,别的页面都对,郁闷!
posted @ 2008-07-08 12:04 周奇 阅读(129) | 评论 (3)编辑
  2008年2月28日

<script   type="text/javascript">
        //根据传入的checkbox的选中状态设置所有checkbox的选中状态
        function   selectAll(obj)
        {
                var   allInput   =   document.getElementsByTagName("input");
                //alert(allInput.length);
                var   loopTime   =   allInput.length;
                for(i   =   0;i   <   loopTime;i++)
                {
                        //alert(allInput[i].type);
                        if(allInput[i].type   ==   "checkbox")
                        {
                                allInput[i].checked   =   obj.checked;
                        }
                }
        }
</script>

</head>
<body>
        <form   id="form1"   runat="server">
        <div>
                <table   style="width:   497px;   height:   363px">
                        <tr>
                                <td   rowspan="3"   style="width:   100px">
                                        <uc1:carte   ID="Carte1"   runat="server"   />
                                </td>
                                <td   colspan="2"   rowspan="3">
                                        <asp:GridView   ID="GridView1"   runat="server"   AllowPaging="True"   AutoGenerateColumns="False"
                                                DataKeyNames="ProductId"   DataSourceID="SqlDataSource1">
                                                <Columns>
                                                        <asp:TemplateField>  
                                                                        <ItemTemplate>  
                                                                                <asp:CheckBox   ID="chk_Del"   runat="server"   />  
                                                                        </ItemTemplate>  
                                                                </asp:TemplateField>  
                                                        <asp:BoundField   DataField="ProductId"   HeaderText="编号"   InsertVisible="False"
                                                                ReadOnly="True"   SortExpression="ProductId"   />
                                                        <asp:BoundField   DataField="Client"   HeaderText="客户"   SortExpression="Client"   />
                                                        <asp:BoundField   DataField="Item"   HeaderText="项目"   SortExpression="Item"   />
                                                        <asp:BoundField   DataField="Addmine"   HeaderText="时间"   SortExpression="Addmine"   />
                                                        <asp:CommandField   HeaderText="操作"     ShowDeleteButton="True"   />
                                                        <asp:TemplateField   HeaderText="操作">
                                                        <ItemTemplate>
                                                        <a   href='P_app.aspx?Id= <%#   Eval("ProductId")   %> '   0> 编辑 </a>
                                                        </ItemTemplate>
                                                        </asp:TemplateField>
                                                </Columns>
                                        </asp:GridView>
                                        <asp:CheckBox   ID="chk_JS"   runat="server"   Text="全选"   onclick="selectAll(this)"/>
                                        <asp:Button   ID="btn_DeleteRecords"   runat="server"   OnClientClick="return   judgeSelect();"   Text="删除"   OnClick="btn_DeleteRecords_Click"   />

我还用js
//   JScript   文件

//判断是否选中记录,用户确认删除
function   judgeSelect()
{
        var   result   =   false;
        var   allInput   =   document.getElementsByTagName("input");
        var   loopTime   =   allInput.length;
        for(i   =   0;i   <   loopTime;i++)
        {
                if(allInput[i].checked)
                {
                        result   =   true;
                        break;
                }
        }
        if(!result)
        {
                alert("请先选则要删除的记录!");
                return   result;
        }
        result   =   confirm("你确认要删除选定的记录吗?");
        return   result;
}

可就是删除不了,

是不是还要写后台代码?
 
我这样写对么?

请各位大哥大姐帮下我,在这先谢谢你们啦!

posted @ 2008-02-28 13:15 周奇 阅读(28) | 评论 (0)编辑
  2008年2月25日

很多朋友想用SQL2000数据库的编程方法,但是却又苦于自己是学ACCESS的,对SQL只是一点点的了解而已,这里我给大家提供以下参考---将ACCESS转化成SQL2000的方法和注意事项

一,首先,我说的是在ACCESS2000,SQL2000之间转换,其他的我也还没有尝试过,希望大家多多试验,肯定是有办法的;

二,转换的方法

1,打开”控制面板“下”管理工具“中的”数据库源“;

2,按”添加“添加一个新的数据源,在选择栏里选“Driver do microsoft Access (*.mdb)”,完成后将出现一个框,在“数据库源”里面输入你想写的名称,我取名叫“ABC”,说明不需要填,接着,按下面的选择,寻找你的数据库地址和选中(注意,请先备份自己的ACCESS数据库),然后确定。数据源在这里建好了,剩下转换了。

3,打开SQL2000企业管理器,进入数据库,新建一个空的数据库“ABC”;

4,选择新建立的数据库,按鼠标右键,选择“所有任务”下“导入数据”,按“下一步”继续;

5,在数据库源下拉但中选择”Driver do microsoft Access(*.mdb)“,在”用户/系统DSN“中,选种你刚才添加的”ABC“,按 ”下一步“;

6,“目的”不需要修改,选择服务器(一般下为自己的本机local,也可以选择服务器地址或者局域网地址,确定你的权限是否可以操作,),使用WINDOWS 身份验证指用自己的系统管理员身份操作,使用SQL身份操作验证可以用于网站的操作,推荐用后者;

7,选上使用SQL身份操作验证后,填写你的用户名和密码,我自己选择的是系统默认号码sa,****,数据库选择刚新建的ABC,按下一步;

8,这一步的两个单项选择,从数据源复制表和视图与用一条查询指令指定要传输的数据,选择前者,按下一步继续;

9,这里将出现你自己ACCESS数据库的表,按全选后,下一步;

10,DTS导入/导出向导,看立即运行被选中按下一步,

11,按完成继续;

12,这个步骤你将看到你的数据被导入SQL2000里面,当出现已经成功把XXX个表导入到数据库的字样,而且所有的表前面都有绿色的勾,就表示成功导入所有数据,如果中途出现问题或者表前面有红色的叉的话,说明该表没有成功导入,这时就要回去查看自己的操作是否正确了.

三,数据修改

1,由于SQL2000里面没有自动编号,所以你的以自动编号设置的字段都会变成非空的字段,这就必须手工修改这些字段,并把他的标示选择是,种子为1,增量为1,

2,另外,ACCESS2000转换成SQL2000后,原来属性为是/否的字段将被转换成非空的bit,这时候你必须修改成自己想要的属性了;

3,另外,大家要注意对时间函数的把握.ACCESS与SQL是有很多不同的.

ACCESS转MS SQL数据库的几点经验

1.ACCESS的数据库中的自动编号类型在转化时,sql server并没有将它设为自动编号型,我们需在SQL创建语句中加上identity,表示自动编号!

2.转化时,跟日期有关的字段,SQL SERVER默认为smalldatetime型,我们最好将它变为datetime型,因为datetime型的范围比smalldatetime型大。我遇见这种情况,用smalldatetime型时,转化失败,而用datetime型时,转化成功。

3.对此两种数据库进行操作的sql语句不全相同,例如:在对ACCESS数据库进行删除纪录时用:delete * from user where id=10,而对SQL SERVER数据库进行删除是用:delete user where id=10.

4.日期函数不相同,在对ACCESS数据库处理中,可用date()、time()等函数,但对SQL SERVER数据库处理中,只能用datediff,dateadd等函数,而不能用date()、time()等函数。

5.在对ACCESS数据库处理中,sql语句中直接可以用一些VB的函数,像cstr()函数,而对SQL SERVER数据库处理中,却不能用。

posted @ 2008-02-25 14:45 周奇 阅读(25) | 评论 (0)编辑
  2008年2月21日
有没有把文章翻译成英文的软件?

有的话留个网址,名字都可以!

先谢了哈!
posted @ 2008-02-21 09:46 周奇 阅读(169) | 评论 (0)编辑
  2008年1月22日
本例完成的功能就是防止用户重复登录!若用户已经

登录,则当其再次登录时,弹出提示框后返回!

实现思路:用户登录成功后,将用户登录信息存放到

Hashtable类型的Application["Online"]里面,其键

值为SessionID,其Value值为用户ID;当用户注销时

,调用Session.Abandon;在Global.asax里面的

SessionEnd事件中,将用户ID从Hashtable中删除;在

用户访问页面时,察看Hashtable中是否有对应的用户

ID如果没有则判断用户不在线(用户不在线的原因可

能是按了注销按钮、网页超时等)

1、公用类中判断用户是否在线的函数(供用户调用)
/// <summary>
/// 判断用户strUserID是否包含在Hashtable h中
/// </summary>
/// <param name="strUserID"></param>
/// <param name="h"></param>
/// <returns></returns>
public static bool AmIOnline(string

strUserID,Hashtable h)
{
if(strUserID == null)
return false;

//继续判断是否该用户已经登陆
if(h == null)
return false;

//判断哈希表中是否有该用户
IDictionaryEnumerator e1 = h.GetEnumerator();
bool flag = false;
while(e1.MoveNext())
{
if(e1.Value.ToString().CompareTo(strUserID) ==

0)
{
flag = true;
break;
}
}
return flag;
}

2、用户登录事件处理:
private void btnlogin_Click(object sender,

System.Web.UI.ImageClickEventArgs e)
{ ////User为自定义的类,其中包含Login方法
User CurUser = new User();
CurUser.UserID = this.username.Text.Trim();

if(MyUtility.AmIOnline(CurUser.UserID,

(Hashtable)Application["Online"]))
{
JScript.Alert("您所使用的登录ID已经在线了!您不

能重复登录!");
return;
}

CurUser.LoginPsw =

FormsAuthentication.HashPasswordForStoringInCon

figFile(this.password.Text.Trim(),"SHA1");
int ii = CurUser.Login();
StringBuilder sbPmt = new StringBuilder();

switch(ii)
{
case 0: //如果登录成功,则将UserID加入

Application["Online"]中
Hashtable h = (Hashtable)Application["Online"];
if(h == null)
h = new Hashtable();
h[Session.SessionID] = CurUser.UserID;
Application["Online"] = h;

Session["UserID"] = CurUser.UserID;
Session["UserNM"] = CurUser.UserNM;
Session["RoleMap"] = CurUser.RoleMap;
Session["LoginPsw"] = CurUser.LoginPsw;
Session["LoginTime"] = DateTime.Now;
Response.Redirect("ChooseRole.aspx");
break;
case -1:
JScript.Alert("用户名错误!");
break;
case -2:
JScript.Alert("密码错误!");
break;
default:
sbPmt.Append("登录过程中发生未知错误!");
JScript.Alert(sbPmt.ToString());
break;
}
return;
}

3、在Global.asax中的Session_End事件:
protected void Session_End(Object sender,

EventArgs e)
{
Hashtable h=(Hashtable)Application["Online"];

if(h[Session.SessionID]!=null)
h.Remove(Session.SessionID);

Application["Online"]=h;
}

4、在每一个页面需要刷新的地方,调用如下代码:
try
{
if(!common.MyUtility.AmIOnline(Session

["UserID"].ToString(),(Hashtable)Application

["OnLine"]))
{
//用户没有在线 ,转到登录界面
Response.Write

("<script>parent.document.location.href='Login.

aspx';</script>"); ////有框架时用
//Response.Redirect("login.aspx"); ////无框架时


return;
}
}
catch
{
//会话过期 ,转到登录界面
Response.Write

("<script>parent.document.location.href='Login.

aspx';</script>"); ////有框架时所用
//Response.Redirect("login.aspx"); ////无框架时


return;
}

深入思考:
由本例的解决方法可以加以延伸,比如,在存储

UserID的时候,将UserID+客户端IP地址一起存进去,

则在将相应信息取出来分析的时候,可以做到:当用

户在不同的计算机上先后登录的时候,则允许最近一

次的登录,而将之前的登录删除!等等等等
posted @ 2008-01-22 00:56 周奇 阅读(155) | 评论 (0)编辑
 前言:  
  权限往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“Who对What(Which)进行How的操作”的逻辑表达式是否为真。针对不同的应用,需要根据项目的实际情况和具体架构,在维护性、灵活性、完整性等N多个方案之间比较权衡,选择符合的方案。  
  目标:  
  直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要,系统不辞劳苦的实现了组的继承,除了功能的必须,更主要的就是因为它足够直观。  
  简单,包括概念数量上的简单和意义上的简单还有功能上的简单。想用一个权限系统解决所有的权限问题是不现实的。设计中将常常变化的“定制”特点比较强的部分判断为业务逻辑,而将常常相同的“通用”特点比较强的部分判断为权限逻辑就是基于这样的思路。  
  扩展,采用可继承在扩展上的困难。的Group概念在支持权限以组方式定义的同时有效避免了重定义时  
  现状:  
  对于在企业环境中的访问控制方法,一般有三种:  
  1.自主型访问控制方法。目前在我国的大多数的信息系统中的访问控制模块中基本是借助于自主型访问控制方法中的访问控制列表(ACLs)。  
  2.强制型访问控制方法。用于多层次安全级别的军事应用。  
  3.基于角色的访问控制方法(RBAC)。是目前公认的解决大型企业的统一资源访问控制的有效方法。其显著的两大特征是:1.减小授权管理的复杂性,降低管理开销。2.灵活地支持企业的安全策略,并对企业的变化有很大的伸缩性。  
  名词:  
  粗粒度:表示类别级,即仅考虑对象的类别(the   type   of   object),不考虑对象的某个特  
  定实例。比如,用户管理中,创建、删除,对所有的用户都一视同仁,并不区分操作的具体对象实例。  
  细粒度:表示实例级,即需要考虑具体对象的实例(the   instance   of   object),当然,细  
  粒度是在考虑粗粒度的对象类别之后才再考虑特定实例。比如,合同管理中,列表、删除,需要区分该合同实例是否为当前用户所创建。  
  原则:  
  权限逻辑配合业务逻辑。即权限系统以为业务逻辑提供服务为目标。相当多细粒度的权限问题因其极其独特而不具通用意义,它们也能被理解为是“业务逻辑”的一部分。比如,要求:“合同资源只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能够浏览”。这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题。在这里它是业务逻辑问题,在整个权限系统的架构设计之中不予过多考虑。当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。即,设计原则归结为:“系统只提供粗粒度的权限,细粒度的权限被认为是业务逻辑的职责”。  
  需要再次强调的是,这里表述的权限系统仅是一个“不完全”的权限系统,即,它不提供所有关于权限的问题的解决方法。它提供一个基础,并解决那些具有“共性”的(或者说粗粒度的)部分。在这个基础之上,根据“业务逻辑”的独特权限需求,编码实现剩余部分(或者说细粒度的)部分,才算完整。回到权限的问题公式,通用的设计仅解决了Who+What+How   的问题,其他的权限问题留给业务逻辑解决。  
  概念:  
  Who:权限的拥用者或主体(Principal、User、Group、Role、Actor等等)  
  What:权限针对的对象或资源(Resource、Class)。  
  How:具体的权限(Privilege,   正向授权与负向授权)。  
  Role:是角色,拥有一定数量的权限。  
  Operator:操作。表明对What的How   操作。  
  说明:  
  User:与   Role   相关,用户仅仅是纯粹的用户,权限是被分离出去了的。User是不能与   Privilege   直接相关的,User   要拥有对某种资源的权限,必须通过Role去关联。解决   Who   的问题。  
  Resource:就是系统的资源,比如部门新闻,文档等各种可以被提供给用户访问的对象。资源可以反向包含自身,即树状结构,每一个资源节点可以与若干指定权限类别相关可定义是否将其权限应用于子节点。  
  Privilege:是Resource   Related的权限。就是指,这个权限是绑定在特定的资源实例上的。比如说部门新闻的发布权限,叫做"部门新闻发布权限"。这就表明,该Privilege是一个发布权限,而且是针对部门新闻这种资源的一种发布权限。Privilege是由Creator在做开发时就确定的。权限,包括系统定义权限和用户自定义权限用户自定义权限之间可以指定排斥和包含关系(如:读取,修改,管理三个权限,管理   权限   包含   前两种权限)。Privilege   如"删除"   是一个抽象的名词,当它不与任何具体的   Object   或   Resource   绑定在一起时是没有任何意义的。拿新闻发布来说,发布是一种权限,但是只说发布它是毫无意义的。因为不知道发布可以操作的对象是什么。只有当发布与新闻结合在一起时,才会产生真正的   Privilege。这就是   Privilege   Instance。权限系统根据需求的不同可以延伸生很多不同的版本。  
  Role:是粗粒度和细粒度(业务逻辑)的接口,一个基于粗粒度控制的权限框架软件,对外的接口应该是Role,具体业务实现可以直接继承或拓展丰富Role的内容,Role不是如同User或Group的具体实体,它是接口概念,抽象的通称。  
  Group:用户组,权限分配的单位与载体。权限不考虑分配给特定的用户。组可以包括组(以实现权限的继承)。组可以包含用户,组内用户继承组的权限。Group要实现继承。即在创建时必须要指定该Group的Parent是什么Group。在粗粒度控制上,可以认为,只要某用户直接或者间接的属于某个Group那么它就具备这个Group的所有操作许可。细粒度控制上,在业务逻辑的判断中,User仅应关注其直接属于的Group,用来判断是否“同组”   。Group是可继承的,对于一个分级的权限实现,某个Group通过“继承”就已经直接获得了其父Group所拥有的所有“权限集合”,对这个Group而言,需要与权限建立直接关联的,仅是它比起其父Group需要“扩展”的那部分权限。子组继承父组的所有权限,规则来得更简单,同时意味着管理更容易。为了更进一步实现权限的继承,最直接的就是在Group上引入“父子关系”。  
  User与Group是多对多的关系。即一个User可以属于多个Group之中,一个Group可以包括多个User。子Group与父Group是多对一的关系。Operator某种意义上类似于Resource   +   Privilege概念,但这里的Resource仅包括Resource   Type不表示Resource   Instance。Group   可以直接映射组织结构,Role   可以直接映射组织结构中的业务角色,比较直观,而且也足够灵活。Role对系统的贡献实质上就是提供了一个比较粗颗粒的分配单位。  
  Group与Operator是多对多的关系。各概念的关系图示如下:  
  解释:  
  Operator的定义包括了Resource   Type和Method概念。即,What和How的概念。之所以将What和How绑定在一起作为一个Operator概念而不是分开建模再建立关联,这是因为很多的How对于某What才有意义。比如,发布操作对新闻对象才有意义,对用户对象则没有意义。  
  How本身的意义也有所不同,具体来说,对于每一个What可以定义N种操作。比如,对于合同这类对象,可以定义创建操作、提交操作、检查冲突操作等。可以认为,How概念对应于每一个商业方法。其中,与具体用户身份相关的操作既可以定义在操作的业务逻辑之中,也可以定义在操作级别。比如,创建者的浏览视图与普通用户的浏览视图要求内容不同。既可以在外部定义两个操作方法,也可以在一个操作方法的内部根据具体逻辑进行处理。具体应用哪一种方式应依据实际情况进行处理。
 
这样的架构,应能在易于理解和管理的情况下,满足绝大部分粗粒度权限控制的功能需要。但是除了粗粒度权限,系统中必然还会包括无数对具体Instance的细粒度权限。这些问题,被留给业务逻辑来解决,这样的考虑基于以下两点:  
  一方面,细粒度的权限判断必须要在资源上建模权限分配的支持信息才可能得以实现。比如,如果要求创建者和普通用户看到不同的信息内容,那么,资源本身应该有其创建者的信息。另一方面,细粒度的权限常常具有相当大的业务逻辑相关性。对不同的业务逻辑,常常意味着完全不同的权限判定原则和策略。相比之下,粗粒度的权限更具通用性,将其实现为一个架构,更有重用价值;而将细粒度的权限判断实现为一个架构级别的东西就显得繁琐,而且不是那么的有必要,用定制的代码来实现就更简洁,更灵活。  
  所以细粒度控制应该在底层解决,Resource在实例化的时候,必需指定Owner和GroupPrivilege在对Resource进行操作时也必然会确定约束类型:究竟是OwnerOK还是GroupOK还是AllOK。Group应和Role严格分离User和Group是多对多的关系,Group只用于对用户分类,不包含任何Role的意义;Role只授予User,而不是Group。如果用户需要还没有的多种Privilege的组合,必须新增Role。Privilege必须能够访问Resource,同时带User参数,这样权限控制就完备了。  
  思想:  
  权限系统的核心由以下三部分构成:1.创造权限,2.分配权限,3.使用权限,然后,系统各部分的主要参与者对照如下:1.创造权限   -   Creator创造,2.分配权限   -   Administrator   分配,3.使用权限   -   User:  
  1.   Creator   创造   Privilege,   Creator   在设计和实现系统时会划分,一个子系统或称为模块,应该有哪些权限。这里完成的是   Privilege   与   Resource   的对象声明,并没有真正将   Privilege   与具体Resource   实例联系在一起,形成Operator。  
  2.   Administrator   指定   Privilege   与   Resource   Instance   的关联。在这一步,   权限真正与资源实例联系到了一起,   产生了Operator(Privilege   Instance)。Administrator利用Operator这个基本元素,来创造他理想中的权限模型。如,创建角色,创建用户组,给用户组分配用户,将用户组与角色关联等等...这些操作都是由   Administrator   来完成的。  
  3.   User   使用   Administrator   分配给的权限去使用各个子系统。Administrator   是用户,在他的心目中有一个比较适合他管理和维护的权限模型。于是,程序员只要回答一个问题,就是什么权限可以访问什么资源,也就是前面说的   Operator。程序员提供   Operator   就意味着给系统穿上了盔甲。Administrator   就可以按照他的意愿来建立他所希望的权限框架可以自行增加,删除,管理Resource和Privilege之间关系。可以自行设定用户User和角色Role的对应关系。(如果将   Creator看作是   Basic   的发明者,   Administrator   就是   Basic   的使用者,他可以做一些脚本式的编程)   Operator是这个系统中最关键的部分,它是一个纽带,一个系在Programmer,Administrator,User之间的纽带。  
  用一个功能模块来举例子。  
  一.建立角色功能并做分配:  
  1.如果现在要做一个员工管理的模块(即Resources),这个模块有三个功能,分别是:增加,修改,删除。给这三个功能各自分配一个ID,这个ID叫做功能代号:  
  Emp_addEmp,Emp_deleteEmp,Emp_updateEmp。  
  2.建立一个角色(Role),把上面的功能代码加到这个角色拥有的权限中,并保存到数据库中。角色包括系统管理员,测试人员等。  
  3.建立一个员工的账号,并把一种或几种角色赋给这个员工。比如说这个员工既可以是公司管理人员,也可以是测试人员等。这样他登录到系统中将会只看到他拥有权限的那些模块。  
  二.把身份信息加到Session中。  
  登录时,先到数据库中查找是否存在这个员工,如果存在,再根据员工的sn查找员工的权限信息,把员工所有的权限信息都入到一个Hashmap中,比如就把上面的Emp_addEmp等放到这个Hashmap中。然后把Hashmap保存在一个UserInfoBean中。最后把这个UserInfoBean放到Session中,这样在整个程序的运行过程中,系统随时都可以取得这个用户的身份信息。  
  三.根据用户的权限做出不同的显示。  
  可以对比当前员工的权限和给这个菜单分配的“功能ID”判断当前用户是否有打开这个菜单的权限。例如:如果保存员工权限的Hashmap中没有这三个ID的任何一个,那这个菜单就不会显示,如果员工的Hashmap中有任何一个ID,那这个菜单都会显示。    
  对于一个新闻系统(Resouce),假设它有这样的功能(Privilege):查看,发布,删除,修改;假设对于删除,有"新闻系统管理者只能删除一月前发布的,而超级管理员可删除所有的这样的限制,这属于业务逻辑(Business   logic),而不属于用户权限范围。也就是说权限负责有没有删除的Permission,至于能删除哪些内容应该根据UserRole   or   UserGroup来决定(当然给UserRole   or   UserGroup分配权限时就应该包含上面两条业务逻辑)。  
  一个用户可以拥有多种角色,但同一时刻用户只能用一种角色进入系统。角色的划分方法可以根据实际情况划分,按部门或机构进行划分的,至于角色拥有多少权限,这就看系统管理员赋给他多少的权限了。用户—角色—权限的关键是角色。用户登录时是以用户和角色两种属性进行登录的(因为一个用户可以拥有多种角色,但同一时刻只能扮演一种角色),根据角色得到用户的权限,登录后进行初始化。这其中的技巧是同一时刻某一用户只能用一种角色进行登录。  
  针对不同的“角色”动态的建立不同的组,每个项目建立一个单独的Group,对于新的项目,建立新的   Group   即可。在权限判断部分,应在商业方法上予以控制。比如:不同用户的“操作能力”是不同的(粗粒度的控制应能满足要求),不同用户的“可视区域”是不同的(体现在对被操作的对象的权限数据,是否允许当前用户访问,这需要对业务数据建模的时候考虑权限控制需要)。   
 
扩展性:  
  有了用户/权限管理的基本框架,Who(User/Group)的概念是不会经常需要扩展的。变化的可能是系统中引入新的   What   (新的Resource类型)或者新的How(新的操作方式)。那在三个基本概念中,仅在Permission上进行扩展是不够的。这样的设计中Permission实质上解决了How   的问题,即表示了“怎样”的操作。那么这个“怎样”是在哪一个层次上的定义呢?将Permission定义在“商业方法”级别比较合适。比如,发布、购买、取消。每一个商业方法可以意味着用户进行的一个“动作”。定义在商业逻辑的层次上,一方面保证了数据访问代码的“纯洁性”,另一方面在功能上也是“足够”的。也就是说,对更低层次,能自由的访问数据,对更高层次,也能比较精细的控制权限。  
  确定了Permission定义的合适层次,更进一步,能够发现Permission实际上还隐含了What的概念。也就是说,对于What的How操作才会是一个完整的Operator。比如,“发布”操作,隐含了“信息”的“发布”概念,而对于“商品”而言发布操作是没有意义的。同样的,“购买”操作,隐含了“商品”的“购买”概念。这里的绑定还体现在大量通用的同名的操作上,比如,需要区分“商品的删除”与“信息的删除”这两个同名为“删除”的不同操作。  
  提供权限系统的扩展能力是在Operator   (Resource   +   Permission)的概念上进行扩展。Proxy   模式是一个非常合适的实现方式。实现大致如下:在业务逻辑层(EJB   Session   Facade   [Stateful   SessionBean]中),取得该商业方法的Methodname,再根据Classname和   Methodname   检索Operator   数据,然后依据这个Operator信息和Stateful中保存的User信息判断当前用户是否具备该方法的操作权限。  
  应用在   EJB   模式下,可以定义一个很明确的   Business层次,而一个Business   可能意味着不同的视图,当多个视图都对应于一个业务逻辑的时候,比如,Swing   Client以及   Jsp   Client   访问的是同一个   EJB   实现的   Business。在   Business   层上应用权限较能提供集中的控制能力。实际上,如果权限系统提供了查询能力,那么会发现,在视图层次已经可以不去理解权限,它只需要根据查询结果控制界面就可以了。  
  灵