在项目开发中,为了安全、方便地判断用户是否有访问当前资源(Action)的权限,我们一般通过全局过滤器来实现。
Asp.net MVC 页面中常见的权限判断使用过滤器主要在以下几种情况(根据权限判断的先后顺序):
1、判断要访问的Controller或Action是可以匿名访问;
2、判断要访问的Controller或Action是否登录就可以访问;
3、判断是否有访问某个Controller的Action的权限;
4、资源访问权限,如在下拉菜单中获取当前账号可以显示几个下拉项;
通过在全局过滤器中逐项判断,我们可以让有权限的用户顺利通过,没有权限的就会被禁止掉,下面来看看过滤器的代码实现:
public class CheckPermissionFilter : IAuthorizationFilter { /// <summary> /// 权限判断 /// </summary> /// <param name="filterContext"></param> public void OnAuthorization(AuthorizationContext filterContext) { if (filterContext == null) { throw new ArgumentNullException("filterContext"); } if (filterContext.HttpContext.Request.Url == null) { throw new ArgumentNullException("filterContext"); } string pageUrl = filterContext.HttpContext.Request.Url.AbsolutePath; //OperateContext.GetThisPageUrl(false); // 是否是Ajax请求 var bAjax = filterContext.HttpContext.Request.IsAjaxRequest(); // 1、允许匿名访问 用于标记在授权期间要跳过 AuthorizeAttribute 的控制器和操作的特性 var actionAnonymous = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>; var controllerAnonymous = filterContext.Controller.GetType().GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>; if ((actionAnonymous != null && actionAnonymous.Any()) || (controllerAnonymous != null && controllerAnonymous.Any())) { return; } // 判断是否登录或登录已超时 需要重新登录 if (Infrastructure.OperateContext.Current.UserInfo == null) { if (bAjax) { BusinessResultBase result = new BusinessResultBase(); result.Title = "未登录或登录已超时"; result.Status = false; result.StatusCode = BusinessStatusCode.LoginTimeOut.ToString(); result.StatusMessage = "请重新登录系统。"; var jsonResult = new JsonResult(); jsonResult.Data = result; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Account", action = "Login" })); } } else { // 2、拒绝某个账号登录当前系统 if (OperateContext.Current.IsDenyVisit()) { if (bAjax) { BusinessResultBase result = new BusinessResultBase(); result.Title = "拒绝访问当前系统"; result.Status = false; result.StatusCode = BusinessStatusCode.AccessDeny.ToString(); result.StatusMessage = "您的账号不允许访问当前系统。"; var jsonResult = new JsonResult(); jsonResult.Data = result; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Common", action = "DenyAccess", bAjaxReq = false, message = "没有获取您拥有的权限菜单,请尝试重新登录。" })); } } else { // 3、判断登录状态 Controller Action 标签 某些功能只需判断是否登录 用户没登录 调到登录页面 // 判断Controller上是否有CheckLoginAttribute标签 只需要登录就可以访问 var checkLoginControllerAttr = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>; if (checkLoginControllerAttr != null && checkLoginControllerAttr.Any()) { return; } // 判断action上是否有CheckLoginAttribute标签 只需要登录就可以访问 var checkLoginActionAttr = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>; if (checkLoginActionAttr != null && checkLoginActionAttr.Any()) { return; } // 4、有些要判断是否有某个菜单 action的权限 具体判断某个用户是否有某个权限 // 用于标记在授权期间需要CustomerResourceAttribute 的操作的特性 var attNames = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CustomerResourceAttribute), true) as IEnumerable<CustomerResourceAttribute>; // 用户具有的菜单 var moduleList = OperateContext.Current.GetPermissionList(); if (moduleList == null || !moduleList.Any()) { if (bAjax) { BusinessResultBase result = new BusinessResultBase(); result.Title = "没有访问权限"; result.Status = false; result.StatusCode = BusinessStatusCode.AccessDeny.ToString(); result.StatusMessage = "没有获取您拥有的权限菜单,请尝试重新登录。"; var jsonResult = new JsonResult(); jsonResult.Data = result; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Common", action = "DenyAccess", bAjaxReq = false, message = "没有获取您拥有的权限菜单,请尝试重新登录。" })); } } else { // 判断用户的权限菜单中的code是否与控制器上标示的资源的code一致 var joinResult = (from aclEntity in moduleList join attName in attNames on aclEntity.Code equals attName.ResourceName select attName).Any(); if (!joinResult) { if (bAjax) { BusinessResultBase result = new BusinessResultBase(); result.Title = "没有访问权限"; result.Status = false; result.StatusCode = BusinessStatusCode.AccessDeny.ToString(); result.StatusMessage = "您没有访问权限:" + pageUrl; var jsonResult = new JsonResult(); jsonResult.Data = result; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Common", action = "DenyAccess", bAjaxReq = false, message = "您没有访问权限:" + pageUrl })); } } else { return; } } } } } }
基于这样的过滤器,我们的Controller和Action该怎么写呢?下面分别举出几个demo
1、匿名访问的Controller,这是一个测试安装环境中数据库连接是否连通的功能:
/// <summary> /// InstallationController /// /// 安装环境测试 /// /// 2015-12-25 版本:1.0 SongBiao 创建文件。 /// /// <author> /// <name>SongBiao</name> /// <date>2015-12-25</date> /// </author> /// </summary> [AllowAnonymous] public class InstallationController : Controller { // // GET: /InstallationEnvironment/ public ActionResult Index() { string result; try { // 获取数据库时间 测试数据库是否通 using (IDbHelper dbHelper = DbHelperFactory.GetHelper(BaseSystemInfo.UserCenterDbType, BaseSystemInfo.UserCenterDbConnection)) { result ="数据库已连通,当前数据库时间:"+ DateTime.Parse(dbHelper.GetDbDateTime()).ToString(BaseSystemInfo.DateTimeFormat); } } catch (Exception ex) { result="数据库连接异常:"+ex.Message; } ViewBag.Result = result; return View(); } }
这个头部增加了AllowAnonymous标签,实现匿名访问功能,用户不需登录即可访问。
2、只需要登录就可以访问的功能
namespace DotNet.MVC.Controllers { using DotNet.Business; using DotNet.Model; using DotNet.MVC.Attributes; using DotNet.MVC.Infrastructure; /// <summary> /// 系统菜单权限 /// /// 修改纪录 /// /// 2015-09-09 版本:1.0 SongBiao 创建文件。 /// /// <author> /// <name>SongBiao</name> /// <date>2015-09-09</date> /// </author> /// </summary> [CheckLogin] public class ItemsSystemController : BaseController { // // GET: /ItemsSystem/ public ActionResult Index() { return View(); } /// <summary> /// 返回全部子系统下拉列表数据 /// </summary> /// <returns></returns> public ActionResult GetSystemSelect() { string tableName = "ItemsSystem"; Hashtable hashTable = new Hashtable(); List<BaseItemDetailsEntity> list = null; var itemDetailsManager = new BaseItemDetailsManager(UserCenterDbHelper, UserInfo, tableName); // 管理员取得所有数据 if (UserInfo.IsAdministrator) { list= itemDetailsManager.GetList<BaseItemDetailsEntity>(new KeyValuePair<string, object>(BaseItemDetailsEntity.FieldDeletionStateCode, 0) , BaseItemDetailsEntity.FieldSortCode); } else { string permissionCode = "Resource.ManagePermission"; BasePermissionScopeManager permissionScopeManager = new BasePermissionScopeManager(UserCenterDbHelper, UserInfo); string[] ids = permissionScopeManager.GetResourceScopeIds(UserInfo.Id, tableName, permissionCode); list = itemDetailsManager.GetList<BaseItemDetailsEntity>(ids); } if (list != null && list.Any()) { var result = (from p in list select new { key = p.ItemName, value = p.ItemValue }).ToList(); hashTable.Add("list", result); } else { hashTable.Add("list", null); } return Json(hashTable, JsonRequestBehavior.AllowGet); } } }
这里在控制器上使用 CheckLogin 标签来实现只要用户登录就可以访问当前控制器里的Action,当然也可以指定Controller下某个Action只要登录就可以访问,如下写法
#region public ActionResult LogOff() 退出 /// <summary> /// 退出 /// </summary> /// <returns></returns> [CheckLogin] public ActionResult LogOff() { try { OperateContext.Current.RemoveCurrent(); return RedirectToAction("Login", "Account"); } catch (Exception) { return RedirectToAction("Login", "Account"); } } #endregion
那么,上面的CheckLogin怎么写呢?请看如下实现(OperateContext.Current.UserInfo是我实现的上下文中获取当前用户的方法,Views层也可以使用)。
namespace DotNet.MVC.Attributes { using DotNet.MVC.Infrastructure; /// <summary> /// CheckLoginAttribute /// 用于检测用户是否处于登录状态的标签 /// 某些功能只需要用户登录就可以使用 /// /// 修改纪录 /// /// 2015-10-11 版本:1.0 SongBiao 创建文件。 /// /// <author> /// <name>SongBiao</name> /// <date>2015-10-11</date> /// </author> /// </summary> public class CheckLoginAttribute :System.Web.Mvc.AuthorizeAttribute //AuthorizeAttribute { protected override bool AuthorizeCore(HttpContextBase httpContext) { bool pass = false; // httpContext.Session[DotNet.Business.Utilities.SessionName] //!httpContext.Request.IsAuthenticated if (OperateContext.Current.UserInfo==null) { httpContext.Response.StatusCode = 401;//无权限状态码 pass = false; } else { pass = true; } return pass; } protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { base.HandleUnauthorizedRequest(filterContext); if (filterContext.HttpContext.Response.StatusCode == 401) { filterContext.Result = new RedirectResult("/Account/Login"); } } } }
3、判断是否具有某个Action的权限
/// <summary> /// 获取列表数据 /// </summary> /// <param name="quoteId"></param> /// <param name="pager"></param> /// <param name="sort"></param> /// <param name="direction"></param> /// <param name="firstHaveData"></param> /// <returns></returns> [AjaxRequest] [CustomerResource("XpressionList")] public ActionResult GetList(string quoteId, QuiGridPager pager, string sort, string direction, bool firstHaveData = true) { XpressionManager xpressionManager = new XpressionManager(BusinessDbHelper, UserInfo); List<XpressionEntity> list = xpressionManager.ExecuteReaderByPage(quoteId, pager, sort, direction, "*", firstHaveData).ToList<ZtoQuoteExpressionEntity>(); return JsonQuiGridPager(pager, list, pager.totalRows, sort, direction, StartTime, firstHaveData); }
通过在Action 上添加标签[CustomerResource("XpressionList")]实现,在过滤去中会自动去判断当前账号是否具有这个Action的访问权限,这个标签的实现如下,
namespace DotNet.MVC.Attributes { /// <summary> /// CustomerResourceAttribute /// /// 自定义的对方法应用的属性,在Action上标注权限菜单对应的Code /// /// 修改纪录 /// /// 2015-10-11 版本:1.0 SongBiao 创建文件。 /// /// <author> /// <name>SongBiao</name> /// <date>2015-10-11</date> /// </author> /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public sealed class CustomerResourceAttribute : Attribute { private readonly string _resourceName; public CustomerResourceAttribute(string resourceName) { _resourceName = resourceName; } /// <summary> /// 资源名称 /// </summary> public string ResourceName { get { return _resourceName; } } /// <summary> /// 资源描述 /// </summary> public string Descript { get; set; } } }
这里CustomerResourceAttribute是实现权限细分判断的关键点,在权限配置中,我们把每一个Action都存储到数据库中,实现界面(B/S)
通过Code实现当前用户是否具有该权限(当然也可以通过ID),Code实现了不可重复的检查。
目前这个过滤器底层使用了通用权限管理系统,主要用于权限菜单的存储及判断,数据库及功能设计上完全考虑到了各种形式的判断(按用户、公司、角色)。结合Asp.net MVC,轻轻松松就实现了权限过滤器功能。开发人员只需要会配置系统菜单及系统角色,通过接口或底层dll完全就可以了,开发人员只需关注业务功能的实现。
其它语言,如有类似权限判断全局访问过滤器的功能,也可参照实现。
对以上功能,如有疑问,欢迎一起探讨~~~
原文:http://www.cnblogs.com/hnsongbiao/p/5087359.html