基于资源名的MVC权限控制

  在程序复杂程度不断上升的过程当中,无可避免须要触碰到权限控制,而权限控制又与业务逻辑牢牢相关,市场上出现了大量的权限控制产品,而程序的开发,讲究去繁化简的抽象,在个人开发过程当中,逐渐发现程序的权限控制核心不外乎两个方面:一、资源定位;二、访问控制列表。本文主要针对资源定位进行分析,并解决一些我所碰见过的问题。而在MVC上,MVC提供给咱们了很是好的访问控制扩展机制,咱们可以经过这些机制更好地控制系统权限。git

  在咱们以前的开发中,针对ASP.NET下WebForm进行开发,不少人都采用了继承Page基类自定义BasePage,构造本身的验证逻辑后再将最终的展现页面继承于BasePage,全部的验证逻辑中,也必须解决我上文中提到的两个问题:资源定位和访问控制列表。而WebForm下使用路由机制的机会并很少,意义也并不大,在这个背景下,大多数人产生了这样的错觉:使用URL进行资源定位就够了。我所见过的几个项目,大多采用了这个办法,或者这个办法的细微变种。而WebForm的机制也决定了这个方法是具备必定可行性的,每个资源(页面)都是一个类,资源定位较为容易。而进入MVC时代后,一切都发生了改变。在MVC的场景下,你将遇到如下几个问题:github

  一、  路由机制的引入会出现一个资源多个URL的可能以及增长Area等参数后URL的不肯定性。ajax

  二、  Action重载的问题难以解决。例如一个页面Order,在Get方法下不带参访问是被容许的,而在Post下带参访问是被禁止的,但这两个资源的URL都是 /Order/,在一些第三方库的辅助下,甚至能有更多的重载Action,这就致使URL定位机制的全面失效。spa

 

  而面对上述状况,用一种什么样的方式进行资源的定位、控制就成为放在咱们面前的难题。在MVC下,能够认为每个方法(Action)即为一个资源(页面),用何种方法能在程序外部控制这些资源使得权限控制能对其轻松进行,这是整个问题的核心。上文说到,每一个方法即为一个资源,那咱们是否能够将方法名、方法签名做为资源的定位标识?答案是能够的。code

  在MVC下,进行权限控制,很天然地想到了使用自定义AuthorizationFilter来进行控制,那么在这个Attribute中是能够得到方法相关信息的。orm

var t = (ReflectedActionDescriptor) filterContext.ActionDescriptor;
var method= t.MethodInfo.ToString();

  在派生自FilterAttribute, IAuthorizationFilter的自定义Attribute中,能够根据上面两个方法获取方法的完整签名,包括返回类型、方法名、参数类型。blog

  经过这个方法是能够进行权限控制的,可是这个方法存在着一个致命的缺点:返回值的类型名、参数的类型名,有的是彻底限定名,即须要带命名空间,而有的是不彻底限定名。而这个彻底限定名的获取是较为繁琐的,所以,这个方法的可操做性大大下降。继承

  那如何优雅地定位到咱们所须要控制的资源呢?咱们是否能够为咱们所须要的资源取一个名字,而后在访问控制列表中将这个名字添加进去,每次执行这个Action的时候获取当前Action的名字,而后在访问控制列表中进行比对就能够解决这个问题。那如何将资源进行命名就成了解决问题的关键。代码级的资源控制必须想到元数据,而须要在元数据中增长信息,这个任务天然又落到Attribute的身上。ip

  咱们自定义一个Attribute为Action标注资源名称:资源

  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public sealed class ResourceCustomerNameAttribute : Attribute
    {
        private readonly string _resourceName;

        public ResourceCustomerNameAttribute(string resourceName)
        {
            _resourceName = resourceName;
        }

        public string ResourceName
        {
            get { return _resourceName; }
        }
    }

  这个Attribute实现的功能很简单:为资源进行命名(若是方便,使用ID会更高效一点)。

  完成了对资源的命名,接下来须要对资源与访问控制列表进行比对,我是这样使用的:继承FilterAttribute, IAuthorizationFilter,在派生类中先获取ResourceCustomerNameAttribute实例

  而后用attribute.ResourceName属性与咱们的访问控制列表进行比对:

    public void OnAuthorization(AuthorizationContext filterContext)
        {
            var acl = filterContext.HttpContext.Session[SessionName.PermissionSessionName] as IEnumerable<IAcl>;
                
            var attNames = filterContext.ActionDescriptor.GetCustomAttributes(typeof(ResourceCustomerNameAttribute), true) as IEnumerable<ResourceCustomerNameAttribute>;

            var anonymous =filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>;

            if (anonymous != null && anonymous.Any())
            {
                return;
            }
            if (acl == null || !acl.Any())
            {
                filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = _controller, action = _action }));
            }
            else
            {
                var joinResult = (from aclEntity in acl
                                  join attName in attNames on aclEntity.ResourceName equals attName.ResourceName
                                  select attName
                ).Any();

                if (joinResult)
                {
                    return;
                }
                else
                {
                    filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = _controller, action = _action }));
                }
            }
        }

  若是比对成功则获取相应访问许可,能够对资源进行访问,若比对失败则跳转至受权失败页面。

  而在实际的使用过程当中,咱们在登陆成功后,将构造用户的ACL,并将ACL存入Session中,以后用户在每一次的访问中均使用Session中的ACL进行比对,受权。

  在这个具体实现中,我有一个难以处理的问题:即用户访问页面时若未受权,则跳转到未受权提示页面,若用户进行基于Json传递的ajax调用时如何向用户提供合适的信息?以前考虑过使用ContentType来判断,但是后来以为这会致使开发之中的约定过多(更况且我对不少人对Http协议的了解程度并不抱有信心)。不知各位是否有更好的办法来实现多种错误返回信息?望不吝赐教。

  模块的具体代码已托管于GitHub:https://github.com/uliian/ULiiAnPermissionControlModule

  功能核心模块已经上传,示例正被GFW狂虐~各位稍安勿躁,若有靠谱的全局***方法的同窗也但愿悄悄告诉我一下~T.T

  欢迎参考,提出您的宝贵意见。

相关文章
相关标签/搜索