喜欢金庸的武侠,对他那几部小说也是乐此不疲
拿独孤求败来说,他的剑,从无名利剑,玄铁重剑,到木剑乃至最后的无剑,不知道破世间多少玄机
软件设计与用剑也颇有几分相似之处
下面就拿大家耳熟能详的权限设计为例,聊聊我对权限设计理解的三个层次吧
第一层:手中有剑,心中无剑
年少时,凭着手上的这把无名利剑,锋芒毕露,以为只要有这件利器在手,这天下便唯我独尊。
"XX,你那个薪资查询系统,怎么谁都可以进去查啊?"
"啊?..."
"只有总经理和他的助理可以查!"
"哦..."
if(Session["UserID"]==null)
throw new PermissionException("必须登录");
string userID = Session["UserID"].ToString();
if(!(userID=="88.00" || userID=="888.00")) //总经理的用户ID 88,总经理的助理用户ID:888
throw new PermissionException("您没有访问此页的权限");
权限这玩意刚开始其实非常简单,谁又能不会?
然后听说大部分页面都要管控,很快:
public class PermissionHelper
{
public static bool CheckPermission(string pageID);
}
用一个方法统一封装那部分代码。
再建一个table,记入哪些页面可以给哪些user访问
pageID userID
------- --------
薪资查询 88.00
薪资查询 888.00
薪资输入 99.00 --人事主管
----------------------------------
这样只要在PageLoad中CheckPermission一下就行
接着,又可以由要进行权限管控的页面统一继承一个类,然后只在这基类中call一次CheckPermission就行
public class PermissionPage:System.Web.UI.Page{
override PageLoad(){
PermissionHelper.CheckPermission(pageID);
}
protected abstract string pageID{get;}
}
权限页面继承此类,override pageID,就能完成当前页的权限管控
当然在asp.net中还有其它的一些方法,如利用asp.net的HttpModule,可以在访问页面之前就完成权限管控,这种方法耦合性更低,灵活性更高,不过可能就要多费一些功夫记录一下Request的Url和PageID对应才可
url pageID
------------------
SalaryQuery.aspx 薪资查询
SalaryEdit.aspx 薪资输入
------------------------------------
昨夜西风凋碧树。独上高楼,望尽天涯路
执着,加上勤奋,终于利剑出鞘,手到擒来
当然,世间万物,各有不同
有通过控制WebForm的各种控件的状态(如Button的Enable,Visible)来控制权限的
也有在MVC中,通过在Controller中设定Attribute完成
还有控制菜单
控制数据库中table的栏位以及增/删/改/查的
可谓八仙过海,各显神通,最终都在自己的势力范围下建功立业
剑,是好剑,也能杀人
不过却不明白这把剑好在哪?
于是就今天用的还是青龙剑,明天却又发现了偃月刀也很犀利,可能也就转成刀客了
停留在此种境界,即是心中无剑,最终也不过是个剑客罢了
要做剑圣,还必须真正地研究各种不同的剑,领悟剑的本质,锻造出适合自己风格的绝世好剑--玄铁重剑
这也是第二层境界:手中有剑,心中也有剑
心中有剑后,手中的剑才能直指目标,更犀利,更直接,杀人于无形,真正雄霸天下。
要到这一层,除了要有“为伊消得人憔悴,衣带渐宽终不悔”的勇气和毅力,
还要悟
权限是什么
无非就是要让没有权限的人不能访问没有授权的物
那谁有权,谁没权
这就是权限的表示问题
不管是01也好,Table记录也罢,文件配置也行
最终都要告诉
人--物的对应,只能在有了这种对应之后,我们的权限管控才是物有所托。
当然,很多时候这两者不是直接对应的,
为了某种原因,如更易管理,更易写程式,更易提供界面等等,往往会设计一些中间过程
如人会加到群组里面,由群组来对应物
物也可以组成群组
但是不管中间多少层,最终还是人与物的对应。
最终要提供类似
HasPermission(UserID,ResourceID)(有无权限),
PermissionResource(UserID)(用户权限资源),
PermissionUser(ResourceID)(拥有资源权限的用户)等方法。
有了权限的表示,并不能阻止访问没有授权的资源,它只是一个死物,需要有地方去用它。
这就是权限的管控,它包括
1.选择管控的地点,即在哪里下手,在哪里进行管控
有通过PageLoad,有通过HttpModule,还有通过AOP在方法调用前横切管控等等
如果是资源权限,则可能在资源下拉框中按权限筛选,在提交时根据参数判断资源权限等等。
最终只要你记得,这是管控的地点
如何在系统中更简单,更方便地管控,取决于系统架构,其灵活性也让管控地点的选择是否顺利与简单
2.管控过程,分为四步
a.识别出人
cookie,Session,或者是c/s中的UserID变量,也可以是webservice的soap头经过登录后的用户ID,还可以是IP或者手机号码,最终都转为权限表示中的UserID
b.识别出物
Url,参数中的变量都行,最终要转成权限表示中的ObjectID
c.调用HasPermission(人,物)判断权限
这是由权限表示决定的方法
d.实施管控策略
对于无权限者,或转向无权登录页,或抛出异常,或Button.Visible = false最终实现权限管控
经过这个本质的识别,接下来就可以来锻造真正的玄铁重剑了
针对权限表示,设计一个比较通用的方案
也就是基本上能够通用的最简单抽象
---------------------------------
不吐不快,插播一下
在系统架构过程中,如何快速实现可供用户测试和使用的系统才是最重要的,至于一些底层服务框架,如数据访问,AOP横切,IOC,日志等,并不是越完美越好,而是要简单,要在自己全部理解的基础上使用,有了这个基础,就算碰到不能实现某些需求时,也可以很容易地通过自己修改去实现
就说日志吧,对于一些新手来说,完全没必要去用log4net,直接几行代码使用txt就完成日志记录了,要不然在程式出错时,除了要找寻程式为啥出错,还可能要去找为什么没有日志出来
还有如AOP,先想想整体架构,为什么需要AOP,AOP何时使用,如果使用spring.net,caslte,就会出现只要一个轮子,结果将整个汽车仓库都搬来了,不是不好,而是用不到,用不到再好的东西也等于零,关键是对于不熟悉的人,一旦出现问题,那维护起来也是相当麻烦
当然以上只是适用于不太熟悉的人,如果你对Castle或者log4net熟悉得就像老婆身上有哪几颗痣,在什么地方都知道,则另当别论
好了,拉回来,不跑远了
--------------------------------------
1:Object,Group的对应
2:User,Group的对应
基本上这个简单的抽象,就可以完成绝大部分权限表示问题(如果真有幸,碰到了剩下5%不能完成的权限表示时,那就再去抽象一次,最终提供权限表示上的权限方法就是)
对于权限方法,也只需写一遍,就可以用在任何权限类别上了
复制几个权限配置的片段吧
Ajax的权限
--------------------------
Role,Object
--------------------------
Admin,*
?,PCIWeb.ProgramsHelper.Programs
?,ClientTool.*
?,WebFileBrowser_CIFiles.*
?,WebFileBrowser_PQM.*
?,WebFileBrowser_ISO.*
?,MRB.BookingService.RoomBooking
?,MRB.BookingService.RoomBookingAfterNow
*,MRB.BookingService.Cancel
*,MRB.BookingService.Booking
--PQM系統
PQM_Exced,PQM.ExcedService.*
--7S,Lean,Kaizen
PQM_CI,PQM.CI
这是直接访问DB服务的权限
--------------------------
Role,Object
--------------------------
Admin,*
?,MRB/Room_Query
?,MRB/Borrow_Query2
--任何人都可以查詢每日超標回饋表
?,PQM/Exced_Query
?,PQM/Excem_Query
?,PQM/Excedd_Query
?,PQM/Excem_Query2
这是User与Role的对应
------------------------------
User,Role
------------------------------
850.00,Admin
850.00,PQM_Exced
206.00,PQM_Exced
54.00,Admin
54.00,PQM_Exced
其次,对于管控地点,不同的人的系统架构不同,可能实现也不一样。
笔者的系统架构采用RIA架构 + SOA服务,
因此在服务层使用aop的横切方式,就完成了服务权限的管控
而数据权限,也是抽象了几个UI控件,通过PermissionResource方法过滤下拉列表框和弹出Grid,而在服务调用时,在具体的程式中直接调用HasPermission方法进行管控
融入剑本质的玄铁重剑出炉,所向披靡
这把剑与当初那把无名利剑表面上似乎没有什么不同,但他的锻造过程更标准,剑招也需要内力的支撑。
举个例子来说,
通过URL管控,可能只能用于Web
而通过管控Button的Visible,虽然能管控到WinForm,但是WebService的权限怎么办
而明确了权限的本质后,针对各种不同的部分统一或单独实现,做到有的放矢,不再盲目跟从。
特别是如果自己的系统针对某一范围,更能够实现一套符合自己的通用的权限架构。
有了这一层的实现后,第三层的境界其实也是水到渠成,这便是
手中无剑,心中有剑
剑终究有形,不管它多轻,又或多通用,到处带着,始终会很累
只要心中有剑,就是手上拿着的是木剑也能杀人。
笔者所服务的部门留有很多旧系统,里面各种权限实现五花八门,加上移植兄弟公司的系统,里面也有单独的权限实现,对这一部分,原来还想全部改写,后来终于放下。
只要实现目的,管他什么手段
只是与刚开始的漫无目的不同,这时候已能十分清楚这些额外的权限是如何实现,有无漏洞等等
驾驭起来,也是顺风顺水
其实mvc的controller管控也非常不错,没有谁在这里做了漏网之鱼
其实webform的control管控也运行稳定,至今顺利在跑
存在即合理
为什么要去改变他们,为什么要花这么大心思去补救,去重构
生命中要做的事情太多太多 ,比权限管控更重要的事情也多得多,如何让用户查起来更顺手,如何让资料显示得更舒服,如何让画面更流畅。。。
资源管控住了就行,管它用什么方法,合适的就是最好的。
如一个webservice
系统总共就这么一个webservice
只允许本机访问(同一台主机,不同系统的数据交换)
那就在方法中直接写
if(HttpContext.Current.Request.UserHostAddress!="127.0.0.1")
throw new PermissionException("抱歉,你没有访问这个web服务的权限");
这其中不也包括了
权限表示:hardcode在代码中,就是用户为127.0.0.1才有权限
管理地点:执行方法的主体代码前
识别人: HttpContext.Current.Request.UserHostAddress
识别物:就是当前管控的方法调用(在这里是隐式的)
调用权限表示方法验证:HttpContext.Current.Request.UserHostAddress!="127.0.0.1"
无权时的动作:throw new PermissionException("抱歉,你没有访问这个web服务的权限")
同样它就是一个标准的权限管控方案
信手拈来即是剑
按照权限思路,查看是否有漏洞,有则补之,无则放行
无剑一身轻
众里寻她千百度,蓦然回首,那人却在灯火阑珊处