权限设计

但凡涉及多用户不同权限的网络或者单机程序,都会有权限管理的问题,比较突出的是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语法实现
为了设计一套具有较强可扩展性的用户认证管理,需要建立用户、角色和权限等数据库表,并且建立之间的关系,具体实现如下。
用户
用户仅仅是纯粹的用户,用来记录用户相关信息,如用户名、密码等,权限是被分离出去了的。用户(User)要拥有对某种资源的权限,必须通过角色(Role)去关联。
用户通常具有以下属性:
ü         编号,在系统中唯一。
ü         名称,在系统中唯一。
ü         用户口令。
ü         注释,描述用户或角色的信息。
1.2 角色
角色是使用权限的基本单位,拥有一定数量的权限,通过角色赋予用户权限,通常具有以下属性:
ü         编号,在系统中唯一。
ü         名称,在系统中唯一。
ü         注释,描述角色信息
1.3 权限
       权限指用户根据角色获得对程序某些功能的操作,例如对文件的读、写、修改和删除功能,通常具有以下属性:
ü         编号,在系统中唯一。
ü         名称,在系统中唯一。
ü         注释,描述权限信息
1.4 用户与角色的关系
一个用户(User)可以隶属于多个角色(Role),一个角色组也可拥有多个用户,用户角色就是用来描述他们之间隶属关系的对象。用户(User)通过角色(Role)关联所拥有对某种资源的权限,例如
l         用户(User):
UserID      UserName      UserPwd
1                   张三                 xxxxxx
2                   李四                 xxxxxx    
……
l         角色(Role):
RoleID           RoleName          RoleNote
       01                  系统管理员       监控系统维护管理员
       02                  监控人员          在线监控人员
       03                  调度人员          调度工作人员
       04                  一般工作人员   工作人员
       ……
l         用户角色(User_Role):
UserRoleID           UserID           RoleID           UserRoleNote
1                       1                   01                  用户“张三”被分配到角色“系统管理员”
2                        2                   02                  用户“李四”被分配到角色“监控人员”
3                        2                   03                  用户“李四”被分配到角色“调度人员”
……
       从该关系表可以看出,用户所拥有的特定资源可以通过用户角色来关联。
1.5 权限与角色的关系
一个角色(Role)可以拥有多个权限(Permission),同样一个权限可分配给多个角色。例如:
l         角色(Role):
RoleID           RoleName          RoleNote
       01                  系统管理员       监控系统维护管理员
       02                  监控人员          在线监控人员
       03                  调度人员          调度工作人员
       04                  一般工作人员   工作人员
       ……
l         权限(Permission):
PermissionID      PermissionName       PermissionNote
0001                        增加监控                 允许增加监控对象
0002                        修改监控                 允许修改监控对象
0003                        删除监控                 允许删除监控对象
0004                        察看监控信息       允许察看监控对象
……
l         角色权限(Role_Permission):
RolePermissionID   RoleID PermissionID RolePermissionNote
1                             01            0001        角色“系统管理员”具有权限“增加监控”
2                             01            0002        角色“系统管理员”具有权限“修改监控”
3                             01            0003        角色“系统管理员”具有权限“删除监控”
4                             01            0004        角色“系统管理员”具有权限“察看监控”
5                             02            0001        角色“监控人员”具有权限“增加监控”
6                             02            0004        角色“监控人员”具有权限“察看监控”
……
       由以上例子中的角色权限关系可以看出,角色权限可以建立角色和权限之间的对应关系。
1.6 建立用户权限
用户权限系统的核心由以下三部分构成:创造权限、分配权限和使用权限。
第一步由Creator创造权限(Permission),Creator在设计和实现系统时会划分。利用存储过程CreatePermissionInfo(@PermissionName,@PermissionNote)创建权限信息,指定系统模块具有哪些权限。
第二步由系统管理员(Administrator)创建用户和角色,并且指定用户角色(User-Role)和角色权限(Role-Permission)的关联关系。

1)        具有创建用户、修改用户和删除用户的功能: Administrator

 
l         存储过程CreateUserInfo(@UserName,@UserPwd)创建用户信息;
l         存储过程ModifyUserInfo(@UserName,@UserPwd)修改用户信息;
l         存储过程DeleteUserInfo(@UserID)删除用户信息;

