Django之路 如何開發(fā)通用且萬能的的權(quán)限框架組件
我是51CTO學(xué)院講師Alex(李杰),在51CTO學(xué)院“4.20 IT充電節(jié)”(4月19~20日)到來之際,和大家分享一下Django之路。正文來啦~~~
業(yè)務(wù)場景分析
假設(shè)我們在開發(fā)一個培訓(xùn)機(jī)構(gòu)的客戶關(guān)系管理系統(tǒng),系統(tǒng)分客戶管理、學(xué)員管理、教學(xué)管理3個大模塊,每個模塊大體功能如下:
客戶管理
銷售人員可以錄入客戶信息,對客戶進(jìn)行跟蹤,為客戶辦理報名手續(xù)
銷售人員可以修改自己錄入的客戶信息
客戶信息不能刪除
銷售主管可以查看銷售報表
學(xué)員管理
學(xué)員可以在線報名
學(xué)員可以查看自己的報名合同、學(xué)習(xí)有效期
學(xué)員可以在線提交作業(yè)、查看自己的成績
教學(xué)管理
管理員可以創(chuàng)建新課程、班級
講師可以創(chuàng)建上課紀(jì)錄
講師可以在線點名、批作業(yè)
從上面的需求中,我們至少提取出了5個角色,普通銷售、銷售主管、學(xué)員、講師、管理員,他們能做的事情都是不一樣的。
如何設(shè)計一套權(quán)限組件來實現(xiàn)對上面各種不同功能進(jìn)行有效的權(quán)限控制呢?我們肯定不能LOW到為每個動作都一堆代碼來控制權(quán)限,對吧?這些表面上看著各種不盡相同的功能,肯定是可以提取出一些相同的規(guī)律的,仔細(xì)分析,其實每個功能本質(zhì)上都是一個個的動作,如果能把動作再抽象出具體權(quán)限條目,然后把這些權(quán)限條目再跟用戶關(guān)聯(lián),每個用戶進(jìn)行這個動作,就檢查他沒有這個權(quán)限,不就實現(xiàn)權(quán)限的控制了么?由于這個系統(tǒng)是基于Web的B/S架構(gòu),我們可以把每個動作的構(gòu)成提取成以下的元素。
一個動作 = 一條權(quán)限 = 一個url + 一種請求方法(get/post/put...) + 若干個請求參數(shù)
那我們接下來需要做的,就是把一條條的權(quán)限條目定義出來,然后跟用戶關(guān)聯(lián)上就可以了!
開發(fā)中需要的權(quán)限定義
什么是權(quán)限?
權(quán)限就是對軟件系統(tǒng)中各種資源的訪問和操作的控制!
什么是資源?
在軟件系統(tǒng)中,數(shù)據(jù)庫、內(nèi)存、硬盤里數(shù)據(jù)都是資源,資源就是數(shù)據(jù)!
動作
資源本身是靜態(tài)的,必須通過合適的動作對其進(jìn)行訪問和操作,我們說要控制權(quán)限,其實本質(zhì)上是要對訪問軟件中各種數(shù)據(jù)資源的動作進(jìn)行控制 。
動作又可以分為2種:
資源操作動作:訪問和操作各種數(shù)據(jù)資源,比如訪問數(shù)據(jù)庫或文件里的數(shù)據(jù)。
業(yè)務(wù)邏輯事件動作:訪問和操作的目的不是數(shù)據(jù)源本身,而是借助數(shù)據(jù)源而產(chǎn)生的一系列業(yè)務(wù)邏輯,比如批量往遠(yuǎn)程主機(jī)上上傳一個文件,你需要從數(shù)據(jù)庫中訪問主機(jī)列表,但你真正要操作的是遠(yuǎn)程的主機(jī),這個遠(yuǎn)程的主機(jī),嚴(yán)格意義上來并不是你的數(shù)據(jù)資源,而是這個資源代表的實體。
權(quán)限授權(quán)
權(quán)限的使用者可以是具體的個人、亦可以是其他程序,這都沒關(guān)系,我們可以把權(quán)限的授權(quán)主體,統(tǒng)稱為用戶,無論這個用戶后面是具體的人,還是一個程序,對權(quán)限控制組件來講,都不影響。
權(quán)限必然是需要分組的,把一組權(quán)限分成一個組,授權(quán)給特定的一些用戶,分出來的這個組,就可以稱為角色。
權(quán)限應(yīng)該是可以疊加的!
權(quán)限組件的設(shè)計與代碼實現(xiàn)
我們把權(quán)限組件的實現(xiàn)分3步,權(quán)限條目的定義,權(quán)限條目與用戶的關(guān)聯(lián),權(quán)限組件與應(yīng)用的結(jié)合。
權(quán)限條目的定義
我們前面講過以下概念,現(xiàn)在需要做的,就是把我們系統(tǒng)中所有的需要控制的權(quán)限所對應(yīng)的動作提取成一條條 url+請求方法+參數(shù)的集合就可以。
一個動作 = 一條權(quán)限 = 一個url + 一種請求方法(get/post/put...) + 若干個請求參數(shù)
以下是提取出來的幾條權(quán)限
- perm_dic={
- 'crm_table_index':['table_index','GET',[],{},], #可以查看CRM APP里所有數(shù)據(jù)庫表
- 'crm_table_list':['table_list','GET',[],{}], #可以查看每張表里所有的數(shù)據(jù)
- 'crm_table_list_view':['table_change','GET',[],{}],#可以訪問表里每條數(shù)據(jù)的修改頁
- 'crm_table_list_change':['table_change','POST',[],{}], #可以對表里的每條數(shù)據(jù)進(jìn)行修改
- }
字典里的key是權(quán)限名,一會我們需要用過這些權(quán)名來跟用戶進(jìn)行關(guān)聯(lián)。
后面values列表里***個值如'table_index'是django中的url name,在這里必須相對的url name, 而不是絕對url路徑,因為考慮到django url正則匹配的問題,搞絕對路徑,不好控制。
values里第2個值是http請求方法。
values里第3個[]是要求這個請求中必須帶有某些參數(shù),但不限定對數(shù)的值是什么。
values里的第4個{}是要求這個請求中必須帶有某些參數(shù),并且限定所帶的參數(shù)必須等于特定的值。
有的同學(xué)看了上面的幾條權(quán)限定義后,提出疑問,說你這個權(quán)限的控制好像還是粗粒度的,比如我想控制用戶只能訪問客戶表里的一條或多條特定的用戶怎么辦?
哈,這個問題很好,但很容易解決呀,只需要在[] or {}里指定參數(shù)就可呀,比如要求http請求參數(shù)中必須包括指定的參數(shù),舉個例子,我的客戶表如下:
Customer表
里面的status字段是用來區(qū)分客戶是否報名的,我現(xiàn)在的需求是,只允許用戶訪問客戶來源為qq群且已報名的客戶,你怎么控制?
通過分析我們得出,這個動作的url為
- http://127.0.0.1:9000/kingadmin/crm/customer/?source=qq&status=signed
客戶來源參數(shù)是source,報名狀態(tài)為status,那我的權(quán)限條目就可以配置成
- 'crm_table_list':['table_list','GET',[],{'source':'qq', 'status':'signed'}]
權(quán)限條目與用戶的關(guān)聯(lián)
我們并沒有像其他權(quán)限系統(tǒng)一樣把權(quán)限定義的代碼寫到了數(shù)據(jù)里了,也許是因為我懶,不想花時間去設(shè)計存放權(quán)限的表結(jié)構(gòu),but anyway,基于現(xiàn)有的設(shè)計,我們?nèi)绾伟褭?quán)限條目與用戶關(guān)聯(lián)起來呢?
good news is 我們可以直接借用django自帶的權(quán)限系統(tǒng),大家都知道 django admin 自帶了一個簡單的權(quán)限組件,允許把用戶在使用admin過程中控制到表級別的增刪改查程度,但沒辦法對表里的某條數(shù)據(jù)控制權(quán)限,即要么允許訪問整張表,要么不允許訪問,實現(xiàn)不了只允許用戶訪問表中的特定數(shù)據(jù)的控制。
我們雖然沒辦法對通過自帶的django admin 權(quán)限系統(tǒng)實現(xiàn)想要的權(quán)限控制,但是可以借用它的權(quán)限與用戶的關(guān)聯(lián)邏輯!自帶的權(quán)限系統(tǒng)允許用戶添加自定義權(quán)限條目,方式如下:
- class Task(models.Model):
- ...
- class Meta:
- permissions = (
- ("view_task", "Can see available tasks"),
- ("change_task_status", "Can change the status of tasks"),
- ("close_task", "Can remove a task by setting its status as closed"),
- ) 這樣就添加了3條自定義權(quán)限的條目,然后 manage.py migrate 就可以在django自帶的用戶表里的permissions字段看到你剛添加的條目。
只要把剛添加的幾條權(quán)限移動的右邊的框里,那這個用戶就相當(dāng)于有相應(yīng)的權(quán)限了!以后,你在代碼里通過以下語句,就可以判定用戶是否有相應(yīng)的權(quán)限。
- user.has_perm('app.view_task')
看到這,有的同學(xué)還在蒙逼,這個自帶的權(quán)限跟我們剛才自己定義的權(quán)限條目有半毛錢關(guān)系么?聰明的同學(xué)已經(jīng)看出來了, 只要我們把剛才自己定義的perm_dic字典里的所有key在這個META類的permissions元組里。就相當(dāng)于把用戶和它可以操作的權(quán)限關(guān)聯(lián)起來了!這就省掉了我們必須自己寫權(quán)限與用戶關(guān)聯(lián)所需要的代碼了。
權(quán)限組件與應(yīng)用的結(jié)合
我們希望我們的權(quán)限組件是通用的,可插拔的,它一定要與具體的業(yè)務(wù)代碼分離,以后可以輕松把這個組件移植到其他的項目里去,因此這里我們采用裝飾器的模式,把權(quán)限的檢查、控制封裝在一個裝飾器函數(shù)里,想對哪個Views進(jìn)行權(quán)限控制,就只需要在這個views上加上裝飾器就可以了。
- @check_permission
- def table_change(request,app_name,table_name,obj_id):
- .....
那這個@check_permission裝飾器里干的事情,就是以下幾步:
1.拿到用戶請求的url+請求方法+參數(shù)到我們的的perm_dic里去一一匹配。
2.當(dāng)匹配到了對應(yīng)的權(quán)限條目后,就拿著這個條目所對應(yīng)的權(quán)限名,和當(dāng)前的用戶,調(diào)用request.user.has_perm(權(quán)限名)。
3.如果request.user.has_perm(權(quán)限名)返回為True,就認(rèn)為該用戶有權(quán)限,直接放行,否則,則返回403頁面!
權(quán)限檢查代碼
加入自定義權(quán)限
仔細(xì)按上面的步驟走下來,并玩了一會的同學(xué),可能會發(fā)現(xiàn)一個問題,這個組件對有些權(quán)限是控制不到的,就是涉及到一些業(yè)務(wù)邏輯的權(quán)限,沒辦法控制, 比如我只允許用戶訪問自己創(chuàng)建的客戶數(shù)據(jù),這個你怎么控制?
通過控制用戶的請求參數(shù)是沒辦法實現(xiàn)的,因為你獲取到的request.user是個動態(tài)的值,你必須通過代碼來判斷這條數(shù)據(jù)是否是由當(dāng)前請求用戶創(chuàng)建的。類似的業(yè)務(wù)邏輯還有很多?你怎么搞?
仔細(xì)思考了10分鐘,既然這里必須涉及到允許開發(fā)人員通過自定義一些業(yè)務(wù)邏輯代碼來判斷用戶是否有權(quán)限的話,那我在我的權(quán)限組件里再提供一個權(quán)限自定義函數(shù)不就可以了,開發(fā)者可以把自定的權(quán)限邏輯寫到函數(shù)里,我的權(quán)限組件自動調(diào)用這個函數(shù),只要返回為True就認(rèn)為有權(quán)限,就可以啦!
加入了自定義權(quán)限鉤子的代碼
權(quán)限配置條目
- 'crm_can_access_my_clients':['table_list','GET',[],
- {'perm_check':33,'arg2':'test'},
- custom_perm_logic.only_view_own_customers],
看***面我們加入的only_view_own_customers就是開發(fā)人員自已加的權(quán)限控制邏輯,里面想怎么寫就怎么寫。
- def only_view_own_customers(request,*args,**kwargs):
- print('perm test',request,args,kwargs)
- consultant_id = request.GET.get('consultant')
- if consultant_id:
- consultant_id = int(consultant_id)
- print("consultant=1",type(consultant_id))
- if consultant_id == request.user.id:
- print("\033[31;1mchecking [%s]'s own customers, pass..\033[0m"% request.user)
- return True
- else:
- print("\033[31;1muser can only view his's own customer...\033[0m")
- return False
這樣,萬通且通用的權(quán)限框架就開發(fā)完畢了,權(quán)限的控制粒度,可粗可細(xì)、可深可淺,包君滿意!以后要移植到其它django項目時,你唯一需要改的,就是配置好perm_dic里的權(quán)限條目!
51CTO學(xué)院 4.20 IT充電節(jié)
(19-20號兩天,100門視頻課程免單搶,更有視頻課程會員享6折,非會員享7折,套餐折上8折,微職位立減2000元鉅惠)
活動鏈接:http://edu.51cto.com/activity/lists/id-47.html?wenzhang
相關(guān)視頻教程:
Python運(yùn)維自動化開發(fā)視頻課程套餐