最近項(xiàng)目的項(xiàng)目很奇怪,一個(gè)大項(xiàng)目(系統(tǒng))里包含了很多小的子系統(tǒng),而這些子系統(tǒng)中都有權(quán)限控制的部分,這件事情挺讓我頭痛的,記得一年前在沈陽(yáng),我曾經(jīng)有一段時(shí)間也因因這個(gè)問(wèn)題而疲于奔命,為什么說(shuō)疲于奔命呢?由于當(dāng)時(shí)項(xiàng)目進(jìn)度不允許,導(dǎo)致最終系統(tǒng)權(quán)限模塊草草了事,每個(gè)模塊都是由讀權(quán)限字符串來(lái)控制用戶(hù)ACL,當(dāng)用戶(hù)無(wú)法訪(fǎng)問(wèn)時(shí),提示權(quán)限不夠。這么做對(duì)用戶(hù)是很不負(fù)責(zé)任的,既然讓用戶(hù)看到了操作的方式和界面,為什么又告訴用戶(hù)沒(méi)有權(quán)限呢?我開(kāi)始懷疑我們是否應(yīng)該在底層就封殺用戶(hù)的訪(fǎng)問(wèn)權(quán)限。
現(xiàn)在項(xiàng)目開(kāi)展起來(lái)了,雖然目前我已經(jīng)有了對(duì)權(quán)限控制的一套方案,并且實(shí)施成了我的可重用框架代碼,雖然目前的權(quán)限也是基于眾星捧月的AOP思想,但我至今對(duì)權(quán)限設(shè)計(jì)仍有兩個(gè)疑惑:
疑惑一:很多同行提出方案,想要在底層就截取用戶(hù)權(quán)限,控制用戶(hù)對(duì)方法或者類(lèi)的訪(fǎng)問(wèn)。這樣做的好處在于可以將系統(tǒng)功能與業(yè)務(wù)邏輯松散耦合,并且實(shí)現(xiàn)簡(jiǎn)單,結(jié)構(gòu)清晰,三兩個(gè)advisor、filter,或者acegi就能搞定,但在web程序中體現(xiàn)出了他的劣勢(shì),當(dāng)我們將用戶(hù)的訪(fǎng)問(wèn)拒絕在業(yè)務(wù)邏輯之外的時(shí)候,我們此時(shí)是否應(yīng)該拋出異常提示用戶(hù)?一旦提示用戶(hù)沒(méi)有相應(yīng)的權(quán)限,我認(rèn)為對(duì)于用戶(hù)來(lái)說(shuō),這就不是一個(gè)perfect practice。由此得出,我們根本就不應(yīng)該讓用戶(hù)做此次操作,而控制用戶(hù)操作的源頭就是界面,也就是說(shuō),在界面上我們就應(yīng)該對(duì)用戶(hù)的權(quán)限元素(如添加按鈕、功能菜單等)進(jìn)行控制。此時(shí),一對(duì)矛盾出現(xiàn)了,要控制界面上形形色色的元素只有兩種辦法,一,將權(quán)限與你的界面結(jié)合起來(lái)設(shè)計(jì),這將違背AOP的思想,也使得系統(tǒng)控制模塊的重用性大大下降,二,我們借鑒primeton的想法,將權(quán)限控制的理念抽取出來(lái),單獨(dú)做成一套權(quán)限系統(tǒng),解決你所有的需要權(quán)限控制的系統(tǒng)需求,這樣也有令人頭痛的問(wèn)題,你的所有想用它來(lái)控制權(quán)限的系統(tǒng),必須界面上統(tǒng)一風(fēng)格。或許這樣的方式對(duì)商業(yè)web系統(tǒng)是合適的,畢竟需要你大刀闊斧個(gè)性化的地方不多,但我們卻很難保證在未來(lái)幾年內(nèi)商業(yè)web系統(tǒng)的風(fēng)格不改變。再者,開(kāi)發(fā)這么一個(gè)系統(tǒng)也不是一蹴而就的事,在這個(gè)問(wèn)題上一直讓我困惑不已。
疑惑二:大多應(yīng)用的權(quán)限判定是基于權(quán)限字符串的,但存儲(chǔ)在數(shù)據(jù)庫(kù)中的權(quán)限字符串能夠判定的權(quán)限并不多,在我們這次項(xiàng)目中,我引用了基于二進(jìn)制的8421權(quán)限判定法則,我深深的感覺(jué)到權(quán)限字符串的弱勢(shì),這使我想起了中國(guó)古老一套數(shù)學(xué)理論-“盈不足術(shù)”,超遞增序列的魅力在我眼前滑過(guò),
首先我來(lái)解釋一下盈余不足理論:有十只盒子,第一個(gè)盒子里放一個(gè)盤(pán)子,第二個(gè)盒子里放兩只,第三個(gè)盒子里放四只,第四個(gè)盒子里放八只……第九個(gè)盒子里放256只,第十個(gè)盒子放512只,即第N只箱子里放2^(N-1)只盤(pán)子,一共1023只。那么命題如下:在1023這個(gè)數(shù)字之內(nèi),任何一個(gè)數(shù)目都可以由這十只盒子里的幾只組合相加而成。那么1、2、4、8、16、32、64、128、256、512這個(gè)序列為什么有這么個(gè)魔力?這個(gè)數(shù)列的特點(diǎn):1、每項(xiàng)是后一項(xiàng)的二倍,2、每項(xiàng)都比前面所有項(xiàng)的和大,而且大1。這個(gè)1就是關(guān)鍵,就因?yàn)檫@個(gè)1,它才可以按1遞增,拼出總和之內(nèi)任意一個(gè)整數(shù)。這個(gè)序列叫做超遞增序列,它是解決背包問(wèn)題的基礎(chǔ)。3、拼出總和之內(nèi)任意一個(gè)整數(shù)可以由這個(gè)序列中的一些數(shù)構(gòu)成,且構(gòu)成方法唯一,據(jù)說(shuō)是密碼學(xué)中的NP定理。譬如說(shuō)這個(gè)數(shù)列總合中20這個(gè)數(shù),只能由16+4一種方法構(gòu)成,由此延伸出來(lái),如果綜合中這個(gè)數(shù)據(jù)代表一個(gè)權(quán)值,我們可以解出它的所有構(gòu)成參數(shù)(操作),如20這個(gè)數(shù)據(jù),我們可以挨個(gè)和序列中每一項(xiàng)按位與,得出來(lái)如果不等于0,就說(shuō)明他是由這個(gè)數(shù)構(gòu)成的。
保存權(quán)值到int還是varchar對(duì)于我們來(lái)說(shuō)是個(gè)問(wèn)題,當(dāng)然,保存字符串的好處是運(yùn)算壓力小。我們可能聽(tīng)過(guò)一個(gè)故事,就是把這個(gè)超遞增序列延伸到第64項(xiàng),就是那個(gè)術(shù)士和皇帝在國(guó)際象棋棋盤(pán)上要米粒的傳說(shuō)。64項(xiàng)的和是一個(gè)天文數(shù)字!但計(jì)算機(jī)本身就是一個(gè)只認(rèn)識(shí)二進(jìn)制的機(jī)器!很多程序員整天只關(guān)心架構(gòu),甚至不知道或者不關(guān)心位操作是什么玩意,當(dāng)然我們有朋友擔(dān)心數(shù)據(jù)庫(kù)的int不夠長(zhǎng),那么既然可以保存一個(gè)只有0、1組成的varchar字符串,為什么不能保存一個(gè)十六進(jìn)制的字符串,有人規(guī)定varchar只能保存01嗎?十六進(jìn)制串的長(zhǎng)度正好是二進(jìn)制的四分之一。
由此我們可以無(wú)限制的擴(kuò)展權(quán)值操作。
在最近的項(xiàng)目里,我對(duì)權(quán)限的控制分成兩個(gè)部分,第一就是用戶(hù)體驗(yàn)上,我設(shè)置了一個(gè)權(quán)限標(biāo)簽,從數(shù)據(jù)庫(kù)中抽取權(quán)限信息,然后做到標(biāo)簽里,也湊或算成是界面AOP了,第二就是底層的攔截,用了Spring 的AOP,為的是防止權(quán)限沖突,雙管齊下。暫時(shí)解決權(quán)限所需,另外在算法上我用了16進(jìn)制的權(quán)限判別代碼,雖然配置較麻煩,寫(xiě)完代碼還要寫(xiě)文檔說(shuō)明,不過(guò)也解決了權(quán)限繁雜又多的問(wèn)題,暫時(shí)就這樣了,嘿嘿,以后有空再研究。