2)        具有创建角色和删除角色的功能: Administrator

 
l         存储过程CreateRoleInfo(@RoleName,@RoleNote)创建角色信息;
l         存储过程DeleteRoleInfo(@RoleID)删除角色信息;
3)Administrator具有建立用户和角色、角色和权限的关联关系功能:
l         存储过程GrantUserRole(@UserID,@RoleID,@UserRoleNote)建立用户和角色的关联关系;
l         存储过程DeleteUserRole(@UserRoleID)删除用户和角色的关联关系;
l         存储过程GrantRolePermission(@RoleID,@PermissionID,@RolePermissionNote)建立角色和权限的关联关系;
l         存储过程DeleteRolePermission(@RolePermissionID)删除角色和权限的关联关系;
第三步用户(User)使用Administrator分配给的权限去使用各个系统模块。利用存储过程GetUserRole(@UserID, @UserRoleID output),GetRolePermission(@RoleID,@Role-
-PermissinID output)获得用户对模块的使用权限。
1.7 用户认证实现
当用户通过验证后,由系统自动生成一个128位的TicketID保存到用户数据库表中,建立存储过程Login(@UserID,@UserPwd,@TicketID output)进行用户认证,认证通过得到一个TicketID,否则TicketID为null。其流程图如下:
图1 Login流程图
得到TicketID后,客户端在调用服务端方法时传递TicketID,通过存储过程JudgeTicketPermission(@TicketID,@PermissionID)判断TicketID对应的用户所具有的权限,并根据其权限进行方法调用。
当用户退出系统时,建立存储过程Logout(@UserID)来退出系统。当用户异常退出系统时,根据最后的登陆时间(LastSignTime)确定用户的TickeID,建立存储过程ExceptionLogout(@UserID,@LastSignTime)处理用户的异常退出。
图2 Logout流程图
WebService可以采用SoapHeader中写入TicketID来使得TicketID从客户端传递给服务端。.Net Remoting可以采用CallContext类来实现TicketID从客户端传递给服务端。
2 数据库设计
 
2.1 数据库表
图3 数据库关系图
2.2 数据库表说明
2.2.1 用户表(Static_User)
Static_User

Static_User字段名

 
 

详细解释

 
 

类型

 
 

备注

 
 

UserID

 
 

路线编号

 
 

varchar(20)

 
 

PK

 
 

UserName

 
 

用户名称

 
 

varchar(20)

 
 

 

 
 

UserPwd

 
 

用户密码

 
 

varchar(20)

 
 

 

 
 

LastSignTime

 
 

最后登陆时间

 
 

datatime

 
 

 

 
 

SignState

 
 

用户登陆状态标记

 
 

int

 
 

 

 
 

TickeID

 
 

验证票记录编号

 
 

varchar(128)

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 

 

 
2.2.2 角色表(Static_Role)
Static_Role

Static_User字段名

 
 

详细解释

 
 

类型

 
 

备注

 
 

RoleID

 
 

角色编号

 
 

varchar(20)

 
 

PK

 
 

RoleName

 
 

角色名称

 
 

varchar(20)

 
 

 

 
 

RoleNote

 
 

角色信息描述

 
 

varchar(20)

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
2.2.3 用户-角色表(Static_User_Role)
Static_User_Role

Static_User字段名

 
 

详细解释

 
 

类型

 
 

备注

 
 

UserRoleID

 
 

用户角色编号

 
 

varchar(20)

 
 

PK

 
 

UserID

 
 

用户编号

 
 

varchar(20)

 
 

FK

 
 

RoleID

 
 

角色编号

 
 

varchar(20)

 
 

FK

 
 

UserRoleNote

 
 

用户角色信息描述

 
 

varchar(20)

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
2.2.4 权限表(Static_Permission)
Static_Permission

Static_User字段名

 
 

详细解释

 
 

类型

 
 

备注

 
 

PermissionID

 
 

编号

 
 

varchar(20)

 
 

PK

 
 

PermissionName

 
 

权限名称

 
 

varchar(20)

 
 

 

 
 

PermissionNote

 
 

全息信息描述

 
 

varchar(20)

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
2.2.5 角色-权限表(Static_Role_Permission)
Static_Role_Permission

Static_User字段名

 
 

详细解释

 
 

类型

 
 

备注

 
 

RolePermissionID

 
 

角色权限编号

 
 

varchar(20)

 
 

PK

 
 

RoleID

 
 

角色编号

 
 

varchar(20)

 
 

FK

 
 

PermissionID

 
 

权限编号

 
 

varchar(20)

 
 

FK

 
 

RolePermissionNote

 
 

角色权限信息描述

 
 

varchar(20)

 
 

 

 
 

 

 
 

 

 
 

 

 
 

 

 
 

3 .net技术概要
 
3.1 WebService SoapHeader
数据库执行自定义身份验证和授权。在这种情况中,应向服务传递自定义凭据(如用户名和密码),并让服务自己处理身份验证和授权。 将额外的信息连同请求一起传递给 XML Web 服务的简便方法是通过 SOAP 标头。为此,需要在服务中定义一个从 SOAPHeader 派生的类,然后将服务的公共字段声明为该类型。这在服务的公共合同中公开,并且当从 WebServiceUtil.exe 创建代理时可由客户端使用,如下例所示:

 

 

