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