在上篇解耦的故事中,我把权限设计分成了2类,一类是以系统功能为出发点管控系统的访问权限,并且将页面的权限功能模块与业务模块解耦。很多网友提出第2类的数据权限管控比较重要,所以今天就第2类权限的设计思想再详细描述一下。
以一个例子开始
类似在人事考勤查询中,不同人员能够查询的人事考勤信息应该是不一样的,如只可以查到自己部门的人员信息。
于是可能在代码中会这样写到:
Dept dept = GetUserDept(CurrentUserID); //获取当前登录用户的部门
DataTable dt = Query(dept); //按照这个部门查询人事考勤信息
dataGrid1.DataSource = dt;
dataGrid1.DataBind(); //绑定DataGrid,显示在网页上
很快user就会把需求变更到,管理员可以查所有部门的人员信息。有的主管管多个部门,可以查询到他所管理的部门的人员信息。显然,这个时候使用当前登录用户的部门来作为查询依据已经无法满足了,于是,权限部门的概念就呼之欲出。即给用户分配部门,他有几个部门,他就能查询到几个部门的信息。代码当然很简单,如下:
List<Dept> deptList = GetUserRightDepts(CurrentUserID); //获取当前登录用户的权限部门
dropdownlist1.DataSource = deptList; //绑定部门下拉框(user只能在权限部门里面选择,并查询)
dropdownlist1.DataTextField = "Dept_Name";
dropdownlist1.DataValueField = "Dept_No";
dropdownlist1.DataBind();
可能很多系统设计师到这里也就结束了,最多封装成一个控件,然后每次使用时直接拖到aspx页面,这样做也行,不过无论如何,还是觉得别扭,为什么,因为很明显,这个aspx页面与权限耦合在一起了,其实,想象一下,这个页面和权限关系有这么大吗?完全没有,去看那些查询UI框,查询方法的实现,都不是,但是为什么就会耦合在一起呢,观念没有转过来。
解耦
很明显,整个页面如果像上面那样写,那就和GetUserRightDepts方法耦合在一起呢,事实上,这个页面最不关心的就是到底是GetUserRightDepts方法,还是其它Get***Depts方法,只要最终都是返回一个List<Dept>结果,那就OK了。
权限与业务不要耦合,因为两者实际上关系不大,有无权限,并不会决定这个程序的执行过程(如上述这段代码)。
那怎么办,很简单,首先转换观念,写这支程序实现的就是查询用户所选部门的考勤数据,没必要我写的查询程序只能查询权限部门,你也可以通过QueryString把能查询的Dept带过来。
而在程序中只想通过一个约定的方法或到一个固定的地方去取就行,如:
(List<Dept>)HttpContext.Current.Items["DeptList"],然后再用这个List去绑定部门下拉列表框就完成了。
现在已不需要Hardcode调用GetUserRightList方法了,那这个方法放在哪里呢?在asp.net中很简单,放在一个地方统一处理这类的权限问题即可(如:HttpModule)。
实现一个HttpModule,
这个HttpModule会在程序Request之前的某个事件中就注入某支程序所需要的参数,如
上面这支查询程序的部门列表。
注入方式很简单,可能通过在某个地方配置,如
<Injection Url="query/考勤Query.aspx">
<Item Name="DeptList" Type="RightDept" />
</Injection>
然后在Module中统一读取这个配置,按照不同的Type去选择不同的注入方式,如
RightDept,就会调用GetUserRightDept方法,然后将结果放到HttpContext.Items["DeptList"]方法中。
一个示例:
public class SecurityModule : IHttpModule
{
public SecurityModule()
{
}
public void Init(HttpApplication application)
{
//捕获PreRequest事件
application.PreRequestHandlerExecute += new EventHandler(application_PreRequestHandlerExecute);
}
void application_PreRequestHandlerExecute(object sender, EventArgs e)
{
//注入数据权限
LoginHelper lo = new LoginHelper();
List<string> deptList = null;
if(lo.GetUserID()=="admin")
deptList = new List<string>(new string[] { "电脑部", "人事部", "财务部" });
else if(lo.GetUserID()=="zkw")
deptList = new List<string>(new string[] { "电脑部"});
HttpContext.Current.Items["DeptList"] = deptList;
}
public void Dispose()
{
}
}
aspx页面示例:
protected void Page_Load(object sender, EventArgs e)
{
if (HttpContext.Current.Items["DeptList"] != null)
{
List<string> depts = (List<string>)HttpContext.Current.Items["DeptList"];
Response.Write("你的权限部门是:<br>");
foreach (string dept in depts)
{
Response.Write(dept + "<br>");
}
}
}
于是数据权限就统一在程序之外,并且可以灵活配置了。
一直以权限设计作为解耦的故事主题,但是最重要的还是一个设计思想,不单是权限的问题,像我们经常在aspx中的Page_Load中写到
string kind = Request.QueryString["Kind"]
这样的代码,它也是耦合,将kind的获取方式强耦合到QueryString上。如果需要通过另一种方式传递kind到这个aspx页面时,一定又要修改代码。
可以在写程序时多问问自己,是否kind一定就会通过QueryString获得,如果不是,那要怎么改?
这些观念其实每个写程序久一点的人或多或少都有,只是每个人的解决方案不同。如果您了解IOC,再对比的想一下吧。
BTW:接触过的很多朋友,在一个新东西出来时,盲目跟风,会按照那个东东做例子,但是一旦到正式的Project时,又按照自己的方式来。
他不明白那个东东替它解决什么问题,因为他根本没有意识到还有那个问题的存在。
结果发现套用框架或使用这个平台时很复杂,还不如按照自己的方式去写,至少比较容易理解。
像WF,AOP,IOC等,知其然,还要知其所以然。
PS:后面这一段是在听到一个同事要在一个签核模块中用WF状态流来设计时有感而发。
另外,应博客园的出版要求,对《web应用程序安全的思考》系列进行了修改,并且改动较大。和这篇文章一起,相信可以给设计师对web系统的权限管控设计给出一个全面的方案