1. 程式人生 > >Flask許可權管理

Flask許可權管理

許可權管理是一個很常見的功能模組,本文基於RBAC模型針對於多使用者,多角色,多許可權的場景,介紹一種Flask許可權管理方案。 **Flask系列文章**: 1. [Flask開發初探](https://www.cnblogs.com/ybjourney/p/10125532.html) 2. [WSGI到底是什麼](https://www.cnblogs.com/ybjourney/p/12004002.html) 3. [Flask原始碼分析一:服務啟動]( ) 4. [Flask路由內部實現原理](https://www.cnblogs.com/ybjourney/p/11789983.html) 5. [Flask容器化部署原理與實現](https://www.cnblogs.com/ybjourney/p/12014120.html) 本文將在開發初探的程式碼基礎上進行重構。 ## 介紹 在本文所述場景中,具體的許可權管理是:許可權和角色關聯,給使用者新增角色,使用者即擁有角色的許可權,也就是基於角色的許可權控制。當然,若需要基於使用者的許可權控制也是可以的,只需要修改下相關資料結構即可。 具體的許可權驗證採用了位運算,將許可權值用十六進位制表示,每個角色擁有一個許可權總值,當判斷該角色是否有特定許可權時: ```python In [1]: permission = 0X02 In [2]: permissions = 0X0D In [3]: print((permissions & permission) == permission) False In [4]: permissions = 0X07 In [5]: print((permissions & permission) == permission) True ``` 返回值為True表示擁有該許可權,False為沒有該許可權,原理與位運算的原理有關。 0x07 = 0x01 + 0x02 + 0x04 轉換為二進位制數值可以看做是:0111 = 0001 + 0010 + 0100 按照位運算,運算子&(按位與)相應位都為1,則該位為1,否則為0,那麼許可權總值和許可權值執行按位與運算,結果恆為許可權值時才能得出擁有該許可權。 ## 實現 ### 建立 首先,針對以上場景,我們建立資料表。 #### 使用者 建立使用者表,儲存使用者資訊和對應的角色: ```python class User(db.Model): """ 使用者表 """ __tablename__ = "user" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(128), unique=True) email = db.Column(db.String(128)) password = db.Column(db.String(128)) role_id = db.Column(db.Integer) def __init__(self, name, email, password): self.name = name self.email = email self.password = bcrypt_sha256.encrypt(str(password)) ``` #### 許可權 建立許可權類,賦予每種操作許可權值,這裡舉例使用者管理和更新許可權: ```python class Permissions: """ 許可權類 """ USER_MANAGE = 0X01 UPDATE_PERMISSION = 0x02 ``` #### 角色 需要建立角色表結構,我們暫定兩種角色:普通使用者和管理員,並初始化角色和許可權。 ```python class Role(db.Model): """ 角色表 """ __tablename__ = "role" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(128), unique=True, commit="角色名") permissions = db.Column(db.Integer, commit="許可權總值") @staticmethod def init_role(): role_name_list = ['user', 'admin'] roles_permission_map = { 'user': [Permissions.USER_MANAGE], 'admin': [Permissions.USER_MANAGE, Permissions.UPDATE_PERMISSION] } try: for role_name in role_name_list: role = Role.query.filter_by(name=role_name).first() if not role: role = Role(name=role_name) role.reset_permissions() for permission in roles_permission_map[role_name]: role.add_permission(permission) db.session.add(role) db.session.commit() except: db.session.rollback() db.session.close() def reset_permissions(self): self.permissions = 0 def has_permission(self, permission): return self.permissions & permission == permission def add_permission(self, permission): if not self.has_permission(permission): self.permissions += permission ``` 隨著應用更新,許可權值會不斷增加,角色對應的許可權值隨之增大,為了保證每次更新同步到表,可以在flask應用初始化時新增: `Role.init_role()` 這樣,我們就賦予了每個角色其擁有的許可權值。 重啟應用,可以看到role表: ![](https://img2018.cnblogs.com/blog/771535/202002/771535-20200229233727954-1404112442.png) ### 鑑權 前期資料準備妥當了,接下來就是鑑權。 為了保證訪問的安全性,需要對介面和許可權進行關聯繫結,我嘗試過兩種方案: **1. 裝飾器** 封裝裝飾器,對介面檢視函式進行裝飾,裝飾器傳入許可權值作為引數,在裝飾器中根據使用者角色的許可權和許可權值進行對比,判斷該使用者是否有該介面的訪問許可權。 剛開始我是用這種方式的,小型應用介面不多的場景下使用還好,但隨著應用愈來愈複雜,賦權操作就有點繁瑣。 **2. 介面賦權** 這是我在裝飾器之後想到的一種方式,在大型應用介面比較多的情況下比較推薦,而且這種方式耦合度低,易於擴充套件。 具體操作:首先,將介面地址和許可權關聯,介面比較多的話,推薦用藍圖,基本上保證一個藍圖中的介面是一個許可權,這樣操作會簡單一些,然後,在應用初始化時將介面地址和許可權入庫,這樣可以保證每次重啟應用後資料都是最新的,最後,當用戶登入時,會根據使用者角色和請求的地址判斷其是否有許可權訪問。 以上兩種方式,今天以裝飾器鑑權舉例說明。 首先,建立鑑權裝飾器: ```python from functools import wraps from flask import session, abort from app.models import db, Users, Role Permission_code = [0X01, 0X02] def permission_can(current_user, permission): """ 檢測使用者是否有特定許可權 :param current_user :param permission :return: """ role_id = current_user.role_id role = db.session.query(Role).filter_by(id=role_id).first() return (role.permissions & permission) == permission def permission_required(permission): """ 許可權認證裝飾器 :param permission: :return: """ def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): try: current_user = Users.query.filter_by(id=session.get('user_id')).first() if not current_user and permission_can(current_user, permission): abort(403) return f(*args, **kwargs) except: abort(403) return decorated_function return decorator ``` 其中,用到了flask session,獲取當前登入使用者的user_id,根據當前使用者的角色判斷其是否擁有該許可權permission。 然後在檢視函式上新增該裝飾器,就可以鑑權了。舉例使用者管理功能: ```python @user.route('/user-manage', methods=['POST', 'GET']) @permission_required(Permissions.USER_MANAGE) def user_manage(): """ 使用者管理 :return: """ if request.method == 'POST': # 處理... ret_data = dict(code=0, ret_msg='user manage') else: # 資料處理 ... ret_data = dict(code=0, ret_msg='user list') return jsonify(ret_data) ``` 最後,分別構造請求,訪問介面測試: ```python import requests session = requests.Session() # login login_url = 'http://0.0.0.0:9001/login' login_data = dict(user='test', pwd='pwd') login_request = session.post(login_url, json=login_data) print(login_request.json()) # user_manage user_manage_url = 'http://0.0.0.0:9001/user-manage' login_request = session.post(user_manage_url) print(login_request.json()) # permission_manege permission_manage_url = 'http://0.0.0.0:9001/permission-manage' login_request = session.post(permission_manage_url) print(login_request.json()) ``` 具體程式碼見 [my github](https://github.com/Yabea/learn_flask)