[转]Asp.Net大型项目实践(11)-基于MVC Action粒度的权限管理【续】【源码在这里】(在线demo,全部源码)
本文转自:http://www.cnblogs.com/legendxian/archive/2010/01/25/1655551.html
接上篇Asp.Net大型项目实践(10)-基于MVC Action粒度的权限管理(在线demo,全部源码)
在线Demo:
服务器:网通
端口:不要禁用1234端口应该就可以访问
注意:连了数据库的,时间仓促肯定有漏洞,不要捣乱哈:)
登录用户: 1.用户名:牛头人战士 密码:000000 权限:有全部菜单页面,不能进行数据库的更改操作(不影响录入体验)
2.用户名:老虎MM 密码:000000 权限:少两个菜单页面,不能进行数据库的更改操作(不影响录入体验)
3.用户名:admin 密码不公开 权限:所有权限
注:以上的实现都是通过权限管理s配置出的哈,没有任何硬编码
权限判断的边界
由于项目是基于MVC的,除去数据权限不说,功能权限的判断边界做在MVC 的Action上无疑是最好的选择,因为无论是一个页面,还是一个按钮,还是一次查询,都是通过Action请求实现的。这样我们只需要在每个Action请求执行之前进行权限判断就可以了,也不用折腾RBAC里的资源+操作=权限 这么麻烦。
菜单权限和功能权限
其实在MIS项目中,大多数的权限判断粒度还是页面级的,再加上我们还需要根据权限动态生成用户的菜单,所以我们把权限分成“菜单权限”和“功能权限”
菜单权限:在用户登录验证后,每个页面的请求都必须通过权限验证。
功能权限:默认客户进入页面后,页面的相关操作默认都不判断,只对显示维护出的功能权限进行权限判断。
这样有几个好处:一般情况下权限的配置简单了,因为只需要配置粗粒度的页面权限即可使用;增加了效率,不必每个Action执行之前都判断权限(虽然都做了缓存,但能少判断一次还是好的);完全不影响细粒度的权限判断,随时都可以增加对任何一个Action的权限判定
如何取Action功能权限
我们通过反射把所有的Action权限全部取出来,这样在维护选取的时候就比较方便了,也不会产生录入错误,如下图:
大家用Demo可以体验到我们模糊输入Action名称就可以找到我们想要的Action的,因为是配置选取用也不用担心什么反射的效率问题,其实大家从demo可以看到速度还是挺快的,在我真实的项目中Action中有上万个,拉出来一样是瞬时的,所以我觉得有时候吧,也别过于“谈反射色变”,呵呵
通过反射获取所有Action的代码如下:
代码 public IList<ActionPermission> GetAllActionByAssembly() { var result = new List<ActionPermission>();
var types = Assembly.Load("Demo.HIS.MVC").GetTypes();
foreach (var type in types) { if (type.BaseType.Name == "BaseController")//如果是Controller { var members = type.GetMethods(); foreach (var member in members) { if (member.ReturnType.Name == "ActionResult")//如果是Action {
var ap = new ActionPermission();
ap.ActionName = member.Name; ap.ControllerName = member.DeclaringType.Name.Substring(0, member.DeclaringType.Name.Length - 10); // 去掉“Controller”后缀 object[] attrs = member.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), true); if (attrs.Length > 0) ap.Description = (attrs[0] as System.ComponentModel.DescriptionAttribute).Description;
result.Add(ap); }
} } } return result; }
返回的IList<ActionPermission>就是系统中所有Action的集合,大家可看到我们通过BaseController找到了项目中所有的Controller,再通过ActionResult找到Controller中所有的Action。
不知道大家注意下拉出的Action有个描述属性,这个属性是通过在Action上定义DescriptionAttribute实现的,这样通过反射就能取到中文描述了,例如:为了实现页面的选取方便,我们还要实现对IList<ActionPermission>的分页和模糊查询,因为是变量级集合,这里我们使用Linq查询就可以了,代码如下:
[Description("访问功能权限管理页面")] [ViewPage] public ActionResult ActionPermission() { return View(); }
代码 public IList<ActionPermission> QueryActionPlist(string query, int start, int limit, out long total) { IList<ActionPermission> allActions = GetAllActionByAssembly();
total = (from a in allActions where a.ActionName.ToLower().Contains(query.ToLower()) select a).Count();
var result = (from a in allActions where a.ActionName.ToLower().Contains(query.ToLower()) select a).Skip(start).Take(limit);
return new List<ActionPermission>(result); }
把权限判断相关的数据都缓存起来提高效率
我们把当前登录用户的:用户信息,拥有菜单权限,拥有功能权限 放在Session里
我们把需要托管的所有Action功能权限放在 Appliction全局应用程序变量里
这样我们所有的权限相关判断都是从缓存中取数据,不需要频繁访问数据了。
相关代码懒得贴了,自己去下载的源码里翻吧....注意一下缓存相关都是通过ICache这个接口出的,搜一下就能找到
如何对每个Action进行拦截,在它执行之前判断权限
最土的办法就是在每个Action加一段权限判断的代码,哈哈...如果我要这样做的话,估计要被大家的砖头拍死。
看过本系列Asp.Net大型项目实践(7)-用Unity实现AOP之事务处理+为啥要用AOP(附源码)的朋友应该就能想到,这是一个典型的AOP应用场景。
由于Asp.net MVC的Filter机制其实就是Aop,所以我们直接使用它。熟悉Asp.net MVC的朋友估计知道里面其实自带的有一个AuthorizeAttribute的ActionFilter,但基本就是个玩具,本来我想继承它重写的,但无奈里面的filterContext没有ActionDescriptor属性,所以干脆不要它自己写个ActionFilter,代码如下:
代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.Mvc; using System.Web; using System.Security.Principal; using Demo.HIS.Infrastructure.Facade.Authority;
namespace Demo.HIS.MVC.CommonSupport.Filter { /// <summary> /// 权限拦截 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class AuthorizeFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext == null) { throw new ArgumentNullException("filterContext"); } var path = filterContext.HttpContext.Request.Path.ToLower(); if (path == "/" || path == "/Main/Login".ToLower() || path == "/Main/UserLogin".ToLower()) return;//忽略对Login登录页的权限判定 object[] attrs = filterContext.ActionDescriptor.GetCustomAttributes(typeof(ViewPageAttribute), true); var isViewPage = attrs.Length == 1;//当前Action请求是否为具体的功能页 if (this.AuthorizeCore(filterContext, isViewPage) == false)//根据验证判断进行处理 { //注:如果未登录直接在URL输入功能权限地址提示不是很友好;如果登录后输入未维护的功能权限地址,那么也可以访问,这个可能会有安全问题 if (isViewPage == true) { filterContext.Result = new HttpUnauthorizedResult();//直接URL输入的页面地址跳转到登陆页 } else { filterContext.Result = new ContentResult { Content = @"JsHelper.ShowError('抱歉,你不具有当前操作的权限!')" };//功能权限弹出提示框 } } } //权限判断业务逻辑 protected virtual bool AuthorizeCore(ActionExecutingContext filterContext, bool isViewPage) { if (filterContext.HttpContext == null) { throw new ArgumentNullException("httpContext"); }
if (!filterContext.HttpContext.User.Identity.IsAuthenticated) { return false;//判定用户是否登录 } var user = new CurrentUser();//获取当前用户信息 var controllerName = filterContext.RouteData.Values["controller"].ToString(); var actionName = filterContext.RouteData.Values["action"].ToString(); if (isViewPage && (controllerName.ToLower() != "main" && actionName.ToLower() != "masterpage"))//如果当前Action请求为具体的功能页并且不是MasterPage页 { if (user.MenuPermission.Count(m => m.ControllerName == controllerName && m.ActionName == actionName) == 0) return false; } else { var actions = ContainerFactory.GetContainer().Resolve<IAuthorityFacade>().GetAllActionPermission();//所有被维护的Action权限 if (actions.Count(a => a.ControllerName == controllerName && a.ActionName == actionName) != 0)//如果当前Action属于被维护的Action权限 { if (user.ActionPermission.Count(a => a.ControllerName == controllerName && a.ActionName == actionName) == 0) return false; } } return true; } } }
a.我的AuthorizeFilterAttribute继承了ActionFilterAttribute
b.重写ActionFilterAttribute的OnActionExecuting(ActionExecutingContext filterContext)方法,这个方法表示在每个Action执行之前处理
c.返回页面的Action上面要加ViewPageAttribute,这样我在Filter里才能区分当前Action是否为页面(注:由于是在Action执行之前,无法通过获取ActionResult的类型来判断)
d.权限判断和处理的逻辑自己看贴的代码里的注释,已经写的很清楚了
在哪儿加这个AuthorizeFilterAttribute呢?一个一个给Action或Controller加都太麻烦了,还记得我们所有的Controller都继承了BaseController吗?在他上面就完事了呗!这样整个系统要不权限就靠BaseController上的一个AuthorizeFilterAttribute说了算~,BaseController如下:
namespace Demo.HIS.MVC.CommonSupport { // 所有Controller都要继承的基类 [AuthorizeFilter] [ExceFilter] public class BaseController : Controller { } }
使用FormsAuthentication实现登录验证
用户登录验证用的是.Net自带的Forms验证,它默认通过加密的cookies来实现用户的登录判断
登录代码如下:
代码 [ExtResult] [Description("用户登录")] public ActionResult UserLogin() { string loginName = Request["loginname"]; string pwd = HospUser.GetPwdMD5(Request["pwd"]); if (!AuthorityFacade.Validate(loginName, pwd)) throw new ValidationException("用户名密码错误,或用户状态不可用");
FormsAuthentication.SetAuthCookie(loginName, true);//加入from验证票据 Cache.RemoveSessionCache("currentuser");//清空当前用户信息缓存 return this.JsonFormat(new ExtResult { success = true }); }
注销代码如下:
代码 //注销登陆 public ActionResult Logout() { FormsAuthentication.SignOut(); Cache.RemoveSessionCache("currentuser");//清空当前用户信息缓存 return this.JsonFormat(new ExtResult { success = true }); }
大伙期待的源码:
虽然是从公司实际项目中扒出来的,但整个设计,思想,架构和关键代码基本都是我一个人搞的,而且业务相关的东西我都去掉了所以大家可以随便用。
里面可能涉及到不少东西还没有讲到,在以后的系列中会详细说明。
另外有人说源码编译报错缺少“HibernatingRhinos.NHibernate.Profiler.Appender.DLL”,你可以把这个DLL删除,再根据错误信息删除一行代码即可,这个其实是之前讲过的NHProfiler,NH生成SQL查看工具用的DLL。
数据库(估计用SQLSERVER的比较多,所以我把数据库从oracle改成了SqlServer2005,直接附加就可以了):HISDemoDb.rar
总结
权限管理这部分暂时就介绍到这里 ,以后有机会再给大家介绍数据权限,权限与工作流引擎集成,单点登录,多级授权等复杂的权限管理实现。大家可以看到上面介绍权限管理还是有些特点的:
1.充分利用MVC特点;
2.思路清晰;
3.验证逻辑简单;
4.维护方便;
5.没有效率问题;
6.和其他代码完全解耦毫无依赖;
7.非常灵活能满足绝大多数需求;
8.扩展方便;
我想通过这个权限管理的例子和相关讨论,大家应该明白一个道理,只有真正适合自己当前项目的业务和技术特点的技术方案才是好方案。
在技术水平有限的情况下盲目追求什么通用,什么全适应,往往实现费时费力还不讨好。
在软件开发这个领域,真正的高手不在于写高深的代码;玩弄艰涩的术语;也不会不考虑实际价值,为了用技术而用技术;也不会胡吹海吹,堕落到为了赚钱就放弃技术。
理论和实践结合才是王道
还是那两句老话,软件开发没有银弹,一切为了需求
写这个系列的初衷主要还是想感谢.Net技术社区里的各位朋友,长期以来大家贡献的资料文章给我的工作和学习都有很大帮助。
也是受同事 1-2-3影响,本人潜水多年突然良心发现,觉得自己也应该对.Net技术社区做一点力所能及的事,所以写下此系列。
写这玩意儿一不求出名二不求搞头衔三不求赚钱,再加上本人个性,所以写的比较随意粗糙,有时还有恶搞。哈哈,没有啥严格的排版,也没有严谨的书面用语,有时候还有点针对性,如有不适还望大家海涵
大伙捧场我就写着Happy~~
[转]Asp.Net大型项目实践(11)-基于MVC Action粒度的权限管理【续】【源码在这里】(在线demo,全部源码)的更多相关文章
- 基于吉日嘎拉的通用权限管理Webform版老界面bug修复
虽然弄了新界面<基于吉日嘎底层架构的通用权限管理Web端UI更新:参考DTcms后台界面>,但老界面的一点菜单显示的问题还是让我这种强迫症揪心,终于今晚可以美美的睡觉了. 老代码用了Ses ...
- Asp.Net MVC大型项目实践整合 NHibernate与Json序列化
通过NHibernate我们多表查询是实现了 但由于查询出来的集合中的对象“不是平的”,如何在送到UI绑定成了问题.ExtJs UI组件的数据绑定支持多种格式,如简单数组,Json,Xml.在本项目中 ...
- 大数据项目实践:基于hadoop+spark+mongodb+mysql+c#开发医院临床知识库系统
一.前言 从20世纪90年代数字化医院概念提出到至今的20多年时间,数字化医院(Digital Hospital)在国内各大医院飞速的普及推广发展,并取得骄人成绩.不但有数字化医院管理信息系统(HIS ...
- 基于SSM + Redis的Shiro权限管理项目
概述 本教程结合SSM(SpringMVC + Mybatis)框架讲解Shiro,讲解的内容有自定义shiro拦截器,Shiro Freemarker标签,Shiro JSP标签,权限控制讲解. 详 ...
- 基于MVC和Bootstrap的权限框架解决方案 一.搭建HTML
因为某些原因,因为需要,最新要做一套客户管理系统,但是不满足于仅有的框架. 看了很多大牛写的框架,强大是强大,代码也太TM多了,乱七八糟话不多说,开始吧 随便在网上找到一套好看的HTML,看起来还不错 ...
- 基于吉日嘎拉的通用权限管理WebForm版扩展:字典选项管理和缓存管理
关于字典管理,其实就是2个表,一个表记录字典和对应表,另一个表记录字典内容.我这里改名为字典选项,其实是一个意思.直接上图: 这里的字典选项是分子系统的,每个子系统可以有自己的单独字典,方便管理.但是 ...
- 基于MVC和Bootstrap的权限框架解决方案 二.添加增删改查按钮
上一期我们已经搭建了框架并且加入了列表的显示, 本期我们来加入增删改查按钮 整体效果如下 HTML部分,在HTML中找到中意的按钮按查看元素,复制HTML代码放入工程中 <a class=&qu ...
- ASP.NET 5 入门(1) - 建立和开发ASP.NET 5 项目
ASP.NET入门(1) - 建立和开发ASP.NET 5 项目 ASP.NET 5 理解和入门 使用自定义配置文件 建立项目 首先,目前只有VS 2015支持开发最新的ASP.NET 5 程序,所以 ...
- ASP.NET入门(1) - 建立和开发ASP.NET 5 项目
原文转载自:http://www.cnblogs.com/zergcom/p/4493358.html 建立项目 首先,目前只有VS 2015支持开发最新的ASP.NET 5 程序,所以我们首先需要下 ...
随机推荐
- Liunx常用的100条命令汇存
1.关机 shutdown -h now 立刻关机 poweroff shutdown -r now 立刻重启 reboot logout 注销 2.进入图形界面 startx 3.vi编辑器 [vi ...
- 14、OpenCV Python 直线检测
__author__ = "WSX" import cv2 as cv import numpy as np #-----------------霍夫变换------------- ...
- 【python】10分钟教你用python一行代码搞点大新闻
准备 相信各位对python的语言简洁已经深有领会了.那么,今天就带大家一探究竟.看看一行python代码究竟能干些什么大新闻.赶紧抄起手中的家伙,跟我来试试吧. 首先你得先在命令行进入python. ...
- GG and MM HDU - 3595 Every-SG
$ \color{#0066ff}{ 题目描述 }$ 两堆石子,GG和MM轮流取,每次在一堆石子中取另一堆石子的k\((k\ge1)\)倍,不能操作的输 现在二人要玩n个这样的游戏,每回合每个人对每个 ...
- External Tools
Preferences偏好设置-External Tools External Tools: External Script Editor:外部脚本编辑器,通过此项可以切换您所擅用的脚本的编辑器 Ed ...
- django基础知识之Response对象
HttpResponse对象 在django.http模块中定义了HttpResponse对象的API HttpRequest对象由Django自动创建,HttpResponse对象由程序员创建 不调 ...
- C++_代码重用1-总览
C++的主要目的是促进代码重用. 公有继承是实现这一目标的机制之一: 本身是另一个类的成员,这种方法称为包含.组合.层次化. 另一种方法是使用私有.保护继承. 通常包含.私有继承和保护继承用于实现ha ...
- vue-awesome-swiper 的 使用
1.确保 package.json文件中有 "vue-awesome-swiper": "^3.1.3",没有的话install下 2.在main.js配置 ...
- UVALive - 2678 二分/尺取
题意:求最小的长度L满足该长度上的元素和大于等于S 最近dp做多了总有一种能用dp解决一切的错觉 二分长度解决 #include<iostream> #include<algorit ...
- v-model 用在组件中
官方文档: 使用自定义事件的表单输入组件 官方也说明了,v-model只不过是一个语法糖而已,真正的实现靠的还是 1. v-bind : 绑定响应式数据 2. 触发 input 事件 并传递数据 (核 ...