您好,登錄后才能下訂單哦!
Web 程序中的用戶并非都具有同樣地位。在大多數程序中,一小部分可信用戶具有額外權
限,用于保證程序平穩運行。管理員就是最好的例子,但有時也需要介于管理員和普通用
戶之間的角色,例如內容協管員。
有多種方法可用于在程序中實現角色。具體采用何種實現方法取決于所需角色的數量和細
分程度。例如:
1)簡單的程序可能只需要兩個角色,一個表示普通用戶,一個表示管理員。
對于這種情況,在 User 模型中添加一個 is_administrator 布爾值字段就足夠了。
2)復雜的程序可能需要在普通用戶和管理員之間再細分出多個不同等級的角色。
有些程序甚至不能使用分立的角色,這時賦予用戶某些權限的組合或許更合適。
本章介紹的用戶角色實現方式結合了分立的角色和權限,賦予用戶分立的角色,但角色使
用權限定義。
9.1 角色在數據庫中的表示
第 5 章創建了一個簡單的 roles 表,用來演示一對多關系。示例 9-1 是改進后的 Role 模型。
示例 9-1 app/models.py:角色的權限
class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) # 設置為主鍵 name = db.Column(db.String(64), unique=True) # 設置unique唯一 default = db.Column(db.Boolean, default=False, index=True) # index為改字段創建索引,default為設置默認值 permissions = db.Column(db.Integer) # 以數字表示權限等級 users = db.relationship('User', backref='role', lazy='dynamic') # 動態關聯
只有一個角色的 default 字段要設為 True,其他都設為 False。用戶注冊時,其角色會被
設為默認角色。
這個模型的第二處改動是添加了 permissions 字段,其值是一個整數,表示位標志。各操
作都對應一個位位置,能執行某項操作的角色,其位會被設為 1。
顯然,各操作所需的程序權限是不一樣的。對 Flasky 開說,各種操作如表 9-1 所示。
表9-1 程序的權限
操 作 位 值 說 明
關注用戶 0b00000001(0x01) 關注其他用戶
在他人的文章中發表評論 0b00000010(0x02) 在他人撰寫的文章中發布評論
寫文章 0b00000100(0x04) 寫原創文章
管理他人發表的評論 0b00001000(0x08) 查處他人發表的不當評論
管理員權限 0b10000000(0x80) 管理網站
注意,操作的權限使用 8 位表示,現在只用了其中 5 位,其他 3 位可用于將來的擴充。
9-1 中的權限可使用示例 9-2 中的代碼表示。
示例 9-2 app/models.py:權限常量
class Permission: FOLLOW = 0x01 COMMENT = 0x02 WRITE_ARTICLES = 0x04 MODERATE_COMMENTS = 0x08 ADMINISTER = 0x80
表 9-2 列出了要支持的用戶角色以及定義角色使用的權限位。
表9-2 用戶角色
用戶角色 權 限 說 明
匿名 0b00000000(0x00)未登錄的用戶。在程序中只有閱讀權限
用戶 0b00000111(0x07) 具有發布文章、發表評論和關注其他用戶的權限。這是新用戶的默認角色
協管員 0b00001111(0x0f) 增加審查不當評論的權限
管理員0b11111111(0xff) 具有所有權限,包括修改其他用戶所屬角色的權限
(譯注:有點亂,應該結合程序權限表9-1和9-2理解:二進制bit值共八位<0b后面8個零>默認無權限用戶是8位全部0,操作分別有5種(關注,評論,撰寫,屏蔽,管理權) 從右往左,依次設置為1,管理權使第八位。表9-2則是合并權限組合定義了角色,無權用戶8位全是零,普通用戶擁有撰寫,評論,關注三項許可權限(權限標志位分別是1,2,3),所以二進制許可代碼組合就是0b00000111,其他同理
)
示例 9-3 app/models.py::在數據庫中創建角色
class Role(db.Model): # ... @staticmethod def insert_roles(): roles = { 'User': (Permission.FOLLOW | Permission.COMMENT | Permission.WRITE_ARTICLES, True), 'Moderator': (Permission.FOLLOW | Permission.COMMENT | Permission.WRITE_ARTICLES | Permission.MODERATE_COMMENTS, False), 'Administrator': (0xff, False) } for r in roles: role = Role.query.filter_by(name=r).first() if role is None: role = Role(name=r) role.permissions = roles[r][0] role.default = roles[r][1] db.session.add(role) db.session.commit()
insert_roles() 函數并不直接創建新角色對象,而是通過角色名查找現有的角色,然后再
進行更新。只有當數據庫中沒有某個角色名時才會創建新角色對象。如此一來,如果以后
更新了角色列表,就可以執行更新操作了。要想添加新角色,或者修改角色的權限,修改
roles 數組,再運行函數即可。注意,“匿名”角色不需要在數據庫中表示出來,這個角色
的作用就是為了表示不在數據庫中的用戶。
若想把角色寫入數據庫,可使用 shell 會話:
(venv) $ python manage.py shell >>> Role.insert_roles() >>> Role.query.all() [<Role u'Administrator'>, <Role u'User'>, <Role u'Moderator'>]
9.2 賦予角色
用戶在程序中注冊賬戶時,會被賦予適當的角色。大多數用戶在注冊時賦予的角色都是
“用戶”,因為這是默認角色。唯一的例外是管理員,管理員在最開始就應該賦予“管理
員”角色。管理員由保存在設置變量 FLASKY_ADMIN 中的電子郵件地址識別,只要這個電子
郵件地址出現在注冊請求中,就會被賦予正確的角色。示例 9-4 展示了如何在 User 模型的
構造函數中完成這一操作。
示例 9-4 app/models.py:定義默認的用戶角色
class User(UserMixin, db.Model): # ... # 用戶字段 def __init__(self, **kwargs): super(User, self).__init__(**kwargs) if self.role is None: if self.email == current_app.config['FLASKY_ADMIN']: self.role = Role.query.filter_by(permissions=0xff).first() if self.role is None: self.role = Role.query.filter_by(default=True).first() # ... # 用戶的其他方法
User 類的構造函數首先調用基類的構造函數,如果創建基類對象后還沒定義角色,則根據
電子郵件地址決定將其設為管理員還是默認角色
9.3 角色驗證
為了簡化角色和權限的實現過程,我們可在 User 模型中添加一個輔助方法,檢查是否有指
定的權限,如示例 9-5 所示。
示例 9-5 app/models.py:檢查用戶是否有指定的權限
from flask.ext.login import UserMixin, AnonymousUserMixin class User(UserMixin, db.Model): # ... def can(self, permissions): return self.role is not None and (self.role.permissions & permissions) == permissions def is_administrator(self): return self.can(Permission.ADMINISTER) # 匿名用戶類 class AnonymousUser(AnonymousUserMixin): def can(self, permissions): return False def is_administrator(self): return False login_manager.anonymous_user = AnonymousUser
User 模型中添加的 can() 方法在請求和賦予角色這兩種權限之間進行位與操作。如果角色
中包含請求的所有權限位,則返回 True,表示允許用戶執行此項操作。檢查管理員權限的
功能經常用到,因此使用單獨的方法 is_administrator() 實現。
出于一致性考慮,我們還定義了 AnonymousUser 類,并實現了 can() 方法和 is_administrator()
方法。這個對象繼承自 Flask-Login 中的 AnonymousUserMixin 類,并將其設為用戶未登錄時
current_user 的值。這樣程序不用先檢查用戶是否登錄,就能自由調用 current_user.can() 和
current_user.is_administrator()。
如果你想讓視圖函數只對具有特定權限的用戶開放,可以使用自定義的修飾器。示例 9-6
實現了兩個修飾器,一個用來檢查常規權限,一個專門用來檢查管理員權限。
示例 9-6 app/decorators.py:檢查用戶權限的自定義修飾器
from functools import wraps
from flask import abort
from flask.ext.login import current_user
def permission_required(permission):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.can(permission):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
def admin_required(f):
return permission_required(Permission.ADMINISTER)(f)
這兩個修飾器都使用了 Python 標準庫中的 functools 包,如果用戶不具有指定權限,則返
回 403 錯誤碼,即 HTTP“禁止”錯誤。我們在第 3 章為 404 和 500 錯誤編寫了自定義的
錯誤頁面,所以現在也要添加一個 403 錯誤頁面。
下面我們舉兩個例子演示如何使用這些修飾器。
from decorators import admin_required, permission_required from .models import Permission @main.route('/admin') @login_required @admin_required def for_admins_only(): return "For administrators!" @main.route('/moderator') @login_required @permission_required(Permission.MODERATE_COMMENTS) def for_moderators_only(): return "For comment moderators!"
在模板中可能也需要檢查權限,所以 Permission 類為所有位定義了常量以便于獲取。為了
避免每次調用 render_template() 時都多添加一個模板參數,可以使用上下文處理器。上
下文處理器能讓變量在所有模板中全局可訪問。修改方法如示例 9-7 所示。
示例 9-7 app/main/__init__.py:把 Permission 類加入模板上下文
@main.app_context_processor def inject_permissions(): return dict(Permission=Permission)
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。