信息安全设计模式系列 4 (转载请注明出处)
Role
角色模式
王翔 (Vision Wang)
2009-02-16
信息安全结构型模式
相信您和我一样,从进入托儿所、上学、工作这20多年的过程中经常需要学习各种规章制度,不过不知道您注意到没有,区别于通知中关于表扬/批评的内容,这些规章制度往往不是针对个人的。
当然,您也许第一时间跳出来说,“怎么会?我们企业的规章就说了,报销超过2000元的差旅费必须经过X总批准才可以。”,如果真是这样,那没有办法,我们也要遵守。
我们看两个例子,希望可以勾回您的回忆:
规章 |
所有本科生最多每次借阅3本书,2楼人文图书最多借阅1本 |
通知 |
计算机系08级本科张三同学在本学期《数据结构》考试中夹带。 经教务处确认,该同学本次考试成绩无效,给予严重警告处分一次,留校察看,以观后效。 |
上面两个例子从面向对象看,前者是某个规则的定义,后者是另一个规则的实例。对于我们的应用而言,开发阶段我们的主要目标往往是把各种规则表述为代码(或类似的机制),然后用户在执行的过程中会在具体上下文中选择适用不同的规则。
以授权规则为例,回顾一下我们之前的项 目经验,项目中多用户的权限管理往往并不简单,但典型的业务系统往往如上面的示例,他的业务规则往往是基于组织机构以及机构中岗位设置的,而人员作为个体 是通过成长,在不同岗位间流动,在不同岗位完成不同的工作。通过分析,我们不难发现,在原来直接关系的“用户——操作功能”之间如果引入一个第三方对象, 也许可以有效隔绝上述变化带来的影响。它既可以把“一捆”功能打包,也可以把“一批”用户打包,但无论如何我们明确一个目标——将一组具有相似抽象特征的 内容作第二次抽象。
角色模式经典解决方案抽象如下:
“创建一系列名为‘角色’的对象,用它抽象一组用户的访问权限”。
他与传统方式直接授权的区别如下:
图 04-01:引入Role前后的授权结构示意
上面提到的处理方式转换就是角色模式(Role(s) Pattern)的主要目标,他最普遍的应用情景是授权。但对于一些大型项目、超大型项目而言,如果企业业务管理制度明确、岗位定义清晰,项目功能繁多,这样一层的Role也许还不够,例如:
l 企业机构即便在地区总部也有4、5层的层级关系,人员岗位的等级就更复杂了;
l 区域机构人员虽然仅1000多人,但因为都是白领人员,因此工作机构、部门职能设置比较复杂;
l 项目是个比较大型的ERP,大致有14000多个功能界面;
l 授权人员归口一个部门主管,但实际授权人员只有2、3个人;
l 人员部门间交流的情况比较普遍;
相信面对这样的情况,如果仅仅作一层抽象,可能我们的授权人员也忙不过来,那么根据需要不妨作第二层、第三层抽象。例如,下面是个方案:
l 将人员“打包”的Role称为岗位,相同机构层次上有不同的岗位。例如:机构为“开发部”,岗位有:
n 助理开发工程师
n 开发工程师
n 高级开发工程师
n 设计师
n 架构师
n 部门副经理
n 部门经理
n 开发总监
n …
l 将功能“打包”的Role称为权限;例如:项目方案审批权限,包括下列功能:
n 浏览需求分析书
n 浏览用户使用习惯调查报告
n 项目财务预算登记、修改、上报
n …
这样,14000多个功能可能就合并为7、8十个权限,1000多人合并为40多个岗位,授权工作量减少了1、2个量级。
图 04-02:多层角色模式的应用情景
上面说的其实只是“常规”授权,但现实中授权往往还同时受到多个维度的干扰,关于授权相信您也有大把的经验可以分享,去除简单的“用户——角色——功能”外,我们可能还要面临非常多的问题:
l 授权往往与属地关联在一起,而且随着国际化、区域的特点,授权往往需要增加一个维度——“管辖范围”,也就是说您可以审批单证,但能审批单证属地、目标地为何处呢?
l 另外,授权往往也和业务数据关联,例如:您可以审批报销,但是否可以审批金额超过2万元的单子呢?即便您可以定义一个“大金额报销审批”的角色,是不是就够了,毕竟有可能5天以后我们觉得2万Euro元才是大金额,难道还要再定义一个角色?
l 授权是否有反操作?例如:虽然“出纳助理”都可以做一些工作,但对于新人可能暂时要“卡”掉个别功能,您是否也考虑再定义一个角色,还是增加一个授权方向的反向操作;
l 相信您对“秘书”的作用也有了解,秘书能做的往往不仅自己的那些权限,她/他们常常可以代理高层的工作,但我们的高层是否喜欢她/他们掌握自己的账号/密码呢?还是说仅仅在某些情况下把自己“分内”的某些功能分给她/他,这里就涉及如何对于这种具有“委托”关系的内容进行管理;
l 如何让企业不同层次的授权人员可以在一个更加简单的“视图”上看到他的授权相关的组织机构。例如:财务部门很多,总部的、地区总部的、分支机构的,但某批功能就是针对财务开发的,他们授权的时候就只看到自己辖域范围的即可,而且不需要看到其他部门;
l 人员部门间交流普遍,甚至经常出现整个部门的倒换、裁撤。如何令授权人员快速完成类似的“批量”授权;
l 机构间是否有绝对的垂直领导作用,例如:同样是复查审批,总部人员是否可以直接审批区域总部的每票单证呢?
l … …
说实在的,具体项目中解决任何一个问题虽然方法很多,但都并不轻松。主要原因是我们除了要提供功能外,还要尽量避免出现“牵一发、动全身”的被动局面。参考我们在本系列开篇提到解决的思路:
l 编织(weaving)多层AOP设计;
l 采用桥甚至是我们在《设计模式--基于C#的工程化实现及扩展》GOF23经典部分介绍的那样,借助多级桥模式解决;
AOP方式的逻辑示意如下:
图 04-03:采用AOP方式实现具有多个变化维度干扰的角色模式应用场景
图 04-04:采用多级桥模式实现具有多个变化维度干扰的角色模式应用场景
下面,我们还原角色模式最本源的状态,看一个完全定制的具体示例:
抽象接口
C# |
/// 授权体系的基本接口 public interface ISecurityObject { string Name { get; } }
/// 功能 public interface IFunction : ISecurityObject { }
/// 角色 public interface IRole : ISecurityObject { IEnumerable<IFunction> Functions { get; set; }
/// 配置 void Config(IEnumerable<IFunction> functions); }
/// 账号 public interface IAccount : ISecurityObject { IEnumerable<IRole> Roles { get; }
/// 授权检查 bool IsInRole(string roleName);
/// 授权检查 bool CouldOperateFunction(string functionName);
/// 授权(正向) void Grant(IRole role);
/// 授权(反向) void Revoke(IRole role); } |
示例实体类型
C# |
class SecurityObjectMock : ISecurityObject { public string Name { get; set; } }
class FunctionMock : SecurityObjectMock, IFunction { }
class RoleMock : SecurityObjectMock, IRole { IEnumerable<IFunction> functions; public IEnumerable<IFunction> Functions { get { return this.functions; } set { this.functions = value; } }
public void Config(IEnumerable<IFunction> functions) { this.functions = functions; } }
class AccountMock : SecurityObjectMock, IAccount { IList<IRole> roles = new List<IRole>(); public IEnumerable<IRole> Roles { get { return roles; } }
public bool IsInRole(string roleName) { if (string.IsNullOrEmpty(roleName)) throw new ArgumentNullException("roleName"); return (from role in this.Roles where string.Equals(role.Name, roleName) select role) .Count() > 0 ? true : false;
}
public bool CouldOperateFunction(string functionName) { if (string.IsNullOrEmpty(functionName)) throw new ArgumentNullException("functionName"); return (from role in this.Roles from func in role.Functions where string.Equals(func.Name, functionName) select func) .Count() > 0 ? true : false; }
public void Grant(IRole role) { if (role == null) throw new ArgumentNullException("role"); roles.Add(role); }
public void Revoke(IRole role) { if (role == null) throw new ArgumentNullException("role"); roles.Remove(role); } } |
单元测试
C# |
[TestMethod] public void TestAuthorizationWithSimpleRole() { // 配置授权系统环境 IRole role1 = new RoleMock() { Name = "A", Functions = new List<IFunction>(){ new FunctionMock(){Name = "A1"}, new FunctionMock(){Name = "A2"}, new FunctionMock(){Name = "A3"} } };
IRole role2 = new RoleMock() { Name = "B", Functions = new List<IFunction>(){ new FunctionMock(){Name = "B1"}, new FunctionMock(){Name = "B2"} } };
// 实例化用户 IAccount account = new AccountMock();
// 用户授权及权限检查 account.Grant(role1); account.Grant(role2); Assert.IsTrue(account.IsInRole("A")); Assert.IsTrue(account.IsInRole("B")); Assert.IsTrue(account.CouldOperateFunction("A2")); Assert.IsTrue(account.CouldOperateFunction("B2"));
// 调整授权及权限检查 account.Revoke(role1); Assert.IsFalse(account.IsInRole("A")); Assert.IsTrue(account.IsInRole("B")); Assert.IsFalse(account.CouldOperateFunction("A2")); Assert.IsTrue(account.CouldOperateFunction("B1")); Assert.IsTrue(account.CouldOperateFunction("B2")); } |
结果分析
上面的示例不难看出,如果将“角色”、“功能”等仅仅泛化为字符传名称,借助现有的开发手段实现一个定制的授权检查功能其逻辑部分并不复杂。
但如果如上面示例,IFunction、IRole、IAccount抽象了更加复杂的行为,比如:关注授权主体、授权对象、授权上下文等多个方面的时候,借助角色模式这个对象化的体系,就可以赋予系统更多的灵活性,尤其相对于平时通过“单纯数据库 + SQL/存储过程”的那个方案,因为任何一个实体类型,可能都会包括比一个名称丰富的多的信息与控制。
如上,如果角色模式不是面向经典设计中 那样,是关于功能的“打包”,而是关于用户主体的“打包”,那么我们会经常借助组合模式和迭代器模式向用户提供更简单的授权功能,例如:用户要授予一个区 域中心及下属单位所有用户“看公司年报”的功能,那么可以直接把这个区域授与相关的功能,那么各级岗位的人员根据组合和迭代过程都可以操作该功能,而无需 授权管理员费尽辛苦的为每个岗位都作一下授权;
另外,有些时候,我们的授权可能不总是针对角色,就如我们在本系列开篇说的,可能还有Identity Based Security,或者是Identity Based Security作为补充机制的需要,这时候也需要充分结合组合模式与迭代器模式的特征,解决不同层次颗粒度的授权问题。
如果您之前浏览过《设计模式--基于C#的工程化实现及扩展》GOF23经典部分的介绍,相信您在阅读上面示例的同时也已经明显的感觉到组合模式的特征,而迭代器则是用LINQ完成的,其实上面示例之所以增加一个ISecurityObject也是为这里讨论的情境预留退路。
如上面提到的,我们还可能经常用到桥模式解决多维授权变化及管控因素影响的情况;
具体项目中,设计安全机制的过程中,往往还会结合上一章节的检查点模式,实现业务授权的同时,尽量堵住“非法尝试”的过程。
我们使用的Windows是个非常典型的Role Based Security + Identity Based Security系统;
QQ群、MSN Group、GTalk Group都是在典型C2C形式IM工具基础上,扩展角色模式,实现“群”交互的典型例子。