我的第一個python web開發框架(39)——後臺介面許可權訪問控制處理

AllEmpty發表於2018-09-13

  前面的選單、部門、職位與管理員管理功能完成後,接下來要處理的是將它們關聯起來,根據職位管理中選定的許可權控制選單顯示以及頁面資料的訪問和操作。

  那麼要怎麼改造呢?我們可以通過使用者的操作步驟來一步步進行處理,具體思路如下:

  1.使用者在管理端登入時,通過使用者記錄所繫結的職位資訊,來確定使用者所擁有的許可權。我們可以在登入介面中,將該管理員的職位id儲存到session中,以方便後續的呼叫。

  2.登入成功後,跳轉進入管理界口,在獲取選單列表時,需要對選單列表進行處理,只列出當前使用者有許可權的選單項。

  3.在點選選單進入相關資料頁面或在資料頁面進行增刪改查等操作時,需要進行許可權判斷,判斷是否有許可權進行檢視或操作。由於我們是前後端分離,所以許可權只需要在介面進行處理。

 

  首先我們來簡單改造一下登入介面login.py,只需要在將職位id儲存到session中就可以了

1     ##############################################################
2     ### 把使用者資訊儲存到session中 ###
3     ##############################################################
4     manager_id = manager_result.get(`id`, 0)
5     s[`id`] = manager_id
6     s[`login_name`] = username
7     s[`positions_id`] = manager_result.get(`positions_id`, ``)
8     s.save()

  找到上面內容,在裡面插入 s[`positions_id`] = manager_result.get(`positions_id`, “)

 

  接下來改造選單列表介面menu_info.py檔案的@get(`/api/main/menu_info/`)介面,我們需要做以下操作:

  1.首先從session中獲取當前使用者的職位id,然後根據職位id從職位表中讀取對應的許可權資料

  2.其次在選單的遍歷組裝過程中,新增判斷使用者的許可權,沒有許可權的選單項直接過濾掉

 1 @get(`/api/main/menu_info/`)
 2 def callback():
 3     """
 4     主頁面獲取選單列表資料
 5     """
 6     # 獲取當前使用者許可權
 7     session = web_helper.get_session()
 8     if session:
 9         _positions_logic = positions_logic.PositionsLogic()
10         page_power = _positions_logic.get_page_power(session.get(`positions_id`))
11     else:
12         page_power = ``
13     if not page_power:
14         return web_helper.return_msg(-404, `您的登入已超時,請重新登入`)
15 
16     _menu_info_logic = menu_info_logic.MenuInfoLogic()
17     # 讀取記錄
18     result = _menu_info_logic.get_list(`*`, `is_show and is_enabled`, orderby=`sort`)
19     if result:
20         # 定義最終輸出的html儲存變數
21         html = ``
22         for model in result.get(`rows`):
23             # 檢查是否有許可權
24             if `,` + str(model.get(`id`)) + `,` in page_power:
25                 # 提取出第一級選單
26                 if model.get(`parent_id`) == 0:
27                     # 新增一級選單
28                     temp = """
29                     <dl id="menu-%(id)s">
30                         <dt><i class="Hui-iconfont">%(icon)s</i> %(name)s<i class="Hui-iconfont menu_dropdown-arrow">&#xe6d5;</i></dt>
31                         <dd>
32                             <ul>
33                     """ % {`id`: model.get(`id`), `icon`: model.get(`icon`), `name`: model.get(`name`)}
34                     html = html + temp
35 
36                     # 從所有選單記錄中提取當前一級選單下的子選單
37                     for sub_model in result.get(`rows`):
38                         # 檢查是否有許可權
39                         if `,` + str(sub_model.get(`id`)) + `,` in page_power:
40                             # 如果父id等於當前一級選單id,則為當前選單的子選單
41                             if sub_model.get(`parent_id`) == model.get(`id`):
42                                 temp = """
43                                 <li><a data-href="%(page_url)s" data-title="%(name)s" href="javascript:void(0)">%(name)s</a></li>
44                             """ % {`page_url`: sub_model.get(`page_url`), `name`: sub_model.get(`name`)}
45                                 html = html + temp
46     
47                     # 閉合選單html
48                     temp = """
49                             </ul>
50                         </dd>
51                     </dl>
52                         """
53                     html = html + temp
54 
55         return web_helper.return_msg(0, `成功`, {`menu_html`: html})
56     else:
57         return web_helper.return_msg(-1, "查詢失敗")

  第9與第10行,就是從職位表中,讀取指定職位id的許可權page_power欄位值,第24行與第39行中,只需要判斷當前選單id是否存在page_power欄位值中,就可以判斷是否擁有該選單許可權了,因為在前面職位管理那裡,勾選了指定選單id後,就會將選單的id儲存到這個欄位中。

  由於可能多處需要讀取許可權page_power欄位值,這裡我們需要在職位邏輯類positions_logic.py中新增get_page_power()方法,來獲取其值出來使用。

