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

来源:https://www.cnblogs.com/EmptyFS/archive/2018/09/13/9636355.html
-Advertisement-
Play Games

前面的菜單、部門、職位與管理員管理功能完成後,接下來要處理的是將它們關聯起來,根據職位管理中選定的許可權控制菜單顯示以及頁面數據的訪問和操作。 那麼要怎麼改造呢?我們可以通過用戶的操作步驟來一步步進行處理,具體思路如下: 1.用戶在管理端登錄時,通過用戶記錄所綁定的職位信息,來確定用戶所擁有的許可權。我 ...


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

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

  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    作者博客:http://www.cnblogs.com/EmptyFS/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • c/c++ 標準容器 forward_list, resize, 重新定位迭代器 1,forward_list特有的方法: + insert_after + emplace_after + erase_after 2,容器的插入刪除操作後的註意事項 + 必須保證每次改變容器的操作後都正確地重新定位迭 ...
  • 正則表達式是對字元串操作的一種邏輯公式,就是用事先定義好的一些特定字元、及這些特定字元的組合,組成一個“規則字元串”,這個“規則字元串”用來表達對字元串的一種過濾邏輯。 在python中正則表達式被封裝到了re模塊,通過引入re模塊來使用正則表達式 re模塊中有很多正則表達式處理函數,首先用find ...
  • c/c++ 標準順序容器 容器的訪問,刪除 操作 pop_front:vector,string不支持 pop_back:forward_list不支持 知識點 1,front, back, at 成員函數的使用,對應代碼里的test1 2,刪除最後一個元素pop_back, 刪除第一個元素pop_ ...
  • 明天老王要給我們講JVM的知識,提前發了一個小Demo給我們看,代碼如下: 運行上述代碼,結果毫無疑問,電腦瞬間開始狂躁起來,過了十幾秒,然後G了 基於JDK1.8運行的,估計老版本會崩的更快。。。 如果不計算記憶體,這個HashMap一共要插入4000*4000*4個對象,但是其實只有4個是不重覆的 ...
  • 據說Visual Studio Code(VS Code)的諸多好處,瞭解了一下果然很喜歡,我喜歡它的原因主要有3個,一是VS Code開源且跨平臺,二是因為其界面非常酷,三是可以我的大所屬代碼需求,除此之外當然還有強大的好奇心。使用VScode編寫第一個Python程式“one.py”,並將其打包... ...
  • 本文主要記錄如何使用aop切麵的方式來實現日誌記錄功能。 主要記錄的信息有: 操作人,方法名,參數,運行時間,操作類型(增刪改查),詳細描述,返回值。 ...
  • 最近工作上的事不忙了,開始忙著找房子了 我打算在天津再買一套房子,一個是投資一個是給我爸換個環境 但是二手的房子真的很少有非常完美的 現在天天守著以鏈家為主的APP看各種房子,晚上睡覺滿腦袋飛戶型圖 再有一個愁人的就是因為我在北京有房貸,所以在天津也算二套 二套要60%的首付,我手裡的錢不夠,還要湊 ...
  • c/c++ 標準順序容器 之 push_back,push_front,insert,emplace 操作 關鍵概念:向容器添加元素時,添加的是元素的拷貝,而不是對象本身。隨後對容器中元素的任何改變都不會影響到原始對象,反之亦然。 關鍵警告:因為vector,deque,string的記憶體存儲都是在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...