我的第一個python web開發框架(39)——后台接口權限訪問控制處理


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

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

  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('\n', '').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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM