我在上一篇随笔《基于MVC4+EasyUI的Web开发框架形成之旅--框架总体界面介绍》中大概介绍了基于MVC的Web开发框架的权限控制总体思路。其中的权限控制就是分为“用户登录身份验证”、“控制器方法权限控制”、“界面元素权限控制”三种控制方式,可以为Web开发框架本身提供了很好用户访问控制和权限控制,使得用户界面呈现菜单、Web界面的按钮和内容、Action的提交控制,均能在总体权限功能分配和控制之下。

本篇文章主要细化这三个方面的介绍,重点介绍“控制器方法权限控制”、“界面元素权限控制”这两种权限控制方式。

1、用户登录控制

登录界面如下所示。

其中登录的前台页面代码如下所示,其中可以在登录界面接收验证码(如果必要的话)。

//实现用户登录functionLoginUserInfo(){//获取单击用户登录按钮的事件$("#btnLogin").click(function(){//首先获取到要传递到控制器的参数,并且狗造成Json。UserName,UserPassword,CodevarpostData={UserName:$("#UserName").val(),Password:$("#Password").val(),Code:$("#Code").val()};//发送异步请求实现登录ajax$.ajax({url:'/Login/CheckUser',data:postData,cache:false,async:true,type:'post',success:function(data){if(data=="OK"){window.location.href="/Home/Index";}else{alert(data);window.location.href="/Login/Index";}}});});}

用户登录的后台控制器方法如下所示:

///<summary>///对用户登录的操作进行验证///</summary>///<paramname="username">用户账号</param>///<paramname="password">用户密码</param>///<paramname="code">验证码</param>///<returns></returns>publicActionResultCheckUser(stringusername,stringpassword,stringcode){stringresult="";boolcodeValidated=true;if(this.TempData["ValidateCode"]!=null){codeValidated=(this.TempData["ValidateCode"].ToString()==code);}if(string.IsNullOrEmpty(username)){result="用户名不能为空";}elseif(!codeValidated){result="验证码输入有误";}else{stringip=GetClientIp();stringmacAddr="";stringidentity=BLLFactory<WHC.Security.BLL.User>.Instance.VerifyUser(username,password,MyConstants.SystemType,ip,macAddr);if(!string.IsNullOrEmpty(identity)){UserInfoinfo=BLLFactory<WHC.Security.BLL.User>.Instance.GetUserByName(username);if(info!=null){result="OK";Session["UserInfo"]=info;Session["Identity"]=info.Name.Trim();#region取得用户的授权信息,并存储在Session中List<FunctionInfo>functionList=BLLFactory<Function>.Instance.GetFunctionsByUser(info.ID,MyConstants.SystemType);Dictionary<string,string>functionDict=newDictionary<string,string>();foreach(FunctionInfofunctionInfoinfunctionList){if(!string.IsNullOrEmpty(functionInfo.ControlID)&&!functionDict.ContainsKey(functionInfo.ControlID)){functionDict.Add(functionInfo.ControlID,functionInfo.ControlID);}}Session["Functions"]=functionDict;#endregion}}else{result="用户名输入错误或者您已经被禁用";}}returnContent(result);}

从上面的代码,我们可以看到,在用户登录成功后,后台把用户信息、用户权限列表信息放到了Session里面,方便进行后面的权限控制。

然后当前端页面获得成功响应并切换到Home的Index视图前,后台会调用Home的控制器,把一些用户信息放到了ViewBag对象里面,并构造用户的相关菜单项目,代码如下所示。

publicclassHomeController:BaseController{publicActionResultIndex(){if(CurrentUser!=null){ViewBag.FullName=CurrentUser.FullName;ViewBag.Name=CurrentUser.Name;StringBuildersb=newStringBuilder();List<MenuInfo>menuList=BLLFactory<Menu>.Instance.GetTopMenu(MyConstants.SystemType);inti=0;foreach(MenuInfomenuInfoinmenuList){sb.Append(GetMenuItemString(menuInfo,i));i++;}ViewBag.HeaderScript=sb.ToString();//一级菜单代码}returnView();}

2、控制器方法权限控制

我们知道,对页面的权限控制,可以分为前端控制和后台代码的控制,控制器方法的权限控制属于后台代码的控制。为了方便基类代码的权限控制,我们定义一个权限控制键的类,用来记录通用的增加、修改、删除、查看、列表、导出等传统控制元素,代码如下所示。

///<summary>///定义常用功能的控制ID,方便基类控制器对用户权限的控制///</summary>[DataContract][Serializable]publicclassAuthorizeKey{#region常规功能控制ID///<summary>///新增记录的功能控制ID///</summary>publicstringInsertKey{get;set;}///<summary>///更新记录的功能控制ID///</summary>publicstringUpdateKey{get;set;}///<summary>///删除记录的功能控制ID///</summary>publicstringDeleteKey{get;set;}///<summary>///查看列表的功能控制ID///</summary>publicstringListKey{get;set;}///<summary>///查看明细的功能控制ID///</summary>publicstringViewKey{get;set;}///<summary>///导出记录的功能控制ID///</summary>publicstringExportKey{get;set;}#endregion#region常规权限判断///<summary>///判断是否具有插入权限///</summary>publicboolCanInsert{get;set;}///<summary>///判断是否具有更新权限///</summary>publicboolCanUpdate{get;set;}///<summary>///判断是否具有删除权限///</summary>publicboolCanDelete{get;set;}///<summary>///判断是否具有列表权限///</summary>publicboolCanList{get;set;}///<summary>///判断是否具有查看权限///</summary>publicboolCanView{get;set;}///<summary>///判断是否具有导出权限///</summary>publicboolCanExport{get;set;}#endregion///<summary>///默认构造函数///</summary>publicAuthorizeKey(){}///<summary>///常用构造函数///</summary>publicAuthorizeKey(stringinsert,stringupdate,stringdelete,stringview=""){this.InsertKey=insert;this.UpdateKey=update;this.DeleteKey=delete;this.ViewKey=view;}}

有了这个实体类,我们就可以在控制器的基类BaseController里面实现一些控制逻辑了。首先我们在控制器每次执行方法前,都对权限进行一个转换,并把控制键存储到ViewBage里面,方便前端页面的控制,如下代码所示。

///<summary>///重新基类在Action执行之前的事情///</summary>///<paramname="filterContext">重写方法的参数</param>protectedoverridevoidOnActionExecuting(ActionExecutingContextfilterContext){base.OnActionExecuting(filterContext);//得到用户登录的信息CurrentUser=Session["UserInfo"]asUserInfo;if(CurrentUser==null){Response.Redirect("/Login/Index");//如果用户为空跳转到登录界面}//设置授权属性,然后赋值给ViewBag保存ConvertAuthorizedInfo();ViewBag.AuthorizeKey=AuthorizeKey;}

其中ConvertAuthorizedInfo()函数是验证登陆用户是否具有相应的权限的。

///<summary>///对AuthorizeKey对象里面的操作权限进行赋值,用于页面判断///</summary>protectedvirtualvoidConvertAuthorizedInfo(){//判断用户权限AuthorizeKey.CanInsert=HasFunction(AuthorizeKey.InsertKey);AuthorizeKey.CanUpdate=HasFunction(AuthorizeKey.UpdateKey);AuthorizeKey.CanDelete=HasFunction(AuthorizeKey.DeleteKey);AuthorizeKey.CanView=HasFunction(AuthorizeKey.ViewKey);AuthorizeKey.CanList=HasFunction(AuthorizeKey.ListKey);AuthorizeKey.CanExport=HasFunction(AuthorizeKey.ExportKey);}

其中BaseController的控制器基类还定义了判断用户是否有某些权限的逻辑,如果没有没有权限,就会抛出自定义异常(MyDenyAccessException),代码如下。

///<summary>///用于检查方法执行前的权限,如果未授权,返回MyDenyAccessException异常///</summary>///<paramname="functionId"></param>protectedvirtualvoidCheckAuthorized(stringfunctionId){if(!HasFunction(functionId)){stringerrorMessage="您未被授权使用该功能,请重新登录测试或联系管理员进行处理。";thrownewMyDenyAccessException(errorMessage);}}

有了上面的这些逻辑,我们在业务控制器基类(BusinessController<B, T>)里面,就可以实现对一些基本操作的API的权限控制了。

///<summary>///本控制器基类专门为访问数据业务对象而设的基类///</summary>///<typeparamname="B">业务对象类型</typeparam>///<typeparamname="T">实体类类型</typeparam>publicclassBusinessController<B,T>:BaseControllerwhereB:classwhereT:WHC.Framework.ControlUtil.BaseEntity,new(){///<summary>///插入指定对象到数据库中///</summary>///<paramname="info">指定的对象</param>///<returns>执行操作是否成功。</returns>publicvirtualActionResultInsert(Tinfo){//检查用户是否有权限,否则抛出MyDenyAccessException异常base.CheckAuthorized(AuthorizeKey.InsertKey);boolresult=false;if(info!=null){result=baseBLL.Insert(info);}returnContent(result);}///<summary>///更新对象属性到数据库中///</summary>///<paramname="info">指定的对象</param>///<paramname="id">主键ID的值</param>///<returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>publicvirtualActionResultUpdate(stringid,FormCollectionformValues){//检查用户是否有权限,否则抛出MyDenyAccessException异常base.CheckAuthorized(AuthorizeKey.UpdateKey);Tobj=baseBLL.FindByID(id);if(obj!=null){//遍历提交过来的数据(可能是实体类的部分属性更新)foreach(stringkeyinformValues.Keys){stringvalue=formValues[key];System.Reflection.PropertyInfopropertyInfo=obj.GetType().GetProperty(key);if(propertyInfo!=null){try{//obj对象有key的属性,把对应的属性值赋值给它(从字符串转换为合适的类型)//如果转换失败,会抛出InvalidCastException异常propertyInfo.SetValue(obj,Convert.ChangeType(value,propertyInfo.PropertyType),null);}catch{}}}}boolresult=baseBLL.Update(obj,id);returnContent(result);}

3、界面元素权限控制

我们从上面那个Web开发框架的主界面图可以看到,里面对于某个特定的业务,增加、修改、、查看、删除等操作都放在了EasyUI的DataGrid工具栏里面了,为了动态控制用户能访问的界面按钮,我们需要结合用户权限集合进行界面呈现,首先我们把ToolBar放到一个层里面进行定义,如下代码所示。

//实现对DataGird控件的绑定操作functionInitGrid(queryData){$('#grid').datagrid({//定位到Table标签,Table标签的ID是gridurl:'/Information/FindWithPager',//指向后台的Action来获取当前用户的信息的Json格式的数据title:'通知公告',iconCls:'icon-view',height:650,width:function(){returndocument.body.clientWidth*0.9},//自动宽度nowrap:true,autoRowHeight:true,striped:true,collapsible:true,pagination:true,pageSize:50,pageList:[50,100,200],rownumbers:true,//sortName:'ID',//根据某个字段给easyUI排序sortOrder:'asc',remoteSort:false,idField:'ID',queryParams:queryData,//异步查询的参数columns:[[{field:'ck',checkbox:true},//选择{title:'标题',field:'Title',width:350,sortable:true},{title:'编辑者',field:'Editor',width:80,sortable:true},{title:'编辑时间',field:'EditTime',width:150,sortable:true},{title:'附件',field:'Attachment_GUID',width:250,sortable:true}]],toolbar:"#gridtoolbar",

然后在HTML里面添加gridtoolbar的层定义,作为easyUI的表格控件的工具条。由于使用了HTML辅助类来实现界面控件代码控制生成,因此已经可以达到了界面权限的控制了。使用这种HTML层定义的工具条定义方式,比通过脚本定义的工具条效果少了一个分隔线,其他的都还是一致的。

<divid="gridtoolbar"><div>@if(@ViewBag.AuthorizeKey.CanInsert){@Html.ActionLink("添加",null,null,new{onclick="ShowAddDialog()",data_options="iconCls:'icon-add',plain:true",@class="easyui-linkbutton",href="javascript:void(0)"})}@if(@ViewBag.AuthorizeKey.CanUpdate){@Html.ActionLink("修改",null,null,new{onclick="ShowEditOrViewDialog()",data_options="iconCls:'icon-edit',plain:true",@class="easyui-linkbutton",href="javascript:void(0)"})}@if(@ViewBag.AuthorizeKey.CanDelete){@Html.ActionLink("删除",null,null,new{onclick="Delete()",data_options="iconCls:'icon-remove',plain:true",@class="easyui-linkbutton",href="javascript:void(0)"})}@if(@Html.HasFunction("Information/View")){@Html.ActionLink("查看",null,null,new{onclick="ShowEditOrViewDialog('view')",data_options="iconCls:'icon-table',plain:true",@class="easyui-linkbutton",href="javascript:void(0)"})}@Html.ActionLink("刷新",null,null,new{onclick="$('#grid').datagrid('reload');",data_options="iconCls:'icon-reload',plain:true",@class="easyui-linkbutton",href="javascript:void(0)"})</div></div>

上面使用了两种方式来判断用户的权限的,一种是使用这种ViewBag对象的树形进行判断,如下所示。

@if(@ViewBag.AuthorizeKey.CanDelete)

还有一种是使用HTML辅助类的扩展方法进行判断,这种方法适用于一些非常规的权限控制集合的判断,如下所示

@if(@Html.HasFunction("Information/View"))

其中HTML辅助类方法是通过扩展静态方法进行实现,代码如下所示。

publicstaticclassHtmlHelpers{publicstaticboolHasFunction(thisHtmlHelperhelper,stringfunctionId){returnPermission.HasFunction(functionId);}publicstaticboolIsAdmin(){returnPermission.IsAdmin();}}

上面的界面控制方法,是通过控制界面代码的生成与否进行权限控制的,前面我们讲了,通过后台代码的控制器方法也是可以实现控制,而且是抛出自定义的错误,那么我们在使用Ajax方法调用的时候,也可以对这个错误信息进行友好显示,提示用户权限不足,前端页面操作代码如下。

//绑定添加按钮的事件functionBindAddEvent(){$("#btnAddOK").click(function(){//判断表单的信息是否通过验证varvalidate=$("#ffAdd").form('validate');if(validate==false){returnfalse;}varpostData=$("#ffAdd").serializeArray();$.post("/Information/Insert",postData,function(data){if(data="true"){//添加成功1.关闭弹出层,2.刷新DataGird$.messager.alert("提示","添加成功");$("#DivAdd").dialog("close");$("#grid").datagrid("reload");$("#ffAdd").form("clear");//本页面的类型为【通知公告】,固定不变$("#Category").val("通知公告");}else{$.messager.alert("提示","添加失败,请您检查");}}).error(function(){$.messager.alert("提示","您未被授权使用该功能,请联系管理员进行处理。",'warning');});});}

以上就是我对Web开发框架中的权限控制几个方面的思路和代码,希望抛砖引玉,获得大家更好的反馈和支持。