您的位置:首页 > 编程语言 > ASP

(转)一步一步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中,我们通常怎么来进行权限控制呢?


一般情况下,设计基类然后,在基类写好验证方法,子类调用并验证


我们来看看启航动力的开源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]





我们在自定义配置文件中,可以定义允许访问的页面,比如:错误页之类的,可能有些人觉得,不需要,但是这个配置是在程序发布以后,仍然能够很轻松的进行配置,所以,预留一个配置页面还是蛮有必要的.










同时我们也需要配置超级管理员身份和角色....


有人觉得,这些需要么?


我觉得是需要的,因为数据库读取出来的角色,根本没办法分辨超级管理员,只能数据库动态配置,我们就预定义这样的一个超级管理员角色的身份ID,让这个超级管理角色拥有任何的功能和任何的权限,甚至读取数据库的时候,都是读取的所有模块权限,菜单权限.

这样我们的配置工作就非常简单了,特别是数据库没有大部分配置初试信息(比如菜单信息,角色信息,)的时候,我们不需要手动往数据库添加数据,直接在程序中添加就可以了,也算是一个超级管理员身份.

接下来,我们的权限控制基本上就差不多了,现在我们可以安心的写页面了,权限神马东西,跟咱关系就不大了,这样,分工不是更爽,干别人的活才是最纠结的.......

接下来,我们可以继续设计一些公共类来简化我们日常的操作,对于公共类的设计,我觉得要拿出最大的热情与态度,要知道这些是能够真正节省我们时间的东西.


只有设计好它,我们以后才会更快更爽更轻松.



我们经常会遇到这样的代码:


///<summary>

[code]///保存资料业务
///</summary>

///<paramname="context"></param>

publicvoidSaveCompany(HttpContextcontext)

{

//用户json数据读取

companyInfo=newcompany();

StringCompanyStr=context.Request["Company"];

stringid=context.Request["id"];

stringpic=context.Request["pic"];

if(!Tools.IsValidInput(refpic,true)||!Tools.IsValidInput(refid,true))

{

return;

}

//图片保存

//System.IO.StreamWritersw=newSystem.IO.StreamWriter(context.Server.MapPath("tzt.txt"),true);

//sw.Write(CompanyStr);

//sw.Close();

//使用Newtonsoft.Json.dll组件解析json对象


JObjecto=JObject.Parse(CompanyStr);

Info.Username=(string)o.SelectToken("Username");

if(!newcompanyBLL().CheckExistUserName(Info.Username))

{

context.Response.Write(false);

return;

}

Info.Password=(string)o.SelectToken("Password");

Info.Name=(string)o.SelectToken("Name");

Info.Isrecommend=((string)o.SelectToken("Isrecommend"))=="true"?"1":"0";

Info.Fac=(string)o.SelectToken("Fac");

Info.Representative=(string)o.SelectToken("Representative");

Info.Isshow=((string)o.SelectToken("Isshow"))=="true"?"1":"0";

Info.State=((string)o.SelectToken("State"))=="true"?"1":"0";

Info.State1=((string)o.SelectToken("State1"))=="true"?"1":"0";


Info.Zipcode=(string)o.SelectToken("Zipcode");

Info.QQ=(string)o.SelectToken("QQ");

Info.Telephone=(string)o.SelectToken("Telephone");

Info.mobilephone=(string)o.SelectToken("mobilephone");

Info.Email=(string)o.SelectToken("Email");

Info.Address=(string)o.SelectToken("Address");

Info.Website=(string)o.SelectToken("Website");

Info.Award=(string)o.SelectToken("Award");

Info.Introduction=(string)o.SelectToken("Introduction");

Info.Picturepath=pic;


if(!string.IsNullOrEmpty(id))

Info.Id=Convert.ToInt32(id);


if(!Info.Id.HasValue)

{

Info.rank=0;

Info.hit=0;

//执行增加操作

newcompanyBLL().AddNew(Info);

SMTPsmtp=newSMTP(Info.Email);

stringwebpath=context.Request.Url.Scheme+"://"+context.Request.Url.Authority+System.Web.VirtualPathUtility.ToAbsolute("~/Default.aspx");

smtp.Activation(webpath,Info.Name);//发送激活邮件

}

else

{

newcompanyBLL().Update(Info);

}



}

[/code]
这是我以前一个电子商务网站项目写的,

我现在看,简直到处都是毛病.......

首先,关于form读取内容占了一大块.......

可以想象,到处都是赋值语句,看到都吐血,再加上一些验证非空,验证函数等等,更是罪加一等的恶劣.

当然我们需要一步一步的解决这些问题,首先是剥离关于表单读取的内容,封装到一个类中,然后在类中进行验证处理.

权限验证中的样例,









