当前位置: 首页 > news >正文

RBAC的资料

利用 AOP 实现 .NET 上完整的基于角色的访问控制(RBAC)模型 

一. 背景

  1. .NET 平台上没有完整的 RBAC 机制,.NET 中的安全模型(代码访问安全性:CAS)只是实现到 Role 层次,没有细化到 Task 层次,ASP.NET 2.0 中的诸多安全机制,如 Membership、Web.Config 的安全配置,都只能针对 Role 进行设置,大家在利用这些安全机制,往往需要在程序/代码硬编码(HardCode)角色,这样就无法实现在运行期自定义角色的功能
  2. Windows 2000/2003 中自带的 Authorization Manager 虽然实现了较为完整的RBAC模型,但一般只适用于 Windows 用户,而且也需要手动去进行权限检查(调用 AccessCheck方法)
  3. 权限检查是一个通用操作,最好的实现方式就是面向方面的编程(AOP)

二、相关主题介绍

  1. RBAC模型的要素:三个实体:用户、角色、任务(或操作)(User、Role、Task),其稳定性逐渐增强,两个关系,User<->Role、Role<->Task,其中:
    • User 是日常管理运行时建立
    • Role 是部署/交付建立
    • Task 是开发时确定
    • User<->Role 是日常管理运行时建立
    • Role<->Task 是部署/交付时建立
  2. 一般来说,Task是固定的,是和应用程序紧密绑定的,即使对之进行硬编码,也没有关系
  3. User/Role 部分比较容易实现,例如ASP.NET 2.0中 Membership 的实现

三、具体实现

注:本文中实现 AOP 的思路主要来自于如下文章:Aspect oriented Programming using .NET - AOP in C# (http://www.developerfusion.co.uk/show/5307/3/) ,这是我看到的、在.NET 上实现 AOP最简捷/方便的方法,它不便提供了原理介绍,也提供了 Visual Studio 2005 的 Sample Project ,其中有 Security Check 和 Logging 的 AOP 功能。它的优点在于,在实现 AOP 的同时,不需要再去建立接口(这是很多人的做法),直接在原有类上进行少量改动,即可实现完整的 AOP 功能。

1. 定义描述“Task”(任务)的 Attribute

using System;
namespace BusinessLogic.Security
...{
/**//// 
/// 用于定义系统中的操作
/// 
    [AttributeUsage(AttributeTargets.All,AllowMultiple=false,Inherited=true)]
public sealed class Task : Attribute
...{
private string _name,_description;
public string Name
...{
get ...{ return _name; }
set ...{ _name = value; }
        }
public string Description
...{
get ...{ return _description; }
set ...{ _description = value; }
        }
public Task(string name,string description)
...{
            _name = name;
            _description = description;
        }
public Task()
...{
        }
    }
}

