• 2_MVC+EF+Autofac(dbfirst)轻型项目框架_用户权限验证


    前言

      接上面两篇 0_MVC+EF+Autofac(dbfirst)轻型项目框架_基本框架 与 1_MVC+EF+Autofac(dbfirst)轻型项目框架_core层(以登陆为例) 。在第一篇中介绍了此架构的基本分层,在第二篇中,以登陆功能为例,介绍了项目的代码结构。在本篇中将通过过滤器实现用户权限验证功能。

      同样,文中有问题的地方欢迎批评指正!谢谢!

    开发背景 

      在一个常规系统中权限验证是不可缺的,在较简单的系统中,用户只会被简单归为登陆用户和游客,而在较为复杂的系统中,除了判断用户是否登陆外,还需提供一套可靠的机制来验证用户是否拥有执行其请求的操作的权限。在ASP.Net MVC框架中的AuthorizeAttribute过滤器很好的满足了这个要求。在我项目中,也是通过AuthorizeAttribute来实现对用户权限的判断。其中为了便捷,引入了Helper类来统一管理所有的Session和Cookie。

    创建过程

     1.权限验证模式与表结构

      MVC框架中存在的控制器提供了很好的权限范围的单位,所以在我的框架中,权限是验证是以过滤器为基础的,一个过滤器代表一个权限,不同的身份拥有不同的权限集合,而每一个用户归属于一个身份。例如用户甲的身份为超级管理员身份,他可能同时具有A,B,C,D,E,F这六个权限;而权限相对较低的用户乙为普通管理员身份,他可能仅仅具备A,B,C,D这四个权限。

      数据库的表结构如下图:

      可以看到,在教师表中存在一个字段用来标志对应的Power的ID(PID),power表记录所有身份(power这个词用的有点怪 :-)),Authority表中记录了所有权限,解释下Authority中每一个字段的具体含义:

        AUID:该权限的对应ID。

        AUPID:该权限隶属的父权限,例如:学生管理,教师管理的父级权限可能为管理中心。

        Name:权限的名字。

        AOrder:为了方便将权限转化为用户可见的控件操作树,安排权限的排名先后顺序。

        URL:这个权限所对应打开的URL。

        Controller:这个权限对应的控制器。

      Power与Authority之间的AuthorityToPower映射了每一个身份所拥有的权限。

     2.AuthorizeAttribute过滤器

      在介绍AuthorizeAttribute之前有必要先介绍下我web的结构,通过下图可以看到在根路由两个控制器的基础上,我还拥有两个区域,分别为学生和教师。所以在AuthorizeAttribute过滤器中,通过判断区域名来实施具体的权限验证操作。结合业务逻辑,在学生区域下,过滤器的功能仅仅是判断学生的登陆状态,如未登陆跳转到登陆界面;而在教师区域下,除了要判断是否登陆外还要判断其所请求的控制器是否在他的权限集合中。

      AuthorizeAttribute中代码如下

      1 using EDUA_ICore;
      2 using System;
      3 using System.Collections.Generic;
      4 using System.Linq;
      5 using System.Web;
      6 using System.Web.Mvc;
      7 
      8 namespace EDUA_WEB.Filters
      9 {
     10     /// <summary>
     11     /// 身份(权限)过滤器
     12     /// </summary>
     13     public class MyAuthorizeAttribute:AuthorizeAttribute
     14     {
     15         /// <summary>
     16         /// core操作上下文
     17         /// </summary>
     18         private ICoreSession iCoreSession;
     19 
     20         #region 构造方法 传入过滤器
     21         public MyAuthorizeAttribute(ICoreSession iCoreSession)
     22         {
     23             this.iCoreSession = iCoreSession;
     24         } 
     25         #endregion
     26 
     27         #region 重写权限验证器
     28         public override void OnAuthorization(AuthorizationContext filterContext)
     29         {
     30             //检测是否贴有跳过标签
     31             if (!DoesSkip<Filters.SkipAttribute>(filterContext))
     32             {
     33                 //获取区域名
     34                 string strArea = "AreaIsNull";
     35                 if (filterContext.RouteData.DataTokens.Count > 0)
     36                 {
     37                     strArea = filterContext.RouteData.DataTokens["area"].ToString().ToLower();
     38                 }
     39                 //获取控制器名
     40                 string strController = filterContext.RouteData.Values["controller"].ToString().ToLower();
     41                 //获取方法名
     42                 string strAction = filterContext.RouteData.Values["action"].ToString().ToLower();
     43 
     44                 //filterContext.HttpContext.Response.Write(strArea + "--" + strController + "--" + strAction);
     45                 OperateHelper.BussinessHelper h = new OperateHelper.BussinessHelper(iCoreSession);
     46                 //学生域之下
     47                 if (strArea == "student")
     48                 {
     49                     if (h.StudentSession == null)
     50                     {
     51                         filterContext.Result = new RedirectResult("/home/login"); 
     52                     }
     53                 }
     54                 //教师域之下
     55                 else if (strArea == "teacher")
     56                 {
     57                     if (h.TeacherSession == null)
     58                     {
     59                         //如果在主页控制器下 则直接跳转 其他 提示过期 
     60                         if (strController == "teacherhome")
     61                         {
     62                             //filterContext.HttpContext.Response.Redirect("/home/login");
     63                             filterContext.Result = new RedirectResult("/home/login"); 
     64                         }
     65                         else
     66                         {
     67                             filterContext.HttpContext.Response.Write("登陆已过期,请刷新页面");
     68                             filterContext.HttpContext.Response.End();
     69                         }
     70 
     71                     }
     72                     else
     73                     {
     74                         //判断是否对应权限
     75                         if (!CheckPermission(strController, h.TeacherSession.AuthorityList))
     76                         {
     77                             filterContext.HttpContext.Response.Write("你的请求超出了你的权限,如修改过系统权限,请重新登录系统");
     78                             filterContext.HttpContext.Response.End();
     79                         }
     80                     }
     81                 }
     82             }
     83         } 
     84         #endregion
     85 
     86         #region 检测是否贴有某标签 + bool DoesSkip<T>(AuthorizationContext filterContext) where T : Attribute
     87         /// <summary>
     88         /// 检测是否贴有某标签
     89         /// </summary>
     90         /// <typeparam name="T">标签type</typeparam>
     91         /// <param name="filterContext">上下文</param>
     92         /// <returns>是否贴有标签</returns>
     93         bool DoesSkip<T>(AuthorizationContext filterContext) where T : Attribute
     94         {
     95             if (!filterContext.ActionDescriptor.IsDefined(typeof(T), false) && !filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(T), false))
     96             {
     97                 return false;
     98             }
     99             return true;
    100         }
    101         #endregion
    102 
    103         #region 检验访问的管理员控制器是否与权限对应 + bool CheckPermission(string strController, List<WebModel.Authority> aul)
    104         /// <summary>
    105         /// 检验访问的管理员控制器是否与权限对应
    106         /// </summary>
    107         /// <param name="filterContext">控制器名</param>
    108         /// <param name="permission">权限列表</param>
    109         /// <returns>是否有权限</returns>
    110         private bool CheckPermission(string strController, List<WebModel.Authority> aul)
    111         {
    112             bool ret = false;
    113             //将请求的控制器名与权限session中的控制器表进行匹配  如果有,则匹配通过
    114             for (int i = 0; i < aul.Count(); i++)
    115             {
    116                 if (aul[i].Controller.ToLower() == strController)
    117                 {
    118                     ret = true;
    119                     break;
    120                 }
    121             }
    122             return ret;
    123         } 
    124         #endregion
    125     }
    126 }

       MyAuthorizeAttribute继承于AuthorizeAttribute,想要了解他的工作原理需要深入学习Asp.Net MVC的生命周期,网上已经有很多资料,在此就不赘述了。

      86行的DoesSkip利用反射来判断待检测的控制器是否贴有跳过检测的标签,如有DoesSkip标签则跳过检测直接访问。这是因为在Global中注册了全局过滤器,如果对类似于登陆操作的控制器也执行权限验证,这将是一个无限的死循环。所以需要在HelperController与HomeController等控制器上添上Skip标签。

      具体验证功能是怎么实现的,参考代码上的注释。

    3.统一管理Session与Cookie

      也许你已经注意到了,在上面的过滤器中,存在一个BussinessHelper类,其实它的名字和它的功能并没有很大的联系,只是当初在整合时用了原先的部分代码,而又没有修改类名,这个类所实现的功能只是统一管理所有的Session与Cookie信息,并没有其他复杂的业务上的操作。完整代码如下

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Web;
      5 using System.Web.SessionState;
      6 
      7 
      8 namespace EDUA_WEB.OperateHelper
      9 {
     10     public class BussinessHelper
     11     {
     12         #region 对象保存名称
     13         /// <summary>
     14         /// 验证码保存名
     15         /// </summary>
     16         const string VCODE = "vcode";
     17         /// <summary>
     18         /// 教师信息保存名
     19         /// </summary>
     20         const string TEACHER_INFOKEY = "tinfo";
     21         /// <summary>
     22         /// 学生信息保存名
     23         /// </summary>
     24         const string STUDENT_INFOKEY = "sinfo"; 
     25         #endregion
     26 
     27         #region 构造方法
     28         public BussinessHelper() { }
     29 
     30         /// <summary>
     31         /// 构造方法
     32         /// </summary>
     33         /// <param name="coreSession">业务操作对象</param>
     34         public BussinessHelper(EDUA_ICore.ICoreSession coreSession)
     35         {
     36             this.iCoreSession = coreSession;
     37         } 
     38         #endregion
     39 
     40         #region http上下文的对象们
     41         /// <summary>
     42         /// 业务操作对象
     43         /// </summary>
     44         private EDUA_ICore.ICoreSession iCoreSession { get; set; }
     45 
     46         /// <summary>
     47         /// 当前Http上下文
     48         /// </summary>
     49         private HttpContext ContextHttp
     50         {
     51             get
     52             {
     53                 return HttpContext.Current;
     54             }
     55         }
     56 
     57         /// <summary>
     58         /// session对象
     59         /// </summary>
     60         private HttpSessionState Session
     61         {
     62             get
     63             {
     64                 return ContextHttp.Session;
     65             }
     66         }
     67 
     68         /// <summary>
     69         /// Cookie对象
     70         /// </summary>
     71         private HttpCookieCollection Cookies
     72         {
     73             get
     74             {
     75                 return ContextHttp.Request.Cookies;
     76             }
     77         }
     78 
     79         /// <summary>
     80         /// Response 对象
     81         /// </summary>
     82         HttpResponse Response
     83         {
     84             get
     85             {
     86                 return ContextHttp.Response;
     87             }
     88         } 
     89         #endregion
     90 
     91         #region 验证码session对象
     92         /// <summary>
     93         /// 验证码session设置
     94         /// </summary>
     95         public string Vcode
     96         {
     97             get
     98             {
     99                 if (Session[VCODE] == null)
    100                     return "";
    101                 return Session[VCODE].ToString();
    102             }
    103             set
    104             {
    105                 Session[VCODE] = value;
    106             }
    107         } 
    108         #endregion
    109 
    110         #region 学生对象session操作
    111         /// <summary>
    112         /// 学生类session
    113         /// </summary>
    114         public WebModel.Student StudentSession
    115         {
    116             get
    117             {
    118                 WebModel.Student student = Session[STUDENT_INFOKEY] as WebModel.Student;
    119                 if (student == null)
    120                 {
    121                     string id = this.StudentSIDCookie;
    122                     if (id == "")
    123                     {
    124                         return null;
    125                     }
    126                     WebModel.ReturnVal rv = iCoreSession.IStudent.GetStudentBySID(id);
    127                     if (rv.Statu == WebModel.ReturnStatu.Success)
    128                     {
    129                         student = rv.Data as WebModel.Student;
    130                         //写入session
    131                         Session[STUDENT_INFOKEY] = student;
    132                     }
    133                     else
    134                     {
    135                         return null;
    136                     }
    137 
    138                 }
    139                 return student;
    140             }
    141             set
    142             {
    143                 Session[STUDENT_INFOKEY] = value;
    144             }
    145         } 
    146         #endregion
    147 
    148         #region 学生对象cookie操作
    149         public string StudentSIDCookie
    150         {
    151             get
    152             {
    153                 if (Cookies[STUDENT_INFOKEY] == null)
    154                 {
    155                     return "";
    156                 }
    157                 else
    158                 {
    159                     return EDUA_Util.EncrypHelper.DeEncryp(Cookies[STUDENT_INFOKEY].Value.ToString());
    160                 }
    161             }
    162             set
    163             {
    164                 HttpCookie cookie = new HttpCookie(STUDENT_INFOKEY, EDUA_Util.EncrypHelper.ToEncryp(value.ToString()));
    165                 cookie.Expires = DateTime.Now.AddHours(2);
    166                 Response.Cookies.Add(cookie);
    167             }
    168         } 
    169         #endregion
    170 
    171         #region 教师对象Session操作
    172         public WebModel.Teacher TeacherSession
    173         {
    174             get
    175             {
    176                 WebModel.Teacher teacher = Session[TEACHER_INFOKEY] as WebModel.Teacher;
    177                 if (teacher == null)
    178                 {
    179                     string id = this.TeacherTIDCookie;
    180                     if (id == "")
    181                     {
    182                         return null;
    183                     }
    184                     WebModel.ReturnVal rv = iCoreSession.ITeacher.GetTeacherByID(id);
    185                     if (rv.Statu == WebModel.ReturnStatu.Success)
    186                     {
    187                         teacher = rv.Data as WebModel.Teacher;
    188                         Session[TEACHER_INFOKEY] = teacher;
    189                     }
    190                     else
    191                     {
    192                         return null;
    193                     }
    194                 }
    195                 return teacher;
    196             }
    197             set
    198             {
    199                 Session[TEACHER_INFOKEY] = value;
    200             }
    201         } 
    202         #endregion
    203 
    204         #region 教师对象Cookie操作
    205         public string TeacherTIDCookie
    206         {
    207             get
    208             {
    209                 if (Cookies[TEACHER_INFOKEY] == null)
    210                 {
    211                     return "";
    212                 }
    213                 else
    214                 {
    215                     return EDUA_Util.EncrypHelper.DeEncryp(Cookies[TEACHER_INFOKEY].Value.ToString());
    216                 }
    217             }
    218             set
    219             {
    220                 HttpCookie cookie = new HttpCookie(TEACHER_INFOKEY, EDUA_Util.EncrypHelper.ToEncryp(value.ToString()));
    221                 cookie.Expires = DateTime.Now.AddHours(2);
    222                 Response.Cookies.Add(cookie);
    223             }
    224         } 
    225         #endregion
    226 
    227     }
    228 }
    View Code

      它的构造方法需要传入核心类实例,因为当保存在客户端的Session过期,而用户又保存了Cookie的情况下,需要通过对加密的Cookie解密来产生新的session保存并返回。所以这里需要在Core中的teacher和student中添加对应的根据GUID返回教师(学生)实体的方法。

      在前一篇登陆操作中,也使用了这个类,所要说明的是在teacher对象中,教师的权限列表在登陆时被取出放入了Session中,可以防止每一次验证连接数据库,减轻数据库负担。

    4.生成对应权限菜单(树)

      这里我采用了EasyUI的异步加载来生成权限访问列表,效果如下图

      在它的左侧为其对应权限列表映射的权限树。对应生成权限树的控制器代码如下

    1         #region 1.1 生成左侧菜单
    2         public ActionResult GetMenuData()
    3         {
    4             OperateHelper.BussinessHelper h = new OperateHelper.BussinessHelper();
    5             List<WebModel.Authority> authList = h.TeacherSession.AuthorityList;
    6             List<WebModel.EasyUIModel.TreeNode> tl = WebModel.EasyUIModel.TreeNode.getTreeNodeListByAuModelList(authList);
    7             return Content(EDUA_Util.WEB.DataHelper.Obj2Json(tl));
    8         } 
    9         #endregion

      可以看到,它的权限列表也是从session中取出,并不需要再次连接数据库。具体的easyui的代码因为不涉及到框架本身,所以我就不提供了。

    写在最后

      到这里,一个轻型框架已经可使用了。

      最后说下关于分享源代码的问题:

      我当初写这三篇博客的出发点:第一是为了理清我的思路,第二为将来可能接手系统的同学提供扩展开发时的参考,所以我并没有把这些文章分享到博客园的首页。但还是谢谢一些关注到这些文章的朋友。有一些朋友通过微博等问我能不能给下源代码,其实大多数的核心的代码在以上三篇文章中都已经给出了,如果仔细看完一定是能形成框架。至于完整的源代码,因为已经存在不少的业务上的内容,重新整理需要不少时间,也不切实际,所以恕我不能打包上传了,但还是谢谢大家的关注。如果有需要交流的,可以私信我的新浪微博@导弹林瀚,再次谢谢大家!

    转载请注明出处 huhuhuo的博客园

    地址:http://www.cnblogs.com/linhan/p/4320862.html 

  • 相关阅读:
    form提交上传图片
    存储过程分页
    表格隔行换色效果
    C#获取本地局域网IP
    sql 拆分字符串并循环取值
    sql日期转换
    HDU 4858
    HDU 1199
    URAL 1306
    Codeforces Round #288 (Div. 2)
  • 原文地址:https://www.cnblogs.com/linhan/p/4320862.html
Copyright © 2020-2023  润新知