using System.Web.Services;

 

using System.Web.Services.Protocols;

 

 

 

// AuthHeader class extends from SoapHeader

 

public class AuthHeader : SoapHeader {

 

    public string Username;

 

    public string Password;

 

}

 

 

 

public class HeaderService : WebService {

 

    public AuthHeader sHeader;

 

    ...

 
}
服务中的每个 WebMethod 都可以使用 SoapHeader 自定义属性定义一组关联的标头。默认情况下,标头是必需的,但也可以定义可选标头。SoapHeader 属性指定公共字段的名称或者 Client 或 Server 类的属性(本标题中称为 Headers 属性)。在为输入标头调用方法前,WebService 设置 Headers 属性的值;而当方法为输出标头返回时,WebService 检索该值。

[WebMethod(Description="This method requires a custom soap header set by the caller")]

 

[SoapHeader("sHeader")]

 

public string SecureMethod() {

 

 

 

if (sHeader == null)

 

return "ERROR: Please supply credentials";

 

else

 

return "USER: " + sHeader.Username;

 

 

 

}

 
然后,客户端在调用要求标头的方法之前,直接在代理类上设置标头,如下面的示例所示:

HeaderService h = new HeaderService();

 

AuthHeader myHeader = new AuthHeader();

 

myHeader.Username = "username";

 

myHeader.Password = "password";

 

h.AuthHeader = myHeader;

 

String result = h.SecureMethod();

 
3.2 .Net Remoting的安全认证方式
 
CallContext提供与执行代码路径一起传送的属性集,CallContext是类似于方法调用的线程本地存储的专用集合对象,并提供对每个逻辑执行线程都唯一的数据槽。数据槽不在其他逻辑线程上的调用上下文之间共享。当 CallContext 沿执行代码路径往返传播并且由该路径中的各个对象检查时,可将对象添加到其中。当对另一个 AppDomain 中的对象进行远程方法调用时,CallContext 类将生成一个与该远程调用一起传播的 LogicalCallContext 实例。只有公开 ILogicalThreadAffinative 接口并存储在 CallContext 中的对象被在 LogicalCallContext 中传播到 AppDomain 外部。不支持此接口的对象不在 LogicalCallContext 实例中与远程方法调用一起传输。
CallContext.SetData方法存储给定对象并将其与指定名称关联,CallContext.GetData方法从 CallContext 中检索具有指定名称的对象。
下面的代码示例说明如何使用 SetData 方法将主体和标识对象传输到远程位置以进行标识。

public class ClientClass {

 

   public static voidMain() {

 

      GenericIdentity ident = new GenericIdentity("Bob");

 

      GenericPrincipal prpal = new GenericPrincipal(ident,

 

                                          Newstring[] {"Level1"});

 

      LogicalCallContextData data =

 

 new LogicalCallContextData(prpal);

 

      //Enter data into the CallContext

 

      CallContext.SetData("test data", data);

 

 

 

     

 

      Console.WriteLine(data.numOfAccesses);

 

      ChannelServices.RegisterChannel(new TcpChannel());

 

      RemotingConfiguration.RegisterActivatedClientType(

 

typeof(HelloServiceClass), "tcp://localhost:8082");

 

      HelloServiceClass service = new HelloServiceClass();

 

      if(service == null) {

 

          Console.WriteLine("Could not locate server.");

 

          return;

 

      }

 

 

 

      // call remote method

 

      Console.WriteLine();

 

      Console.WriteLine("Calling remote object");

 

      Console.WriteLine(service.HelloMethod("Caveman"));

 

      Console.WriteLine(service.HelloMethod("Spaceman"));

 

      Console.WriteLine(service.HelloMethod("Bob"));

 

      Console.WriteLine("Finished remote object call");

 

      Console.WriteLine();

 

 

 

      //Extract the returned data from the call context

 

      LogicalCallContextData returnedData =

 

         (LogicalCallContextData)CallContext.GetData("test data");

 

 

 

      Console.WriteLine(data.numOfAccesses);

 

      Console.WriteLine(returnedData.numOfAccesses);

 

   }

 

}

 
下面的代码示例说明如何使用 GetData 方法将主体和标识对象传输到远程位置以进行标识。

using System;

 

using System.Text;

 

using System.Runtime.Remoting.Messaging;

 

using System.Security.Principal;

 

 

 

public class HelloServiceClass : MarshalByRefObject {

 