2. 编写权限检查的 AOP 类 SecurityAspect,完成权限检查的功能


   
using System; using System.Diagnostics; using System.Reflection; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Contexts; using System.Runtime.Remoting.Activation; namespace BusinessLogic.Security ... { //消息接收器 internal class SecurityAspect : IMessageSink ...{ //内部变量 private IMessageSink m_next; //构造方法 internal SecurityAspect(IMessageSink next) ...{ m_next = next; } IMessageSink 实现#region IMessageSink 实现 public IMessageSink NextSink ...{ get ...{ return m_next; } } //同步处理消息 public IMessage SyncProcessMessage(IMessage msg) ...{ Preprocess(msg); IMessage returnMethod = m_next.SyncProcessMessage(msg); return returnMethod; } //异步处理消息(不实现) public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) ...{ throw new InvalidOperationException(); } #endregion 自定义的 AOP 方法#region 自定义的 AOP 方法 private void Preprocess(IMessage msg) ...{ //只处理方法调用 if (!(msg is IMethodMessage)) return; //获取方法中定义的 Task 属性,交给权限检查类去检查 IMethodMessage call = msg as IMethodMessage; MethodBase mb = call.MethodBase; object[] attrObj = mb.GetCustomAttributes(typeof(Task), false); if (attrObj != null) ...{ Task attr = (Task)attrObj[0]; if(!string.IsNullOrEmpty(attr.Name)) AzHelper.PermissionCheck(attr.Name); } // Type type = Type.GetType(call.TypeName); } #endregion } public class PermissionCheckProperty : IContextProperty, IContributeObjectSink ...{ IContributeObjectSink 实现,将 AOP 类加入消息处理链#region IContributeObjectSink 实现,将 AOP 类加入消息处理链 public IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink next) ...{ return new SecurityAspect(next); } #endregion IContextProperty 实现#region IContextProperty 实现 public string Name ...{ get ...{ return "PermissionCheckProperty"; } } public void Freeze(Context newContext) ...{ } public bool IsNewContextOK(Context newCtx) ...{ return true; } #endregion } //特性定义,用于 Consumer [AttributeUsage(AttributeTargets.Class)] public class PermissionCheckAttribute : ContextAttribute ...{ public PermissionCheckAttribute() : base("PermissionCheck") ...{ } public override void GetPropertiesForNewContext(IConstructionCallMessage ccm) ...{ ccm.ContextProperties.Add(new PermissionCheckProperty()); } } }

?

3. 定义用于权限检查的两个类:AzMan、AzHelper

这两个类的功能是从 XML 配置文件中读入 Role 和 Task 的映射关系,以确定 Role 中是否包含 Task 的引用,从而确定当前 Role 是否具有对此 Task 的权限。

注:这里可根据项目的实际情况,如果你的 Role 和 Task 的映射关系是存放在 Windows 的授权管理器(Authorizatiom Manager)或数据库中,你可以使用自已
的方法来替换下列类。

在本例中,我的 Role 和 Task 的关系是存放在 XML 文件中,XML文件的格式如下所示:


   
<? xml version="1.0" encoding="utf-8" ?> < ACL > < Tasks > < Task Name ="AddItem" Description ="增加" /> < Task Name ="ModifyItem" Description ="修改" /> < Task Name ="RemoveItem" Description ="删除" /> < Task Name ="ListItem" Description ="获取列表" /> </ Tasks > < Roles > < Role Name ="Manager" > < Task Name ="AddItem" /> < Task Name ="ModifyItem" /> < Task Name ="RemoveItem" /> < Task Name ="ListItem" /> </ Role > </ Roles > </ ACL >

   

AzMan.cs 完成角色/任务映射关系的检查


    
using System; using System.Collections.Generic; using System.Text; using System.Xml; namespace BusinessLogic.Security ... { public class AzMan ...{ public static bool AccessCheck(string taskName, string[] roles, XmlDocument aclDoc) ...{ XmlNode rootNode = aclDoc.DocumentElement; XmlNodeList roleNodes,taskNodes; bool IsPermissiable = false; for (int i = 0; i < roles.Length; i++) ...{ roleNodes = rootNode.SelectNodes("Roles/Role[@Name='" + roles[i] + "']"); if (roleNodes != null) ...{ taskNodes = roleNodes.Item(0).SelectNodes("Task[@Name='" + taskName + "']"); if (taskNodes.Count != 0) ...{ IsPermissiable = true; break; } } } return IsPermissiable; } } }

AzHelper.cs 助手类,协助其他类,更好地调用 AzMan 类的方法,以及基于性能考虑,对Role<-->Task的XML配置文件进行缓存:


    
using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Web; using System.Web.Security; using System.Diagnostics; using System.Reflection; using System.Web.Caching; namespace BusinessLogic.Security ... { public class AzHelper ...{ /**//// /// 检查当前用户是否具有执行当前任务的权限,如果有权限,则不做任何处理 /// 如果不具有权限,则引发异常 /// public static void PermissionCheck(string taskName) ...{ if (HttpContext.Current != null) ...{ XmlDocument aclDoc = (XmlDocument)HttpContext.Current.Cache["ACLDoc"]; if (aclDoc == null) ...{ CacheXml(); aclDoc = (XmlDocument)HttpContext.Current.Cache["ACLDoc"]; } string[] roles = Roles.GetRolesForUser(); if (!AzMan.AccessCheck(taskName, roles, aclDoc)) throw new UnauthorizedAccessException("访问被拒绝,当前用户不具有操作此功能的权限!"); } } /**//// /// 检查当前用户是否具有执行指定任务的权限 /// /// 任务名称 /// True/False 是否允许执行 public static bool IsPermissible(string taskName) ...{ if (HttpContext.Current != null) ...{ XmlDocument aclDoc = (XmlDocument)HttpContext.Current.Cache["ACLDoc"]; if (aclDoc == null) ...{ CacheXml(); aclDoc = (XmlDocument)HttpContext.Current.Cache["ACLDoc"]; } string[] roles = Roles.GetRolesForUser(); aclDoc.Load(HttpContext.Current.Server.MapPath("~/App_Data/ACL.xml")); return AzMan.AccessCheck(taskName, roles, aclDoc); } else return true; } /**//// /// 缓存 XML 文件 /// private static void CacheXml() ...{ string fileName = HttpContext.Current.Server.MapPath("~/App_Data/ACL.xml"); XmlDocument aclDoc = new XmlDocument(); aclDoc.Load(fileName); HttpContext.Current.Cache.Insert("ACLDoc", aclDoc, new CacheDependency(fileName)); } } }


4. 业务逻辑类的实现

由于大多数工作都在 AOP 中实现了,所以业务逻辑类的实现较为简单,主要分为以下几个步骤:

  • 在类的层次定义要求 AOP 方式权限检查的 Attribute: [PermissionCheck()]
  • 使类继承自 ContextBoundObject 对象
  • 在方法层次上利用 Task Attribute 来定义其对应的操作(注:多个方法可以定义为同一个 Task)

例如:ItemManager.cs


    
namespace BusinessLogic ... { [PermissionCheck()] public class ItemManager : ContextBoundObject ...{ [Task("AddItem","增加")] public void AddItem(Item item) ...{ //... } } }

这样就可以了,CLR 会在运行时检查类的 PermissionCheck?Attribute,然后寻找方法上的 Task ,取出当前用户对应的 Role ,再去进行匹配检查,如果不能执行此操作,会抛出 UnauthorizedAccessException 的异常,在外部进行处理即可(如在 ASP.NET 中增加 ErrorPage 等)

5. 其他相关功能的实现

Q:如果我写程序时,在各个业务逻辑类定义了大量的 Task ,如果统一提取出来?

A:利用反射可取出程序集中定义的所有 Task ,代码如下:


    
List < string > dic = new List < string > (); StringBuilder sXml = new StringBuilder( " " ); string curDir = this .GetCurrentPath(); Assembly ass = Assembly.LoadFile(curDir + " \\AppFramework.BusinessLogic.dll " ); foreach (Type t in ass.GetTypes()) ... { MethodInfo[] mis = t.GetMethods(); foreach (MethodInfo mi in mis) ...{ object[] attrs = mi.GetCustomAttributes(false); if (attrs.Length > 0) ...{ foreach (object attr in attrs) ...{ if (attr.GetType().ToString().IndexOf("Task") >= 0) ...{ Task ta = (Task)attr; //检查重复的 Task if (dic.IndexOf(ta.Name) < 0) ...{ dic.Add(ta.Name); sXml.Append(string.Format("\r\n ", ta.Name, ta.Description)); } } } } } //这就是所有的 Task 定义 sXml.Append("\r\n"); }

此段代码是将 Task 定义保存到 XML 文件中,如果你想保存到 SQL Server/Authorzatiom Manager 中,对代码稍加修改即可。

Q:程序中的 Role 如何实现?

A:如果是 ASP.NET 应用程序,可以直接利用其中的 MemberShip Role 机制,还是比较简单的

Q:如果我想在界面上预先实现一些控制,如某用户不能进行某项操作,则直接将其对应的 Button 禁止或隐藏(Disable/Invisible)掉,如何做?

A:这可以利用 ASP.NET 2.0 中的表达式功能,直接检查当前用户的角色是否可以执行 Task ,如果不行,则利用返回的 Bool 值直接设置 Button 等控件的属性,做法如下:

1)在 App_Code 下定义表达式类 PermissionCheckExpressionBuilder.cs


    
[ExpressionEditor( typeof (PermissionCheckExpressionBuilderEditor))] [ExpressionPrefix( " PermissionCheck " )] public class PermissionCheckExpressionBuilder : ExpressionBuilder ... { public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) ...{ string taskName = entry.Expression; return new CodePrimitiveExpression(AzHelper.IsPermissible(taskName)); } } public class PermissionCheckExpressionBuilderEditor : System.Web.UI.Design.ExpressionEditor ... { public override object EvaluateExpression(string expression, object parseTimeData, Type propertyType, IServiceProvider serviceProvider) ...{ //return expression + ":" + parseTimeData + ":" + propertyType + ":" + serviceProvider; string taskName = expression; return AzHelper.IsPermissible(taskName); } }


2)在 Web.Config 中加入上述表达式定义,以便可以直接在页面上引用


    
< configuration xmlns ="http://schemas.microsoft.com/.NetConfiguration/v2.0" > < expressionBuilders > < add expressionPrefix ="PermissionCheck" type ="PermissionCheckExpressionBuilder" /> expressionBuilders> configuration>

3)直接在页面控件的相应属性上绑定表达式,如:

  • 如果能执行此操作则显示,否则则隐藏
        
    < asp:Button ID ="Button1" runat ="server" Text ="AddItem" Visible ="" />
  • 如果能执行此操作则启用,否则则禁止
        
    < asp:Button ID ="Button2" runat ="server" Text ="AddItem" Enabled ="" />

4)如果想在代码中自行检查权限,可以直接调用相应方法,如:


     
protected void Button1_Click( object sender, EventArgs e) ... { AzHelper.PermissionCheck("AddItem"); //..其他操作 }

5)如何建立 User<-->Role 的映射,Role<-->Task的映射

前者较为简单,ASP.NET 2.0 中就已经具有此功能,当然你也可以利用其 API 来实现自己的定义界面。

对于 Role-Task 的映射来说,首先利用上面的代码从程序集中取出所有 Task ,保存在 XML 文件中,然后在进行配置时,可以显示 Role 和 Task ,来进行映射。

如下图所示:

角色与任务的映射


用户与角色的映射

http://www.850816.com/trackback.asp?tbID=44

相关文章:

  • 如何学习AJAX
  • javax.servlet.ServletContext接口
  • 在浏览器中显示JasperReports PDF文档
  • 消息队列(Message Queue)
  • IBatis.Net DataMapper 1.6.1 and DataAccess 1.9.1发布了
  • 王石:我的成功公式
  • 微软年底切断XP供应 强行迫使制造商预装Vista
  • 几个常见的关于日期的问题
  • 华为模拟器3.0
  • 在app.config自定义一些配置信息
  • 修改Struts2默认的模板配置
  • 乐在其中设计模式(C#) - 装饰模式(Decorator Pattern)
  • 微软有内鬼?还是***太高强?
  • CVSNT用户权限配置
  • 30岁前男人应该完成的事
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • (三)从jvm层面了解线程的启动和停止
  • Android Volley源码解析
  • bootstrap创建登录注册页面
  • CentOS从零开始部署Nodejs项目
  • go语言学习初探(一)
  • Gradle 5.0 正式版发布
  • Java Agent 学习笔记
  • java多线程
  • PhantomJS 安装
  • ReactNativeweexDeviceOne对比
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • vue自定义指令实现v-tap插件
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 从输入URL到页面加载发生了什么
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 干货 | 以太坊Mist负责人教你建立无服务器应用
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 移动端解决方案学习记录
  • !!Dom4j 学习笔记
  • #HarmonyOS:软件安装window和mac预览Hello World
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • $.proxy和$.extend
  • (1)STL算法之遍历容器
  • (33)STM32——485实验笔记
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (附源码)ssm码农论坛 毕业设计 231126
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (附源码)计算机毕业设计ssm电影分享网站
  • (九十四)函数和二维数组
  • (七)c52学习之旅-中断
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)http-server应用
  • (转)jQuery 基础
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**