asp.net mvc 全局权限过滤器及继成权限方法
全局权限过滤器
//----------------------------------------------------------------------- // <copyright file="PermissionFilter.cs" company="STO EXPRESS, Ltd."> // Copyright (c) 2015 , All rights reserved. // </copyright> //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; namespace DotNet.MVCInfrastructure.Filter { using DotNet.Model; using DotNet.MVCInfrastructure.Attributes; using DotNet.MVCInfrastructure.Common; using DotNet.MVCInfrastructure.Enumerations; using DotNet.MVCInfrastructure.Models; using DotNet.Utilities; using DotNet.Business; /// <summary> /// 身份验证过滤器 /// /// 1、匿名访问 /// 2、登录就可以访问 /// 3、需要验证是否有菜单或按钮或资源的权限 /// /// /// 修改纪录 /// /// 2015-10-11 版本:1.0 SongBiao 创建文件。 /// /// <author> /// <name>SongBiao</name> /// <date>2015-10-11</date> /// </author> /// </summary> public class CheckPermissionFilter : IAuthorizationFilter { /// <summary> /// 认证和授权是两个方面 /// </summary> /// <param name="filterContext"></param> public void OnAuthorization(AuthorizationContext filterContext) { string pageUrl = filterContext.HttpContext.Request.Url == null ? "$$#$$" : filterContext.HttpContext.Request.Url.AbsolutePath; //OperateContext.GetThisPageUrl(false); //NLogHelper.Debug("CheckPermissionFilter:" + DateTime.Now + ",pageUrl=" + pageUrl); if (filterContext == null) { throw new ArgumentNullException("filterContext"); } if (filterContext.HttpContext.Request.Url == null) { throw new ArgumentNullException("filterContext"); } // 是否是Ajax请求 var bAjax = filterContext.HttpContext.Request.IsAjaxRequest(); // 注意 所有允许匿名访问的Controller,Action 都要设置匿名访问标签 // 只对 Action 做判断 // 判断 控制器 是否可以匿名访问 var controllerAnonymous = filterContext.Controller.GetType().GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>; // 先判断控制器是否可以匿名访问 if ((controllerAnonymous != null && controllerAnonymous.Any())) { // 再检查Action 是否有登录检查标签 var checkLoginActionAttr = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>; if (checkLoginActionAttr != null && checkLoginActionAttr.Any()) { } else { // 如果没有 让他可以继续访问 因为把Controller 设置为 AllowAnonymous 了 return; } // 匿名就可以访问 无需验证登录状态 //return; } // 判断 Action 是否可以匿名访问 var actionAnonymous = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>; if (actionAnonymous != null && actionAnonymous.Any()) { // 匿名就可以访问 无需验证登录状态 return; } // 1、允许匿名访问 用于标记在授权期间要跳过 AuthorizeAttribute 的控制器和操作的特性 // 2017-03-06 检查OpenId 只需传来openId if (!string.IsNullOrWhiteSpace(filterContext.HttpContext.Request["openId"])) { string openId = filterContext.HttpContext.Request["openId"]; UserLogOnResult userLogOnResult = Business.Utilities.LogOnByOpenId(openId, true); if (userLogOnResult != null && !string.IsNullOrEmpty(userLogOnResult.StatusCode) && userLogOnResult.StatusCode == Status.OK.ToString()) { // 用户状态存储 OperateContext.Current.AddCurrent(userLogOnResult.UserInfo); } } else if (!string.IsNullOrWhiteSpace(filterContext.HttpContext.Request["AuthorizationCode"])) { // 2017-05-23 检查传过来的AuthorizationCode 支持code跳转登录 string authorizationCode = filterContext.HttpContext.Request["AuthorizationCode"]; string openId; if (BaseUserManager.VerifyAuthorizationCode(null, authorizationCode, out openId)) { // 用户状态存储 UserLogOnResult userLogOnResult = Business.Utilities.LogOnByOpenId(openId, true); if (userLogOnResult != null && !string.IsNullOrEmpty(userLogOnResult.StatusCode) && userLogOnResult.StatusCode == Status.OK.ToString()) { // 用户状态存储 OperateContext.Current.AddCurrent(userLogOnResult.UserInfo); } } } // 2、判断是否登录或登录已超时 需要重新登录 if (OperateContext.Current.UserInfo == null) { // 用户状态已过期 // 判断请求是否是Ajax请求 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; jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = jsonResult; } else { //filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Area="",Controller = "Account", action = "Login" })); filterContext.Result = new RedirectResult(BusinessSystemInfo.LoginUrl + "?returnUrl=" + pageUrl); } } else { // 用户状态未过期 // 3、拒绝某个账号登录当前系统 判断用户是否在拒绝登录的子系统中 //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 = "Prompt", action = "DenyAccess", bAjaxReq = false, message = "没有获取您拥有的权限菜单,请尝试重新登录。" })); // } //} //else if (OperateContext.Current.UserInfo.IsAdministrator) { // 超级管理员不检查权限了 return; } else { // 4、判断用户是否能够访问当前的controller,action // 5、判断登录状态 根据Controller或Action上的标签 某些功能只需判断是否登录 // 判断Controller上是否有CheckLoginAttribute标签 只需要登录就可以访问 // 实际上检查登录也不好,应该检查改Action是否是公开的 上面做为临时用 某些系统没有菜单或者没有配置的Action可以这样做 否则都必须配置菜单 // 判断Controller上是否有CheckLoginAttribute标签 只需要登录就可以访问的 var checkLoginControllerAttr = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>; if (checkLoginControllerAttr != null && checkLoginControllerAttr.Any()) { // 否则 没有设置 CheckLogin 标签的 就要验证 向下执行 到这里 除了设置 CheckLogin 的action 都要检查菜单访问权限 return; } // 判断action上是否有CheckLoginAttribute标签 只需要登录就可以访问的 var checkLoginActionAttr = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>; if (checkLoginActionAttr != null && checkLoginActionAttr.Any()) { return; } // 如果有 CheckActionPermission 就过 让CheckActionPermission处理 var checkPermissionActionActionAttr = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckActionPermissionAttribute), true) as IEnumerable<CheckActionPermissionAttribute>; if (checkPermissionActionActionAttr != null && checkPermissionActionActionAttr.Any()) { return; } // 6、有些要判断是否有某个controller或 action的权限 通过获取的权限菜单进行比较 // 用户具有的菜单 var moduleList = OperateContext.Current.UserPermission;//.GetPermissionList(false); 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; jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = jsonResult; //var jsonResult = new JsonResult { Data = new BaseModels { IsError = true, ErrMsg = "请先登录!", ErrCode = "unlogin" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; //filterContext.RequestContext.HttpContext.Response.Write(JsonConvert.SerializeObject(result)); //filterContext.RequestContext.HttpContext.Response.End(); } else { //filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Area = "", Controller = "Error", action = "NoAccess", bAjaxReq = false, message = "没有获取您拥有的权限菜单,请尝试重新登录。" })); filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Area = "", Controller = "Error", action = "NoAccess", bAjaxReq = false, message = "没有获取到任何菜单,您没有权限访问当前内容:" + pageUrl + "。" })); } } else { // 获取到菜单,进行比较 //var controllerName = (filterContext.RouteData.Values["controller"]).ToString().ToLower(); //var actionName = (filterContext.RouteData.Values["action"]).ToString().ToLower(); //var areaName = (filterContext.RouteData.DataTokens["area"] ?? "").ToString().ToLower(); /* 这个方式需要在controller或action上人为添加一个Attribute,没有必要,直接可以取到当前的controller和actionName // 用于标记在授权期间需要CustomerResourceAttribute 的操作的特性 var attNames = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CustomerResourceAttribute), true) as IEnumerable<CustomerResourceAttribute>; // 判断用户的权限菜单中的code是否与控制器上标示的资源的code一致 var joinResult = (from aclEntity in moduleList join attName in attNames on aclEntity.Code equals attName.ResourceName select attName).Any(); if (!joinResult) */ // 子系统菜单配置时,子系统的菜单code不能重复 // 同时支持通过访问地址,Code来判断 // 主要是要在全局过滤器里添加 filters.Add(new CheckPermissionFilter()); // 检查地址是否与当前Action的地址一致 var modules = from module in moduleList where string.Equals(module.NavigateUrl, pageUrl, StringComparison.OrdinalIgnoreCase) //|| string.Equals(module.Code, controllerName, StringComparison.OrdinalIgnoreCase) //|| string.Equals(module.Code, actionName, StringComparison.OrdinalIgnoreCase) select module; //string results = JsonHelper.SerializeObject(moduleList); if (!modules.Any()) { // 菜单没有配置或者没有权限 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; jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Area = "", Controller = "Error", action = "NoAccess", bAjaxReq = false, message = "在您的权限中,您没有权限访问当前内容:" + pageUrl + "。" })); } } else { return; } } } } } } }
放在全局过滤器中即可,实现全部Action的访问控制
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { // filters.Add(new HandleErrorAttribute()); filters.Add(new ElmahHandleErrorAttribute()); // 全局身份验证过滤器 更方便 filters.Add(new CheckPermissionFilter()); } }
继承权限的使用场景:
在某些情况下,只要具有其中一个Action的权限,那么跟他关联的Action权限可以不用配置,
如在查询场景中,配置了用户访问 Public ActionResult Index()主页面的权限,查询时请求的是 Public ActionResult List(......)
那么在List上加上标签
[CheckActionPermission("/XXXArea/XXXController/Index")]
Public ActionResult List(......)
这样就可以了,
CheckActionPermission里的参数是要继续的Action的菜单路径。
避免为List再配置菜单,授权。
另外象添加页面,保存的action Save可以不必配置菜单授权,只要在上面配置[CheckActionPermission("/XXXArea/XXXController/Add")]即可。既然能看到添加界面,那么保存操作就应该是允许的。
//----------------------------------------------------------------------- // <copyright file="CheckActionPermissionAttribute" company="STO, Ltd."> // Copyright (c) 2017 , All rights reserved. // </copyright> //----------------------------------------------------------------------- using System; using System.Linq; using System.Web.Mvc; namespace DotNet.MVCInfrastructure.Attributes { using DotNet.MVCInfrastructure.Enumerations; using DotNet.MVCInfrastructure.Models; using DotNet.MVCInfrastructure.Common; using DotNet.Utilities; /// <summary> /// CheckActionPermissionAttribute /// 记权限拦截 /// /// 修改纪录 /// /// 2017-07-21 版本:1.0 SongBiao 创建文件。 /// /// <author> /// <name>SongBiao</name> /// <date>2017-07-21</date> /// </author> /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class CheckActionPermissionAttribute : ActionFilterAttribute, IActionFilter { /// <summary> /// 检查的Module /// </summary> private string Module; /// <summary> /// 构造函数 module表示检查哪个菜单的权限即可 /// 如某些列表,只需要检查主页面的菜单权限即可 /// CheckActionPermission["/Headquarters/DaTouBiMain/Index"] /// </summary> /// <param name="module"></param> public CheckActionPermissionAttribute(string module) { Module = module; } FilterContextInfo fcinfo; /// <summary> /// 在执行操作方法之前由 ASP.NET MVC 框架调用。 /// </summary> /// <param name="filterContext"></param> public override void OnActionExecuting(ActionExecutingContext filterContext) { string pageUrl = filterContext.HttpContext.Request.Url == null ? "$$#$$" : filterContext.HttpContext.Request.Url.AbsolutePath; //OperateContext.GetThisPageUrl(false); var bAjax = filterContext.HttpContext.Request.IsAjaxRequest(); if (string.IsNullOrWhiteSpace(Module)) { if (bAjax) { BusinessResultBase result = new BusinessResultBase(); result.Title = "参数值不可以为空"; result.Status = false; result.StatusCode = BusinessStatusCode.AccessDeny.ToString(); result.StatusMessage = "CheckActionPermission参数值不可以为空。"; var jsonResult = new JsonResult(); jsonResult.Data = result; jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Area = "", Controller = "Error", action = "NoAccess", bAjaxReq = false, message = "CheckActionPermission参数值不可以为空" })); } } else { if (OperateContext.Current.UserInfo == null) { 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; jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectResult(BusinessSystemInfo.LoginUrl + "?returnUrl=" + pageUrl); } } else { fcinfo = new FilterContextInfo(filterContext); string areaName = fcinfo.AreaName; string controllerName = fcinfo.ControllerName; string actionName = fcinfo.ActionName; // 当前访问的 string currentModule = "/"; if (!string.IsNullOrWhiteSpace(areaName)) { currentModule = currentModule + areaName; } if (!string.IsNullOrWhiteSpace(controllerName)) { currentModule = currentModule + "/" + controllerName; } if (!string.IsNullOrWhiteSpace(actionName)) { currentModule = currentModule + "/" + actionName; } var moduleList = OperateContext.Current.UserPermission; // 比较Module在菜单中是否存在 如列表的权限只需要验证主界面的权限即可 var modules = from module in moduleList where !string.IsNullOrWhiteSpace(module.NavigateUrl) && string.Equals(module.NavigateUrl.Trim(), Module.Trim(), StringComparison.OrdinalIgnoreCase) //|| string.Equals(module.Code, controllerName, StringComparison.OrdinalIgnoreCase) //|| string.Equals(module.Code, actionName, StringComparison.OrdinalIgnoreCase) select module; //var modules = moduleList.Where(t => string.Equals(t.NavigateUrl, Module, StringComparison.OrdinalIgnoreCase)); if (modules == null || !modules.Any()) { // 菜单没有配置或者没有权限 if (bAjax) { BusinessResultBase result = new BusinessResultBase(); result.Title = "没有访问权限"; result.Status = false; result.StatusCode = BusinessStatusCode.AccessDeny.ToString(); result.StatusMessage = "在您的权限中,您没有权限访问当前内容:" + currentModule + ",需要授予用户对:" + Module + "的权限。"; var jsonResult = new JsonResult(); jsonResult.Data = result; jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = jsonResult; } else { filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Area = "", Controller = "Error", action = "NoAccess", bAjaxReq = false, message = "在您的权限中,您没有权限访问当前内容:" + currentModule + ",需要授予用户对:" + Module + "的权限。" })); } } else { return; } } } } /// <summary> /// 在执行操作方法后由 ASP.NET MVC 框架调用。 /// </summary> /// <param name="filterContext"></param> public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); } /// <summary> /// OnResultExecuted 在执行操作结果后由 ASP.NET MVC 框架调用。 /// </summary> /// <param name="filterContext"></param> public override void OnResultExecuted(ResultExecutedContext filterContext) { base.OnResultExecuted(filterContext); } /// <summary> /// OnResultExecuting 在执行操作结果之前由 ASP.NET MVC 框架调用。 /// </summary> /// <param name="filterContext"></param> public override void OnResultExecuting(ResultExecutingContext filterContext) { base.OnResultExecuting(filterContext); } public class FilterContextInfo { public FilterContextInfo(ActionExecutingContext filterContext) { #region 获取链接中的字符 DomainName = filterContext.HttpContext.Request.Url.Authority; AreaName = (filterContext.RouteData.DataTokens["area"] ?? "").ToString(); ControllerName = filterContext.RouteData.Values["controller"].ToString(); ActionName = filterContext.RouteData.Values["action"].ToString(); #endregion } /// <summary> /// 获取域名 /// </summary> public string DomainName { get; set; } /// <summary> /// 获取 controllerName 名称 /// </summary> public string AreaName { get; set; } /// <summary> /// 获取 controllerName 名称 /// </summary> public string ControllerName { get; set; } /// <summary> /// 获取ACTION 名称 /// </summary> public string ActionName { get; set; } } } }