角色权限,RBAC
引用说明:原文来自于http://downpour.iteye.com/blog/319965,为了方便本人阅读,文本格式略有调整。
以上是一个简化版本关系图.
User:用户表,存放用户信息
Role:角色表,存放角色信息
UserInRole:用户角色映射表,存放用户和角色的对就关系,多对多,一个用户可以对应多个角色,而不同的角色有一同的权限。
Permissions:权限表,不同的角色对应不同的权限。权限信息使用一个字段flag来表示,好处是可以使用位运算来计算权限,缺点是用位标识的权限受理论值限制,如int理论上可以标识31种不同的权限, 当然可以整加一个字段来弥补,ApplicationID标识不同的模块Application:模块信息。
[Flags] public enum Flag:long { View=1, Edit=2, Delete=4 }
基础知识:
- 位运算
- 枚举Flag
当编译器看到Flag枚举时,它会充许你用|(or)操作符组合枚举值,就像二的整数幂一样,例如
Flag Administer=Flag.View|Flag.Edit|Flag.Delete; //常用操作,检查是否存在 Flag administer=Flag.View|Flag.Edit|Flag.Delete; public bool Check(Flag administer,Flag mask) { bool bReturn = false; if ((administer & mask) == mask) bReturn = true; return bReturn; } //调用 Check(administer,Flag.Edit)将返回true. public Flag SetBit(Flag administer,Flag mask) { return administer |= mask; } administer |= mask;//操作相当于 administer = administer |mask; //从枚举中减去一种状态 administer &=mask; //如 : Flag administer=Flag.View|Flag.Edit|Flag.Delete; //如需要禁止删除权限. administer &=Flag.Delete; //另外,标记为flag的枚举类型,可以不设置值 public enum Flag:long { View, Edit, Delete }
如需要设置,按以下规律, View=1,Edit=2,Delete=4,Reply=8 按2次方累加,为什么会这样?因为他使用二进制操作,当你使用 View=1,Edit=2,Delete=3,Reply=4 这样的值, Flag.Delete 包含的值是Flag.Delete 还是 View=1|Edit=2 就无从检测了.
每个用户,可以属于不同的角色,不同的角色分配不同的权限,计算所有权限的所有可能的权限组合,只要有允许的权限,那么该用户既获取该权限。
在CS系统中,Permissions 表合用了二个字段来标识权限。
AllowMask,DenyMask 规责是Deny 优先,也就是说当权限标记为 Deny 那么不论是否 Allow 一律禁止该用户进行此项操作。
另外,像论坛类的权限设计,仅仅一个 ApplicationID 字段是不够用的,因为每个版块都需要设置不同的权限,来控制权限的粒度,可在增加一张 Permission 表,ApplicationID 修改为版块 ID。这样,就可以针对不同的版块设置不同的权限。
好了,接下的问题是怎么和.net自带的权限系统挂钩了。。
在asp.net系统中,HttpContext.Current.User实现了一个接口IPrincipal,IPrincipal包含了另一个接口Identity
//我们在设计User类的时候继承此接口 public class User:IPrincipal { string username; public string Username { get{return username;} set{username=value;} } } // 实现IPrincipal接口方法 public IIdentity Identity { get { if (!string.IsNullOrEmpty(username)) _Identity = new GenericIdentity(username,"Forums"); return (IIdentity)_Identity; } } public bool IsInRole(string role) { ..... } //怎样和asp.net挂钩呢,这里可以在登陆时做检查 if(HttpContext.Current!=null){ User u= Users.GetUser(name); HttpContext.Current.User =u; //在使用时 User u = HttpContext.Current.User as User; //当然检查用户角色可以直接用 if(HttpContext.Current.User.Identity.IsAuthenticated&&HttpContext.Current.User.IsInRole(角色名)) //另外可以直接把到当用户权限策略挂接到当前线程 ,使用以下方法 AppDomain.CurrentDomain.SetPrincipalPolicy(User); //好了,接下来,怎么check权限? //我倾向于使用Attribute [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate, Inherited = true, AllowMultiple = true)] public class CheckPermissionAttribute : Attribute { int appID; public int ApplicationID { get { return appID; } set { appID = value; } } Permission _allMask; public Permission AllMask { get { return _allMask; } set { _allMask = value; } } public CheckPermissionAttribute(ApplicationID app, Permission allMask) { appID = app; _allMask = allMask; } public CheckPermissionAttribute(Permission allMask) { _allMask = allMask; } } //AttributeUsage 第一个参数表示该属性可以应用于类,方法,属性,代理上Inherited 检查继承的权限。 //AllowMultiple 充许多次应用。 //按下来,设计一个基类,继承自Page: public class PageBase : Page { Flag _allMask; /// <summary> /// 检查类型权限 /// </summary> public void CheckClass() { Type type = this.GetType(); CheckPermissionAttribute att = (CheckPermissionAttribute)CheckPermissionAttribute.GetCustomAttribute(type, typeof(CheckPermissionAttribute)); if (att != null) { Check(att.AllMask); } } /// <summary> /// 检查函数调用权限 /// </summary> /// <param name="methodName">方法名</param> public void CheckMethod(string methodName) { Type type = this.GetType(); string name = "*"; if (!string.IsNullOrEmpty(methodName)) name = methodName; MemberInfo[] mis = type.FindMembers(MemberTypes.Method ,BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase,Type.FilterNameIgnoreCase,name); foreach (MethodInfo m in mis) { CheckPermissionAttribute att = (CheckPermissionAttribute)CheckPermissionAttribute.GetCustomAttribute(m, typeof(CheckPermissionAttribute)); if (att != null) { Check(att.AllMask); } } return; } public void Check(Flag permissions) { if (!CheckPermission(permissions)) { string url = string.Format("MsgPage.aspx?msg={0}", HttpUtility.UrlEncode("您没有权限访问该资源")); Response.Redirect(url); } } public void Check(ApplicationID appID, Flag permissions) { PermissionManager pm= Spaces.PermissionManager.Instance(appType); if (!CheckPermission(pm,permissions)) { string url = string.Format("MsgPage.aspx?msg={0}", HttpUtility.UrlEncode("您没有权限访问该资源")); Response.Redirect(url); } } protected override void OnInit(EventArgs e) { CheckClass(); base.OnInit(e); } }
如何使用:
[CheckPermission(2, Flag.View)] public partial class MyPage : PageBase { }
若没有查看权限,会自运导向错误页面。
在类上应用挺方便。
方法上应用我于一个方法比较麻烦,我还没有找到在页面class里怎么获取当前调用的类名。
可以调用 CheckMethod(方法名称);
如:
[CheckPermission(2, Flag.Delete)] public partial class MyPage : PageBase { public void test() { CheckMethod("test"); ....... } }
这还是需要重复劳动。