验证也没有做大块的处理,不过大家可以注意,可以看到想当的简洁,我们同时需要各种辅助类的设计,强制转化的处理,非空验证的处理,等等,我们直接设计成扩展方法就能达到上图中ObjToIntNULL()的形式,也不需要ConvertToInt32(xxxxx)的形式来转化了,不是更简洁么?


/*作者:tianzh

[code]*创建时间:2012/7/2215:38:20
*

*/
usingSystem;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Text;


namespaceTZHSWEET.Common

{

///<summary>

///强制转化辅助类(无异常抛出)

///</summary>

publicstaticclassConvertHelper

{

#region强制转化

///<summary>

///object转化为Bool类型

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticboolObjToBool(thisobjectobj)

{

boolflag;

if(obj==null)

{

returnfalse;

}

if(obj.Equals(DBNull.Value))

{

returnfalse;

}

return(bool.TryParse(obj.ToString(),outflag)&&flag);

}

///<summary>

///object强制转化为DateTime类型(吃掉异常)

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticDateTime?ObjToDateNull(thisobjectobj)

{

if(obj==null)

{

returnnull;

}

try

{

returnnewDateTime?(Convert.ToDateTime(obj));

}

catch(ArgumentNullExceptionex)

{

returnnull;

}

}

///<summary>

///int强制转化

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticintObjToInt(thisobjectobj)

{

if(obj!=null)

{

intnum;

if(obj.Equals(DBNull.Value))

{

return0;

}

if(int.TryParse(obj.ToString(),outnum))

{

returnnum;

}

}

return0;

}

///<summary>

///强制转化为long

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticlongObjToLong(thisobjectobj)

{

if(obj!=null)

{

longnum;

if(obj.Equals(DBNull.Value))

{

return0;

}

if(long.TryParse(obj.ToString(),outnum))

{

returnnum;

}

}

return0;

}

///<summary>

///强制转化可空int类型

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticint?ObjToIntNull(thisobjectobj)

{

if(obj==null)

{

returnnull;

}

if(obj.Equals(DBNull.Value))

{

returnnull;

}

returnnewint?(ObjToInt(obj));

}

///<summary>

///强制转化为string

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticstringObjToStr(thisobjectobj)

{

if(obj==null)

{

return"";

}

if(obj.Equals(DBNull.Value))

{

return"";

}

returnConvert.ToString(obj);

}

///<summary>

///Decimal转化

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticdecimalObjToDecimal(thisobjectobj)

{

if(obj==null)

{

return0M;

}

if(obj.Equals(DBNull.Value))

{

return0M;

}

try

{

returnConvert.ToDecimal(obj);

}

catch

{

return0M;

}

}

///<summary>

///Decimal可空类型转化

///</summary>

///<paramname="obj"></param>

///<returns></returns>

publicstaticdecimal?ObjToDecimalNull(thisobjectobj)

{

if(obj==null)

{

returnnull;

}

if(obj.Equals(DBNull.Value))

{

returnnull;

}

returnnewdecimal?(ObjToDecimal(obj));

}

#endregion


}

}

[/code]
一些好的常用的扩展方法同样是一个相当大的改进,对于代码,也更清晰,更简洁,我们同时可以设计一些非空验证等等.


#region判断对象是否为空

[code]///<summary>
///判断对象是否为空,为空返回true

///</summary>

///<typeparamname="T">要验证的对象的类型</typeparam>

///<paramname="data">要验证的对象</param>

publicstaticboolIsNullOrEmpty<T>(thisTdata)

{

//如果为null

if(data==null)

{

returntrue;

}


//如果为""

if(data.GetType()==typeof(String))

{

if(string.IsNullOrEmpty(data.ToString().Trim())||data.ToString()=="")

{

returntrue;

}

}


//如果为DBNull

if(data.GetType()==typeof(DBNull))

{

returntrue;

}


//不为空

returnfalse;

}


///<summary>

///判断对象是否为空,为空返回true

///</summary>

///<paramname="data">要验证的对象</param>

publicstaticboolIsNullOrEmpty(thisobjectdata)

{

//如果为null

if(data==null)

{

returntrue;

}


//如果为""

if(data.GetType()==typeof(String))

{

if(string.IsNullOrEmpty(data.ToString().Trim()))

{

returntrue;

}

}


//如果为DBNull

if(data.GetType()==typeof(DBNull))

{

returntrue;

}


//不为空

returnfalse;

}

#endregion

[/code]


这些东西其实真没有太多难的东西,只是简单的为了追求更简洁更方便而已,磨刀不误砍柴工就是这个道理,思考怎么偷懒才是程序员应该干的事情,不管多么忙都要停下来思考,想想怎么样才能偷懒!






界面上,不太会美工,直接照搬的LigerUI界面!

目前进度已经完成的差不多了,接下来要陆续总结学到的经验与技巧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: