前言
以前也写过两篇关于权限这个话题的文章和,最近在新的项目中,权限设计这块做了重新的考虑和设计。一直有人说权限这个东西不能太死,所以我们本着把权限尽量做到透明化,而设计了这样的权限管理。
设计思路
1、因为项目使用的是asp.net mvc,在mvc中一个action就对应的是一个URL,一般来说一个action只会做一件事情,所以我们获取请求的action就可以知道你将要干什么,那么我们把系统中所有的模块全部存放到数据库中,并且把功能按钮也放到数据库中,可以构造成一个树形菜单的形式。如图:
其中,这里分为导航权限和功能权限,导航权限指:系统运行时会自动根据权限分配加载到导航树上去的;功能权限指:这个页面上有哪些按钮。这里我采用一个标识来区分哪些为导航权限,哪些为功能权限。因为登录系统,加载导航栏时只需要获取导航权限进行验证就OK了。
2、保存用户权限,在角色赋权的页面,直接加载整个导航表中的数据,采用树形展示,在角色表中保存模块表的主键ID即可。
3、为角色关联用户则可以直接关联用户的ID或者工号什么的都可以。
4、拉取权限加载导航,用户登录即可去角色表中获取到该用户拥有哪些角色。获取模块表ID,根据模块表ID,获取模块表数据,剔除功能权限,然后动态加载导航。
5、功能权限的验证方案,这也是考虑很久的一个地方,以前的思路就是直接传一个参数值去判断是否显示这个按钮。这次采用了对HTML进行剔除的方式进行功能按钮的隐藏和显示。
具体实现
1、导航权限验证:采用拦截器,在action执行前进行权限验证。
public override void OnActionExecuting(ActionExecutingContext filterContext){ //当前访问地址 string Code = string.Format("/{0}/{1}", filterContext.RouteData.Values["controller"].ToString(), filterContext.RouteData.Values["action"].ToString()).ToLower(); string[] ListResource = ResourceBiz.Instance.Get(new string[] { "Url" }).Select(p => p.Url).Where(p => !string.IsNullOrEmpty(p)).Select(p => p.ToLower()).ToArray();//获取模块表导航代码这个字段与当前URL比对 if (!ListResource.Contains(Code))//模块表中不存在的URL,默认不进行验证 return; if (!Authentication.GetResourceCode().Contains(Code))//Authentication.GetResourceCode()为当前用户所拥有的权限 { //验证不通过 ContentResult Content = new ContentResult(); Content.Content = ""; filterContext.Result = Content; } }
2、验证页面的功能权限:也是采用拦截器,在action执行后,获取即将渲染的HTML源码进行分析及剔除操作。
//获取即将呈现的HTML,剔除功能按钮public override void OnResultExecuted(ResultExecutedContext filterContext){ //用户控件则不进行筛选 if (!filterContext.IsChildAction) filterContext.HttpContext.Response.Filter = new WhitespaceFilter(filterContext.HttpContext.Response, filterContext);//重写}
//重写public class WhitespaceFilter : System.IO.MemoryStream{ private System.IO.Stream Filter = null; private ResultExecutedContext filterContext = null; private string Source = string.Empty; //构造函数,用来接收变量 public WhitespaceFilter(HttpResponseBase HttpResponseBase, ResultExecutedContext filterContexts) { Filter = HttpResponseBase.Filter; filterContext = filterContexts; } //读取HTML源码 public override void Write(byte[] buffer, int offset, int count) { Source += System.Text.Encoding.UTF8.GetString(buffer);//HTML源码 } //分析进行权限处理 public override void Close() { //当前访问地址 string Code = string.Format("/{0}/{1}", filterContext.RouteData.Values["controller"].ToString(), filterContext.RouteData.Values["action"].ToString()).ToLower(); string[] ListResource = ResourceBiz.Instance.Get(new string[] { "Url" }).Select(p => p.Url).Where(p => !string.IsNullOrEmpty(p)).Select(p => p.ToLower()).ToArray();//获取模块表所有记录 if (ListResource.Contains(Code))//模块表中不存在的URL,默认不进行验证 { //解析处理 HtmlDocument Document = new HtmlDocument(); Document.LoadHtml(Source); HtmlNode htmlNode = Document.DocumentNode; /*这里需要获取这个模块下所有的共功能权限,然后跟你所拥有的这个页面的功能权限比对,如果不拥有这个功能权限,则可以根据规则获取到这段HTML,然后删除掉*/ /*_______________________开始分析处理功能按钮,这里可以自己增加验证规则___________________________*/ HtmlNodeCollection hnc = htmlNode.SelectNodes("//a");//获取需要验证的功能按钮HTML,由开发人员自己定义,你也可以给个特定的标识来标识这个标签为功能按钮,譬如:htmlNode.SelectNodes("//a[@class='add']");获取神马的 if (hnc != null) { foreach (HtmlNode node in hnc) { //拿到所有A标签,然后把href取出来,跟当前用户所拥有的功能权限比对,如果相等或者包含则删除 string CodeStr = node.Attributes["href"] != null ? node.Attributes["href"].Value.ToLower() : ""; node.ParentNode.RemoveAll(); } } } Filter.Write(System.Text.Encoding.UTF8.GetBytes(Source), 0, System.Text.Encoding.UTF8.GetByteCount(Source)); base.Close(); }}
这样设计的话,系统中关于权限的地方就全部透明化了。只是在功能权限验证时,要把所有情况全部考虑进去,不知道从文字中大家能不能明白我所需要表达的意思,文笔实在太差了。