   static int n_instances;

 

   int instanceNum;

 

 

 

   public HelloServiceClass() {

 

      n_instances++;

 

      instanceNum = n_instances;

 

      Console.WriteLine(this.GetType().Name + " has been created.

 

             Instance # = {0}", instanceNum);

 

   }

 

 

 

  ~HelloServiceClass() {

 

      Console.WriteLine("Destroyed instance {0} of

 

HelloServiceClass.", instanceNum);     

 

   }

 

 

 

   public String HelloMethod(String name) {

 

 

 

      //Extract the call context data

 

      LogicalCallContextData data =

 

          (LogicalCallContextData)CallContext.GetData("test data");     

 

      IPrincipal myPrincipal = data.Principal;

 

     

 

      //Check the user identity

 

      if(myPrincipal.Identity.Name == "Bob") {

 

         Console.WriteLine("/nHello {0}, you are identified!",

 

myPrincipal.Identity.Name);

 

         Console.WriteLine(data.numOfAccesses);

 

      }

 

      else {

 

         Console.WriteLine("Go away! You are not identified!");

 

         return String.Empty;

 

      }

 

 

 

        // calculate and return result to client   

 

      return "Hi there " + name + ".";

 

   }

 

}

 
4 详细代码设计
 
4.1 WebService代码设计
WebService端代码主要进行对数据库的操作,建立起Client操作数据库所需要的方法,供Client的端调用。

1)class UserInfoMng() 用户信息管理类,其中包括方法:

 

l         CreateUserInfo(string UserName string UserPwd) 建立用户信息,调用存储过程CreateUserInfo(@UserName,@UserPwd)

 

l         ModifyUserInfo(string UserName string UserPwd) 修改用户信息,调用存储过程ModifyUserInfo(@UserName,@UserPwd)

 

l         DeleteUserInfo() 删除用户信息,调用存储过程DeleteUserInfo

 

(@UserID)

 

2)class UserAuthentication() 用户认证类,用来实现用户角色、权限的设置,包括方法:

 

l         CreatePermissionInfo(string PermissionName string Permissi-

 

-onNote) 建立权限信息,调用存储过程CreatePermissionInfo

 

(@PermissionName,@PermissionNote)

 

l         CreateRoleInfo(string RoleName string RoleNote) 建立角色信息,调用存储过程CreateRoleInfo(@RoleName,@RoleNote)

 

l         DeleteRoleInfo() 删除角色信息,调用存储过程DeleteRoleInfo

 

(@RoleID)

 

l         GrantUserRole(string UserID string RoleID string UserRoleNote) 授予用户角色,调用存储过程GrantUserRole(@UserID,@RoleID,

 

@UserRoleNote)

 

l         DeleteUserRole() 删除用户角色,调用存储过程DeleteUserRole

 

(@UserRoleID)

 

l         GrantRolePermission(string RoleID string PermissionID string RolePermissionNote) 授予角色权限,调用存储过程GrantRolePermission(@RoleID,@PermissionID,@RolePermissionNote)

 

l         DeleteRolePermission() 删除授予的角色权限,调用存储过程

 

DeleteRolePermission(@RolePermissionID)

 
4.2 用户认证代码设计(Client端)
Client端调用WebService方法来进行数据库访问,Client端代码设计主要实现界面的功能,包括:权限设置、用户管理、用户授权管理和用户认证管理

1)权限设置

 

class PermissionInfoMng() 用户权限信息管理类,包括方法:

 

l         CreatePermissionInfo() 建立权限信息

 

2)用户管理

 

class UserInfoMng() 用户信息管理类,包括方法:

 

l         CreateUserInfo() 建立用户信息

 

l         ModifyUserInfo() 修改用户信息

 

l         DeleteUserInfo() 删除用户信息

 

3)用户授权管理

 

class RoleInfoMng() 角色信息管理类,包括方法:

 

l         CreateRoleInfo() 建立角色信息

 

l         DeleteRoleInfo() 删除角色信息

 

class UserRoleMng() 用户角色管理类,包括方法:

 

l         GrantUserRole() 授予用户角色

 

l         DeleteUserRole() 删除用户角色

 

class RolePermissionMng() 角色权限管理类,包括方法

 

l         GrantRolePermission() 授予角色权限

 

l         DeleteRolePermission() 删除角色权限

 

4)用户认证管理

 

class Authentication() 用户认证类,包括方法:

 

l         Login(string UserName string UserPwd) 用户登陆认证,用户认证通过分配给用户一个TicketID,否则TicketID则为null

 

l         Logout() 用户正常退出

 
l         ExceptionLogout() 用户异常退出
 
 
 

你可能感兴趣的:(权限设计)