個人第一個python web開發框架(39)——後臺接口權限訪問控制處理

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

  那麼要怎麼改造呢?咱們能夠經過用戶的操做步驟來一步步進行處理,具體思路以下:html

  1.用戶在管理端登陸時,經過用戶記錄所綁定的職位信息,來肯定用戶所擁有的權限。咱們能夠在登陸接口中,將該管理員的職位id存儲到session中,以方便後續的調用。前端

  2.登陸成功後,跳轉進入管理界口,在獲取菜單列表時,須要對菜單列表進行處理,只列出當前用戶有權限的菜單項。java

  3.在點擊菜單進入相關數據頁面或在數據頁面進行增刪改查等操做時,須要進行權限判斷,判斷是否有權限進行查看或操做。因爲咱們是先後端分離,因此權限只須要在接口進行處理。python

 

  首先咱們來簡單改造一下登陸接口login.py,只須要在將職位id存儲到session中就能夠了web

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', '')sql

 

  接下來改造菜單列表接口menu_info.py文件的@get('/api/main/menu_info/')接口,咱們須要作如下操做:數據庫

  1.首先從session中獲取當前用戶的職位id,而後根據職位id從職位表中讀取對應的權限數據後端

  2.其次在菜單的遍歷組裝過程當中,添加判斷用戶的權限,沒有權限的菜單項直接過濾掉api

 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/

相關文章
相關標籤/搜索