(转)一步一步Asp.Net MVC系列_权限管理之权限控制
2012-12-06 11:09
246 查看
原文地址:http://www.cnblogs.com/mysweet/archive/2012/08/05/2623687.html
在权限管理中一个很重要的就是关于权限的拦截验证问题,特别是我们在webform中的验证,比纯winform要更复杂,winform可以通过验证把按钮隐藏或者禁用的方式,但是在web中我们不能仅仅通过隐藏按钮,不显示菜单/按钮之类的手段,因为客户端的代码都是透明的,如果我们不在服务端把好关,那么权限根本就无从谈起,我们必须彻底的进行验证,每一步动作都要进行验证,客户端的每一个ajax提交都要进行验证,如果任何一个ajax动作都做过验证了,那么至少可以保证基本的安全性了.
在纯webform中,我们通常怎么来进行权限控制呢?
一般情况下,设计基类然后,在基类写好验证方法,子类调用并验证
在权限管理中一个很重要的就是关于权限的拦截验证问题,特别是我们在webform中的验证,比纯winform要更复杂,winform可以通过验证把按钮隐藏或者禁用的方式,但是在web中我们不能仅仅通过隐藏按钮,不显示菜单/按钮之类的手段,因为客户端的代码都是透明的,如果我们不在服务端把好关,那么权限根本就无从谈起,我们必须彻底的进行验证,每一步动作都要进行验证,客户端的每一个ajax提交都要进行验证,如果任何一个ajax动作都做过验证了,那么至少可以保证基本的安全性了.
在纯webform中,我们通常怎么来进行权限控制呢?
一般情况下,设计基类然后,在基类写好验证方法,子类调用并验证
我们来看看启航动力的开源CMS怎么进行的验证:
usingSystem;
[code]usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.Web;
usingSystem.Web.UI.WebControls;
usingDTcms.Common;
namespaceDTcms.Web.UI
{
publicclassManagePage:System.Web.UI.Page
{
protectedinternalModel.siteconfigsiteConfig;
publicManagePage()
{
this.Load+=newEventHandler(ManagePage_Load);
siteConfig=newBLL.siteconfig().loadConfig(Utils.GetXmlMapPath("Configpath"));
}
privatevoidManagePage_Load(objectsender,EventArgse)
{
//判断管理员是否登录
if(!IsAdminLogin())
{
Response.Write("<script>parent.location.href='"+siteConfig.webpath+siteConfig.webmanagepath+"/login.aspx'</script>");
Response.End();
}
}
#region管理员============================================
///<summary>
///判断管理员是否已经登录(解决Session超时问题)
///</summary>
publicboolIsAdminLogin()
{
//如果Session为Null
if(Session[DTKeys.SESSION_ADMIN_INFO]!=null)
{
returntrue;
}
else
{
//检查Cookies
stringadminname=Utils.GetCookie("AdminName","DTcms");//解密用户名
stringadminpwd=Utils.GetCookie("AdminPwd","DTcms");
if(adminname!=""&&adminpwd!="")
{
BLL.managerbll=newBLL.manager();
Model.managermodel=bll.GetModel(adminname,adminpwd);
if(model!=null)
{
Session[DTKeys.SESSION_ADMIN_INFO]=model;
returntrue;
}
}
}
returnfalse;
}
///<summary>
///取得管理员信息
///</summary>
publicModel.managerGetAdminInfo()
{
if(IsAdminLogin())
{
Model.managermodel=Session[DTKeys.SESSION_ADMIN_INFO]asModel.manager;
if(model!=null)
{
returnmodel;
}
}
returnnull;
}
///<summary>
///检查管理员权限
///</summary>
///<paramname="channel_id">频道ID</param>
///<paramname="action_type">操作类型</param>
publicvoidChkAdminLevel(intchannel_id,stringaction_type)
{
Model.managermodel=GetAdminInfo();
BLL.manager_rolebll=newBLL.manager_role();
boolresult=bll.Exists(model.role_id,channel_id,action_type);
if(!result)
{
stringmsbox="parent.f_errorTab(\"错误提示\",\"您没有管理该页面的权限,请勿尝试非法进入!\")";
//ClientScript.RegisterClientScriptBlock(Page.GetType(),"JsPrint",msbox.ToString(),true);//修正BUG
Response.Write("<scripttype=\"text/javascript\">"+msbox+"</script>");
Response.End();
}
}
///<summary>
///检查管理员权限
///</summary>
///<paramname="channel_name">栏目名称</param>
///<paramname="action_type">操作类型</param>
publicvoidChkAdminLevel(stringchannel_name,stringaction_type)
{
Model.managermodel=GetAdminInfo();
BLL.manager_rolebll=newBLL.manager_role();
boolresult=bll.Exists(model.role_id,channel_name,action_type);
if(!result)
{
stringmsbox="parent.f_errorTab(\"错误提示\",\"您没有管理该页面的权限,请勿尝试非法进入!\")";
//ClientScript.RegisterClientScriptBlock(Page.GetType(),"JsPrint",msbox.ToString(),true);//修正BUG
Response.Write("<scripttype=\"text/javascript\">"+msbox+"</script>");
Response.End();
}
}
///<summary>
///检查管理员权限
///</summary>
///<paramname="channel_name">栏目名称</param>
///<paramname="action_type">操作类型</param>
///<returns>bool</returns>
publicboolIsAdminLevel(stringchannel_name,stringaction_type)
{
Model.managermodel=GetAdminInfo();
BLL.manager_rolebll=newBLL.manager_role();
returnbll.Exists(model.role_id,channel_name,action_type);
}
#endregion
#region枚举==============================================
///<summary>
///统一管理操作枚举
///</summary>
publicenumActionEnum
{
///<summary>
///所有
///</summary>
All,
///<summary>
///查看
///</summary>
View,
///<summary>
///添加
///</summary>
Add,
///<summary>
///修改
///</summary>
Edit,
///<summary>
///删除
///</summary>
Delete
}
///<summary>
///属性类型枚举
///</summary>
publicenumAttributeEnum
{
///<summary>
///输入框
///</summary>
Text,
///<summary>
///下拉框
///</summary>
Select,
///<summary>
///单选框
///</summary>
Radio,
///<summary>
///复选框
///</summary>
CheckBox
}
#endregion
#regionJS提示============================================
///<summary>
///添加编辑删除提示
///</summary>
///<paramname="msgtitle">提示文字</param>
///<paramname="url">返回地址</param>
///<paramname="msgcss">CSS样式</param>
protectedvoidJscriptMsg(stringmsgtitle,stringurl,stringmsgcss)
{
stringmsbox="parent.jsprint(\""+msgtitle+"\",\""+url+"\",\""+msgcss+"\")";
ClientScript.RegisterClientScriptBlock(Page.GetType(),"JsPrint",msbox,true);
}
///<summary>
///带回传函数的添加编辑删除提示
///</summary>
///<paramname="msgtitle">提示文字</param>
///<paramname="url">返回地址</param>
///<paramname="msgcss">CSS样式</param>
///<paramname="callback">JS回调函数</param>
protectedvoidJscriptMsg(stringmsgtitle,stringurl,stringmsgcss,stringcallback)
{
stringmsbox="parent.jsprint(\""+msgtitle+"\",\""+url+"\",\""+msgcss+"\","+callback+")";
ClientScript.RegisterClientScriptBlock(Page.GetType(),"JsPrint",msbox,true);
}
#endregion
}
}
[/code]
在子类校验的时候继承ManagePage的基类,然后就这样校验:
可以看到这种是通常的设计方案,基类定义,子类校验,不过这个启航动力CMS,他完全是在基类定义枚举,控制仅仅停留在增删改查,浏览,这些,不过CMS确实这一层就可以了,而且它的设计感觉不太好,因为到处都是验证代码,每一个增删改查方法都有这些验证的代码,如果是我我就会用委托的方式绑定方法,在Page_Load里面验证权限,委托绑定增删改查方法,这样,把验证的过程大大节省,这些甚至可以设计成公共的方法,来实现.
我们今天讲解的是MVC里面的权限验证,MVC天然的Controller,Action,让我们的权限控制更加容易,而且更简洁,更清晰.
首先,先来看看,MVC里面的一个小特色设计:
这个是一个普通的model,在mvc示例项目中,他采用Attribute方式来验证,我当时看到的时候感觉耳目一新,以前看<<CLRVIAC#>>Attribute的时候,当时还在想这些东西可以干什么??仅仅是元数据描述??后来才发现,这东西太强大了,控件的设计,反射,ORM,无处不在,Attributes也让代码更加优雅.
我们可以在mvc中定义各种Attribute来让我们的代码更优雅,而且让权限更简洁,比如我们经常会设计,
xxxx页面登录后才能访问,
xxxxx页面匿名用户也能访问,
xxxx页面必须经过权限验证才能访问,
xxxxx各种类型
我们可以定义各种Attribute来描述它,这种描述也让权限控制更加优雅.
比如:我们有几个登录页面,错误显示页面等等,这些页面,可以单独设计一个Attribute标记来标识.
我们可以定义各种各样的标记来描述权限控制,
可以看到我们在这里只需要标记匿名标记,然后在权限拦截中进行处理,如果存在Anonymous标记就默认允许.
这里在公共基类中设计验证标记,子类继承,这样,我们就可以达到子类全部都需要验证处理..
接下来,我们就看看,我们的权限拦截如何处理的.
我们的权限拦截是通过继承ActionFilterAttribute,自定义拦截器,这里参考传说中弦哥的思路.
简单地说,ActionFilter就是Action过滤器,任何一个请求都像筛子一样的过滤.所以,这个Filter就是筛子,我们需要的就是在这个筛子上做权限控制,这样我们的权限不就容易得多了么?
这时候,我想到了以前的一张asp.net生命周期,一个请求过来同样经过了HttpModule等一层层,在那里做权限拦截,可能效果更好,如果webform设计,可能要尝试一下看看能不能这么做.
///<summary>
[code]///权限拦截
///</summary>
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple=false)]
publicclassPermissionFilterAttribute:ActionFilterAttribute
{
///<summary>
///权限拦截
///</summary>
///<paramname="filterContext"></param>
publicoverridevoidOnActionExecuting(ActionExecutingContextfilterContext)
{
//权限拦截是否忽略
boolIsIgnored=false;
if(filterContext==null)
{
thrownewArgumentNullException("filterContext");
}
varpath=filterContext.HttpContext.Request.Path.ToLower();
//获取当前配置保存起来的允许页面
IList<string>allowPages=ConfigSettings.GetAllAllowPage();
foreach(stringpageinallowPages)
{
if(page.ToLower()==path)
{
IsIgnored=true;
break;
}
}
if(IsIgnored)
return;
//接下来进行权限拦截与验证
object[]attrs=filterContext.ActionDescriptor.GetCustomAttributes(typeof(ViewPageAttribute),true);
varisViewPage=attrs.Length==1;//当前Action请求是否为具体的功能页
if(this.AuthorizeCore(filterContext)==false)//根据验证判断进行处理
{
//注:如果未登录直接在URL输入功能权限地址提示不是很友好;如果登录后输入未维护的功能权限地址,那么也可以访问,这个可能会有安全问题
if(isViewPage==true)
{
//跳转到登录页面
filterContext.RequestContext.HttpContext.Response.Redirect("~/Admin/Manage/UserLogin");
}
else
{
//跳转到登录页面
filterContext.RequestContext.HttpContext.Response.Redirect("~/Admin/Manage/Error");
}
}
}
[/code]
这个Attribute直接借鉴的弦哥的模型,
这样我们可以对程序进行细化的处理过程.
针对不同的标记进行处理:
///<summary>
[code]///[Anonymous标记]验证是否匿名访问
///</summary>
///<paramname="filterContext"></param>
///<returns></returns>
publicboolCheckAnonymous(ActionExecutingContextfilterContext)
{
//验证是否是匿名访问的Action
object[]attrsAnonymous=filterContext.ActionDescriptor.GetCustomAttributes(typeof(AnonymousAttribute),true);
//是否是Anonymous
varAnonymous=attrsAnonymous.Length==1;
returnAnonymous;
}
///<summary>
///[LoginAllowView标记]验证是否登录就可以访问(如果已经登陆,那么不对于标识了LoginAllowView的方法就不需要验证了)
///</summary>
///<paramname="filterContext"></param>
///<returns></returns>
publicboolCheckLoginAllowView(ActionExecutingContextfilterContext)
{
//在这里允许一种情况,如果已经登陆,那么不对于标识了LoginAllowView的方法就不需要验证了
object[]attrs=filterContext.ActionDescriptor.GetCustomAttributes(typeof(LoginAllowViewAttribute),true);
//是否是LoginAllowView
varViewMethod=attrs.Length==1;
returnViewMethod;
}
///<summary>
/////权限判断业务逻辑
///</summary>
///<paramname="filterContext"></param>
///<paramname="isViewPage">是否是页面</param>
///<returns></returns>
protectedvirtualboolAuthorizeCore(ActionExecutingContextfilterContext)
{
if(filterContext.HttpContext==null)
{
thrownewArgumentNullException("httpContext");
}
//验证当前Action是否是匿名访问Action
if(CheckAnonymous(filterContext))
returntrue;
//未登录验证
if(SessionHelper.Get("UserID")==null)
{
returnfalse;
}
//验证当前Action是否是登录就可以访问的Action
if(CheckLoginAllowView(filterContext))
returntrue;
//下面开始用户权限验证
varuser=newUserService();
SysCurrentUserCurrentUser=newSysCurrentUser();
varcontrollerName=filterContext.RouteData.Values["controller"].ToString();
varactionName=filterContext.RouteData.Values["action"].ToString();
//如果是超级管理员,直接允许
if(CurrentUser.UserID==ConfigSettings.GetAdminUserID())
{
returntrue;
}
//如果拥有超级管理员的角色就默认全部允许
stringAdminUserRoleID=ConfigSettings.GetAdminUserRoleID().ToString();
//检查当前角色组有没有超级角色
if(Tools.CheckStringHasValue(CurrentUser.UserRoles,',',CurrentUser.UserRoles))
{
returntrue;
}
//Action权限验证
if(controllerName.ToLower()!="manage")//如果当前Action请求为具体的功能页并且不是Manage中Index页和Welcome页
{
//验证
if(!user.RoleHasOperatePermission(CurrentUser.UserRoles,controllerName,actionName))//如果验证该操作是否拥有权限
{
returnfalse;
}
}
//管理页面直接允许
returntrue;
}
[/code]
可以看到我们仅仅这一个PermissionAttribute配合其他的Attribute就实现了权限的拦截控制.
当然,我们仍然需要配置那些东西?
过滤页面.....
有一些页面我们可以直接配置允许访问的页面:
varpath=filterContext.HttpContext.Request.Path.ToLower();
[code]//获取当前配置保存起来的允许页面
IList<string>allowPages=ConfigSettings.GetAllAllowPage();
foreach(stringpageinallowPages)
{
if(page.ToLower()==path)
{
IsIgnored=true;
break;
}
}
if(IsIgnored)
return;
[/code]
我们在自定义配置文件中,可以定义允许访问的页面,比如:错误页之类的,可能有些人觉得,不需要,但是这个配置是在程序发布以后,仍然能够很轻松的进行配置,所以,预留一个配置页面还是蛮有必要的.
同时我们也需要配置超级管理员身份和角色....
有人觉得,这些需要么?