Flask許可權管理
阿新 • • 發佈:2020-03-01
許可權管理是一個很常見的功能模組,本文基於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)