ASP.NET 系列:RBAC权限设计
权限系统的组成通常包括RBAC模型、权限验证、权限管理以及界面访问控制。现有的一些权限系统分析通常存在以下问题:
(1)没有权限的设计思路
认为所有系统都可以使用一套基于Table设计的权限系统。事实上设计权限系统的重点是判断角色的稳定性和找出最小授权需求。角色的稳定性决定了系统是通过角色判断权限还是需要引入RBAC方式,最小授权需求防止我们过度设计导致超出授权需求的权限粒度。
(2)没有独立的RBAC模型的概念
直接使用实体类表示RBAC模型,导致本身本应该只有几行代码且可以在项目级别复用的RBAC模型不仅不能复用,还要在每个项目无论是否需要都要有User、Role、Permission等实体类,更有甚者把实体类对应的数据表的结构和关联当作权限系统的核心。
(3)权限的抽象错误
我们通常既不实现操作系统也不实现数据库,虽然操作系统的权限和数据库的权限可以借鉴,但一般的业务系统上来就弄出一堆增删该查、访问和执行这样的权限,真是跑偏的太远了。首先业务层次的操作至少要从业务的含义出发,叫浏览、编辑、审核等这些客户容易理解或就是客户使用的词汇更有意义,更重要的是我们是从角色中按照最小授权需求抽象出来的权限,怎么什么都没做就有了一堆权限呢。
(4)将界面控制和权限耦合到一起
开始的时候我们只有实体类Entities、应用服务Service以及对一些采用接口隔离原则定义的接口Interfaces,通常这个时候我们在Service的一个或多个方法会对应1个权限,这个时候根本界面还没有,就算有界面,也是界面对权限的单向依赖,对于一个系统,可能不止有1个以上类型的客户端,每个客户端的界面访问控制对权限的依赖都应该存储到客户端,况且不同的客户端对这些数据各奔没有办法复用。
下面我们使用尽可能少的代码来构建一个可复用的既不依赖数据访问层也不依赖界面的RBAC模型,在此基础上对角色的稳定性和权限的抽象做一个总结。
1.创建RBAC模型
使用POCO创建基于RBAC0级别的可复用的User、Role和Permissin模型。
using System.Collections.Generic; namespace RBACExample.RBAC
{
public class RBACUser
{
public string UserName { get; set; } public ICollection<RBACRole> Roles { get; set; } = new List<RBACRole>();
} public class RBACRole
{
public string RoleName { get; set; } public ICollection<RBACPermission> Permissions { get; set; } = new List<RBACPermission>();
} public class RBACPermission
{
public string PermissionName { get; set; }
}
}
2.创建安全上下文
创建安全上下文RBACContext用于设置和获取RBACUser对象。RBACContext使用线程级别的静态变量保存RBACUser对象,不负责实体类到RBAC对象的转换,保证复用性。
using System; namespace RBACExample.RBAC
{
public static class RBACContext
{
[ThreadStatic]
private static RBACUser _User; private static Func<string, RBACUser> _SetRBACUser; public static void SetRBACUser(Func<string, RBACUser> setRBACUser)
{
_SetRBACUser = setRBACUser;
} public static RBACUser GetRBACUser(string username)
{
return _User == null ? (_User = _SetRBACUser(username)) : _User;
} public static void Clear()
{
_SetRBACUser = null;
}
}
}
3.自定义RoleProvider
自定义DelegeteRoleProvider,将权限相关的GetRolesForUser和IsUserInRole的具体实现委托给静态代理,保证复用性。
using System;
using System.Web.Security; namespace RBACExample.RBAC
{
public class DelegeteRoleProvider : RoleProvider
{
private static Func<string, string[]> _GetRolesForUser; private static Func<string, string, bool> _IsUserInRole; public static void SetGetRolesForUser(Func<string, string[]> getRolesForUser)
{
_GetRolesForUser = getRolesForUser;
} public static void SetIsUserInRole(Func<string, string, bool> isUserInRole)
{
_IsUserInRole = isUserInRole;
} public override string[] GetRolesForUser(string username)
{
return _GetRolesForUser(username);
} public override bool IsUserInRole(string username, string roleName)
{
return _IsUserInRole(username, roleName);
} #region NotImplemented #endregion NotImplemented
}
}
在Web.config中配置DelegeteRoleProvider
<system.web>
<compilation debug="true" targetFramework="4.5.2"/>
<httpRuntime targetFramework="4.5.2"/>
<authentication mode="Forms">
<forms loginUrl="~/Home/Login" cookieless="UseCookies" slidingExpiration="true" />
</authentication>
<roleManager defaultProvider="DelegeteRoleProvider" enabled="true">
<providers>
<clear />
<add name="DelegeteRoleProvider" type="RBACExample.RBAC.DelegeteRoleProvider" />
</providers>
</roleManager>
</system.web>
4.配置RBACContext和DelegeteRoleProvider
在Application_Start中配置RBACContext和DelegeteRoleProvider依赖的代理。为了便于演示我们直接创建RBACUser对象,在后文中我们再针对不同系统演示实体类到RBAC模型的映射。
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
RBACContext.SetRBACUser(u =>
{
return new RBACUser
{
UserName = u,
Roles = new List<RBACRole> {
new RBACRole
{
RoleName="admin",
Permissions = new List<RBACPermission> {
new RBACPermission {
PermissionName="admin"
}
}
}
}
};
});
DelegeteRoleProvider.SetGetRolesForUser(userName => RBACContext.GetRBACUser(userName).Roles.SelectMany(o => o.Permissions).Select(p => p.PermissionName).ToArray());
DelegeteRoleProvider.SetIsUserInRole((userName, roleName) => RBACContext.GetRBACUser(userName).Roles.SelectMany(o => o.Permissions).Any(p => p.PermissionName == roleName));
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
5.在ASP.NET MVC中通过.NET API使用
User.IsInRole和AuthorizeAttribute此时都可以使用,我们已经完成了一个RBAC权限中间层,即隔离了不同系统的具体实现,也不用使用新的API调用。如果是服务层,使用Thread.CurrentPrincipal.IsInRole和PrincipalPermissionAttribute。
namespace RBACExample.Controllers
{
public class HomeController : Controller
{
public ActionResult Login(string returnUrl)
{
FormsAuthentication.SetAuthCookie("admin", false);
return Redirect(returnUrl);
} public ActionResult Logoff()
{
FormsAuthentication.SignOut();
return Redirect("/");
} public ActionResult Index()
{
return Content("home");
} [Authorize]
public ActionResult Account()
{
return Content(string.Format("user is IsAuthenticated:{0}", User.Identity.IsAuthenticated));
} [Authorize(Roles = "admin")]
public ActionResult Admin()
{
return Content(string.Format("user is in role admin:{0}", User.IsInRole("admin")));
}
}
}
6.扩展AuthorizeAttribute,统一配置授权
AuthorizeAttribute的使用将授权分散在多个Controller中,我们可以扩展AuthorizeAttribute,自定义一个MvcAuthorizeAttribute,以静态字典保存配置,这样就可以通过代码、配置文件或数据库等方式读取配置再存放到字典中,实现动态配置。此时可以从Controller中移除AuthorizeAttribute。如前文所述,客户端的访问控制与权限的匹配应该存储到客户端为最佳,即使存放到数据库也不要关联权限相关的表。
namespace RBACExample.RBAC
{
public class MvcAuthorizeAttribute : AuthorizeAttribute
{
private static Dictionary<string, string> _ActionRoleMapping = new Dictionary<string, string>(); public static void AddConfig(string controllerAction, params string[] roles)
{
var rolesString = string.Empty;
roles.ToList().ForEach(r => rolesString += "," + r);
rolesString = rolesString.TrimStart(',');
_ActionRoleMapping.Add(controllerAction, rolesString);
} public override void OnAuthorization(AuthorizationContext filterContext)
{
var key = string.Format("{0}{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName);
if (_ActionRoleMapping.ContainsKey(key))
{
this.Roles = _ActionRoleMapping[key];
base.OnAuthorization(filterContext);
}
}
}
}
通过GlobalFilterCollection配置将MvcAuthorizeAttribute配置为全局Filter。
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
MvcAuthorizeAttribute.AddConfig("AccountIndex");
MvcAuthorizeAttribute.AddConfig("AdminIndex", Permission.AdminPermission);
filters.Add(new MvcAuthorizeAttribute());
}
7.按需设计实体类
当RBAC模型不直接依赖实体类时,实体类可以按需设计,不再需要为了迁就RBAC的关联引入过多的实体,可以真正做到具体问题具体分析,不需要什么系统都上Role、Permission等实体类,对于角色稳定的系统,既减少了系统的复杂度,也减少了大量后台的功能实现,也简化了后台的操作,不用什么系统都上一套用户头疼培训人员也头疼的权限中心。
(1)使用属性判断权限的系统
有些系统,比如个人博客,只有一个管理员角色admin,admin角色是稳定的权限不变的,所以既不需要考虑使用多个角色也不需要再进行权限抽象,因此使用User.IsAdmin属性代替Role和Permission就可以,没必要再使用Role和Permission实体类,增大代码量。后台进行权限管理只需要实现属性的编辑。
RBACContext.SetRBACUser(u =>
{
var user = new UserEntity { UserName = "admin", IsAdmin = true };
var rbacUser = new RBACUser { UserName = user.UserName };
if (user.IsAdmin)
{
rbacUser.Roles.Add(new RBACRole
{
RoleName = "admin",
Permissions = new List<RBACPermission> {new RBACPermission {
PermissionName="admin"
} }
});
}
return rbacUser;
});
(2)使用角色判断权限的系统
有些系统,比如B2C的商城,虽然有多个角色,但角色都是稳定的权限不变的,使用User和Role就可以,没有必要为了应用RBAC而引入Permission类,强行引入虽然实现了Role和Permission的分配回收功能,但实际上不会使用,只会使用User的Role授权功能。权限的抽象要做到满足授权需求即可,在角色就能满足授权需求的情况下,角色和权限的概念是一体的。后台实现权限管理只需要实现对用户角色的管理。
(3)需要对角色进行动态授权的系统
有些系统,比如ERP,有多个不稳定的角色,每个角色通常对应多项权限,由于组织机构和人员职责的变化,必须对角色的权限进行动态分配,需要使用User、Role和Permission的组合。User由于权限范围的不同,通常具有一个或多个权限,不同的User具有的角色通常不再是平行关系而是层级关系,如果不从Role中抽象Permission,需要定义大量的Role对应不同权限的组合,遇到这种情况时,分离权限,对角色进行权限管理就成了必然。后台实现权限管理即需要实现对用户角色的管理也需要实现对角色权限的管理。
RBACContext.SetRBACUser(u =>
{
var user = ObjectFactory.GetInstance<IUserService>().GetUserByName(u);
return new RBACUser
{
UserName = user.UserName,
Roles = user.Roles.Select(r => new RBACRole
{
RoleName = r.RoleName,
Permissions = r.Permissions.Select(p => new RBACPermission
{
PermissionName = p.Name
}).ToList()
}).ToList()
};
});
8.总结
使用RBAC模型和.NET的权限验证API解决了权限系统的复用问题,从角色的稳定性出发防止实体类规模膨胀,通过最小授权需求的抽象可以防止权限的滥用。
参考:
(1)https://en.wikipedia.org/wiki/Role-based_access_control
(2)http://csrc.nist.gov/groups/SNS/rbac/faq.html
(3)http://www.codeproject.com/Articles/875547/Custom-Roles-Based-Access-Control-RBAC-in-ASP-NET
(4)http://www.ibm.com/developerworks/cn/java/j-lo-rbacwebsecurity/
ASP.NET 系列:RBAC权限设计的更多相关文章
- ASP.NET MVC+EF框架+EasyUI实现权限管理系列(13)-权限设计
原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(13)-权限设计 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇) (1):框架搭建 (2):数据 ...
- ASP.NET MVC +EasyUI 权限设计(二)环境搭建
请注明转载地址:http://www.cnblogs.com/arhat 今天突然发现博客园出问题了,老魏使用了PC,手机,平板都访问博客园了,都是不能正常的访问,原因是不能加载CSS,也就是不能访问 ...
- ASP.NET MVC +EasyUI 权限设计(一)开篇
在前一段时间中,老魏的确非常的忙碌,Blog基本上没有更新了,非常的抱歉,那么在后面的时间中,老魏会尽量的抽时间来写的,可能时间上就不太富裕了.今天开始呢,老魏会和大家分享一下关于权限设计的有关文章, ...
- RBAC 权限设计(转载)
来源 :https://blog.csdn.net/rocher88/article/details/43190743 这是我在网上找的一些设计比较好的RBAC权限管理 不知道,像新浪.搜狐.网易.百 ...
- RBAC权限设计(一)
序言 RBAC表结构 用户表 角色表 权限表 用户角色(关系)表 角色权限(关系)表 sql脚本 /* Navicat MySQL Data Transfer Source Server : 127. ...
- ASP.MVC 基于AuthorizeAttribute权限设计案例
ASP.MVC上实现权限控制的方法很多,比如使用AuthorizeAttribute这个特性 1.创建自定义特性用于权限验证 public class AuthorizeDiy : Authorize ...
- 22-1 rbac权限设计
一 表结构设计 from django.db import models # Create your models here. from django.db import models # Creat ...
- RBAC权限设计
http://blog.csdn.net/ms_x0828/article/details/7035956 RBAC 模型作为目前最为广泛接受的权限模型 角色访问控制(RBAC)引入了Role的概念, ...
- RBAC权限设计实例
http://blog.csdn.net/painsonline/article/details/7183629 实现业务系统中的用户权限管理 B/S系统中的权限比C/S中的更显的重要,C/S系统因为 ...
随机推荐
- MFC分类
屏幕截图(带光标) MFC Button控件自绘 WM_CTLCOLOR消息 MFC窗口创建.销毁消息流程 DDX_Control.SubclassWindow和SubclassDlgItem 隐藏系 ...
- 关于点击ztree的节点将页面生成到easyui的新增选项卡(easyui-tabs)时,总是在浏览器中生成一个新的页面的问题
最近的项目中用到了easyui,还有ztree菜单.在这里将我遇到的一些问题写出来算是做个笔记吧. 这是我头一次在博客园里分享代码,我的处女作,写的不好的地方还望各位见谅! 由于很久没有写过前台的东西 ...
- Linux find/grep命令
一.Find 1)批量删除文件 find . -name "*.h~" -exec rm '{}' \; 2)定位文件某一行 find / -name "demo.con ...
- Hadoop 1.0 和 2.0 中的数据处理框架 - MapReduce
1. MapReduce - 映射.化简编程模型 1.1 MapReduce 的概念 1.1.1 map 和 reduce 1.1.2 shufftle 和 排序 MapReduce 保证每个 red ...
- jmeter的使用(一)
1.下载jmeter:http://jmeter.apache.org/download_jmeter.cgi 2.启动jmeter,打开jmeter.bat 3.添加线程组 4.添加http请求 5 ...
- Zbrush遮罩边界该怎么实现羽化和锐化
很多情况下为了雕刻制图需要,在ZBrush®中不仅要使用边缘清晰的遮罩,有时还要将遮罩边缘变得模糊,做羽化效果.那么如何在ZBrush中实现羽化遮罩效果或锐化遮罩效果,本文将做详细讲解. 若有疑问可直 ...
- OpenCV的安装与系统环境变量
OpenCV的安装与系统环境变量 安装OpenCV本来是很简单的一件事,但配置却很麻烦.而且在配置过程中尤为重要的步骤就是系统环境变量的配置.我使用的是CodeBlick13.12与OpenCV1.0 ...
- nginx 一二事(3) - 反向代理以及负载均衡
先来说说正向代理 就是普通的代理,记得高中年代偷跑去网吧是要办卡的 题外话: 就是这货...相信很多80同龄人都有吧... 回到正题,那正向代理就不让你直接访问网络,而需要登录一下网吧的某个系统 ...
- C# Reflection BindingFlags
定义包含在搜索中的成员 下列 BindingFlags 筛选标志可用于定义包含在搜索中的成员: 为了获取返回值,必须指定 BindingFlags.Instance 或 BindingFlags.St ...
- java11-6 String类的其它功能
String类的其他功能: 替换功能: String replace(char old,char new) String replace(String old,String new) 去除字符串两空格 ...