1     def get_page_power(self, positions_id):
2         """獲取當前使用者許可權"""
3         page_power = self.get_value_for_cache(positions_id, `page_power`)
4         if page_power:
5             return `,` + page_power + `,`
6         else:
7             return `,`

  我們呼叫ORM的get_value_for_cache()方法,直接通過主鍵id來讀取我們想要的欄位值,並在許可權字串兩端新增逗號,因為我們在比較選單id是否存在於許可權字串時,不加上逗號可能會出錯,比如說許可權串有2,10,11,如果我們直接比較1是否存在於許可權串中,如果不轉為list,直接字串比較,返回結果就會為True,因為10和11都存在1,而各增加逗號以後比較就不一樣了,,2,10,11,與,1,比較肯定返回的是False,也就是說當前管理員沒有擁有1這個選單id的許可權。

 

  PS:完成選單列表功能的改造後,記得檢查選單列表頁面(main.html)和改造的介面是否在上一章節結束後,新增到選單管理項中,並在職位管理中將對應的許可權項打上勾,如果沒有的話,完成本文改造,登入後臺將會提示你沒有訪問許可權。

 

  最後要處理的是後臺管理各介面的許可權判斷,由於bottle勾子(@hook(`before_request`))直接獲取當前訪問的路由(介面),所獲取到的都有具體值(比如:@get(`/system/menu_info/<id:int>/`) 這個路由,在勾子中取到的是/system/menu_info/1/, 由於id值是不固定的,我們要處理起來會很麻),所以我們只能在每個介面中直接處理,也就是說我們需要在每個介面中,新增固定的許可權判斷方法呼叫。

  而許可權的處理需要對資料庫對資料庫進行讀取操作,所以我們可以在邏輯層資料夾中(logic)新增一個通用的邏輯層模組_common_logic.py,將許可權判斷方法在這個檔案中實現,方便呼叫。

  這裡的許可權判斷實現原理是:通過獲取web來路html頁面名稱、當前介面訪問方式(method)、當前訪問的介面路由名稱,將它們組成一個key值,從選單許可權初始化快取中讀取出對應的選單實體(後面會講到如何生成這個選單許可權快取),提取當前所訪問介面所對應的選單id值,然後通過從session中獲取當前使用者的職位id,獲取當前使用者所擁有的職位許可權,將選單id與職位許可權進行比較,判斷使用者是否擁有當前所訪問的介面許可權,從而達到對許可權的訪問控制。

  具體實現這個許可權判斷方法,有以下步驟:

  1.首先我們需要獲取web的來路地址HTTP_REFERER,由於我們在前面選單管理中,錄入的html頁面地址不包括域名和引數,所以來路地址需要去掉當前域名和?號後面的附加引數,只保留html頁面名稱。

  2.直接從從bottle的request中,讀取當前訪問介面的路由值(rule)

  3.從bottle的request中獲取當前訪問介面的方式(get/post/put/delete)

  4.將前面三步獲取的值組合成選單對應的唯一key,然後在選單許可權快取中讀取對應的選單實體

  5.如果選單記錄實體不存在,則表達當前介面未註冊或註冊時所提交的資訊錯誤,當前使用者沒有該介面的訪問許可權

  6.從session中獲取當前使用者登入時所儲存的職位id,然後通過該id讀取對應的職位許可權

  7.從選單實體中提取選單id,與職位許可權進行比較,判斷當前使用者是否擁有訪問該介面的許可權,如果有則跳過,沒有則拒絕訪問。

  具體程式碼如下:

 1 #!/usr/bin/env python
 2 # coding=utf-8
 3 
 4 from bottle import request
 5 from common import web_helper
 6 from logic import menu_info_logic, positions_logic
 7 
 8 def check_user_power():
 9     """檢查當前使用者是否有訪問當前介面的許可權"""
10     # 獲取當前頁面原始路由
11     rule = request.route.rule
12     # 獲取當前訪問介面方式(get/post/put/delete)
13     method = request.method.lower()
14 
15     # 獲取來路url
16     http_referer = request.environ.get(`HTTP_REFERER`)
17     if http_referer:
18         # 提取頁面url地址
19         index = http_referer.find(`?`)
20         if index == -1:
21             url = http_referer[http_referer.find(`/`, 8) + 1:]
22         else:
23             url = http_referer[http_referer.find(`/`, 8) + 1: index]
24     else:
25         url = ``
26 
27     # 組合當前介面訪問的快取key值
28     key = url + method + `(` + rule + `)`
29     # 從選單許可權快取中讀取對應的選單實體
30     menu_info = menu_info_logic.MenuInfoLogic()
31     model = menu_info.get_model_for_url(key)
32     if not model:
33         web_helper.return_raise(web_helper.return_msg(-1, "您沒有訪問許可權1" + key))
34 
35     # 讀取session
36     session = web_helper.get_session()
37     if session:
38         # 從session中獲取當前使用者登入時所儲存的職位id
39         positions = positions_logic.PositionsLogic()
40         page_power = positions.get_page_power(session.get(`positions_id`))
41         # 從選單實體中提取選單id,與職位許可權進行比較,判斷當前使用者是否擁有訪問該介面的許可權
42         if page_power.find(`,` + str(model.get(`id`, -1)) + `,`) == -1:
43             web_helper.return_raise(web_helper.return_msg(-1, "您沒有訪問許可權2"))
44     else:
45         web_helper.return_raise(web_helper.return_msg(-404, "您的登入已失效,請重新登入"))

 

  對於前面所講的選單許可權快取,下面詳細講解一下。

  由於選單跟介面都很多,我們在做許可權判斷時,就需要在訪問介面時,自動匹配找到該介面對應的選單項,然後才可以根據選單id和許可權字元進行比較,判斷是否擁有操作許可權,而自動匹配這裡如果直接通過資料庫查詢的話,操作會比較複雜,也會影響使用效能,所以我們可以通過將在選單管理中註冊的選單項進行分解,按一定的規則組合生成對應的快取key,儲存到nosql中,當訪問介面時,我們根據規則組合成對應的key直接在nosql中查詢就可以實現我們想要的功能了。當然第一次訪問或我們清除快取後,這些key值是不存在的,所以我們可以加個判斷,如果快取不存在時,重新載入生成對應的key就可以了。

  具體程式碼如下:

 1     def get_model_for_url(self, key):
 2         """通過當前頁面路由url,獲取選單對應的記錄"""
 3         # 使用md5生成對應的快取key值
 4         key_md5 = encrypt_helper.md5(key)
 5         # 從快取中提取選單記錄
 6         model = cache_helper.get(key_md5)
 7         # 記錄不存在時,執行記錄載入快取程式
 8         if not model:
 9             self._load_cache()
10             model = cache_helper.get(key_md5)
11         return model
12 
13     def _load_cache(self):
14         """全表記錄載入快取"""
15         # 生成快取載入狀態key,主要用於檢查是否已執行了選單表載入快取判斷
16         cache_key = self.__table_name + `_is_load`
17         # 將自定義的key儲存到全域性快取佇列中(關於全域性快取佇列請檢視前面ORM對應章節說明)
18         self.add_relevance_cache_in_list(cache_key)
19         # 獲取快取載入狀態,檢查記錄是否已載入快取,是的話則不再執行
20         if cache_helper.get(cache_key):
21             return
22         # 從資料庫中讀取全部記錄
23         result = self.get_list()
24         # 標記記錄已載入快取
25         cache_helper.set(cache_key, True)
26         # 如果選單表沒有記錄,則直接退出
27         if not result:
28             return
29         # 迴圈遍歷所有記錄,組合處理後,儲存到nosql快取中
30         for model in result.get(`rows`, {}):
31             # 提取選單頁面對應的介面(後臺選單管理中的介面值,同一個選單操作時,經常需要訪問多個介面,所以這個值有中儲存多們介面值)
32             interface_url = model.get(`interface_url`, ``)
33             if not interface_url:
34                 continue
35             # 獲取前端html頁面地址
36             page_url = model.get(`page_url`, ``)
37 
38             # 同一頁面介面可能有多個,所以需要進行分割
39             interface_url_arr = interface_url.replace(`
`, ``).replace(` `, ``).split(`,`)
40             # 逐個介面處理
41             for interface in interface_url_arr:
42                 # html+介面組合生成key
43                 url_md5 = encrypt_helper.md5(page_url + interface)
44                 # 儲存到全域性快取佇列中,方便選單記錄更改時,自動清除這些自定義快取
45                 self.add_relevance_cache_in_list(url_md5)
46                 # 儲存到nosql快取
47                 cache_helper.set(url_md5, model)

  這裡的許可權管理邏輯有點繞,需要認真思考與debug檢查,才能真正掌握。另外,也可以通過後臺選單管理中,故意修改選單項的某些值,來檢查這裡的程式碼處理與變化。

 

  完成以上程式碼以後,許可權的處理就完成了,接下來只需要在每個後臺管理介面中新增下面程式碼就可以做到介面的訪問許可權控制了。

@get(`/api/main/menu_info/`)
def callback():
    """
    主頁面獲取選單列表資料
    """
    # 檢查使用者許可權
    _common_logic.check_user_power()

  具體大家可以檢視文章後面提供的原始碼,看看後臺管理介面處理就清楚了。

 

 

 

 

  本文對應的原始碼下載 

 

版權宣告:本文原創發表於 部落格園,作者為 AllEmpty 本文歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則視為侵權。

python開發QQ群:669058475(本群已滿)、733466321(可以加2群)    作者部落格:http://www.cnblogs.com/EmptyFS/

相關文章