一、用戶在數據庫中的表示shell
app/models.py 修改roles表,添加角色的權限數據庫
只要有一個角色的defalut字段要設爲True,其餘都設爲False。用戶註冊時,其角色會被設爲默認角色flask
添加了permissions字段,其值是一個整數,表示位標誌,各個操做都對應一個位位置,能執行某項操做的角色,其位會被設爲1數組
class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) default = db.Column(db.Boolean, default=False, index=True) permissions = db.Column(db.Integer) users = db.relationship('User', backref='role', lazy='dynamic') def __repr__(self): return '<Role %r>' %self.name
二、權限常量session
各個操做所需的程序權限是不同的。Flask中,各個操做以下所示,其中,操做的權限使用8位表示,如今只用了其中5位,其餘3位可用於未來的補充app
使用權限組織角色,在之後添加新角色時,只需執行不一樣的權限組合便可函數
app/models.py 添加權限常量ui
#權限常量 class Permission: FOLLOW = 0x01 COMMENT = 0x02 WRITE_ARTICLES = 0x04 MODERATE_COMMENTS = 0x08 ADMINSTER = 0x80
三、在數據庫中使用建立角色spa
將角色添加到手動添加到數據庫既耗時又容易出錯,做爲替代,咱們在Role類中添加一個類方法,完成這個操做code
app/models.py
insert_roles()函數並不直接建立新角色對象,而是經過角色名查找現有角色,而後再更新,只有當數據庫中沒有該角色時纔會建立,這樣之後更新了列表,就能夠執行更新操做了。要想添加新角色,或者修改角色的權限,修改roles數組,再運行函數便可。
注:「匿名」角色不須要在數據庫中表示出來,這個角色的做用就是爲了表示不在數據庫中的用戶
#定義數據庫模型 class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) default = db.Column(db.Boolean, default=False, index=True) permissions = db.Column(db.Integer) users = db.relationship('User', backref='role', lazy='dynamic') #在數據庫中建立角色 @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() def __repr__(self): return '<Role %r>' %self.name
若想把角色寫入數據庫,可以使用shell會話:
三、賦予角色
app/models.py 定義默認的用戶角色
大多數用戶在註冊時賦予的角色都是「用戶」,由於這是默認角色
管理員在最開始就被賦予「管理員」角色
管理員由設置變量FLASKY_ADMIN中的電子郵件地址識別,只要這個電子郵件地址出如今註冊請求中,就會被賦予正確的角色
class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) password_hash = db.Column(db.String(128)) email = db.Column(db.String(64), unique=True, index=True) #確認用戶帳戶 confirmed = db.Column(db.Boolean, default=False) #定義默認的用戶角色 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(permission=0xff).first() if self.role is None: self.role = Role.query.filter_by(default=True).first() def generate_confirmation_token(self, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'confirm': self.id}) def confirm(self, token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return False if data.get('confirm') != self.id: return False self.confirmed = True db.session.add(self) return True #在User模型中加入密碼散列 @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password) def __repr__(self): return '<User %r>' %self.username
四、角色驗證
爲簡化角色和權限的實現過程,在User模型中添加一個輔助方法,檢查是否有指定的權限
app/models.py 檢查用戶是否有指定的權限
User模型中添加的can()方法在請求和賦予角色這兩種權限之間進行位與操做
若是用戶中包含請求的全部權限位,則返回True,表示容許用戶執行此項操做。
from flask_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.ADMINSTER) def generate_confirmation_token(self, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'confirm': self.id})
class AnonymousUser(AnonymousUserMixin): def can(self, permissions): return False def is_administrator(self): return False login_manager.anonymous_user = AnonymousUser
四、檢查用戶權限的自定義修飾器
app/decorators.py
這兩個修飾器使用了functools包,若是用戶不具備指定權限,則返回403錯誤,即HTTP「禁止」錯誤
from functools import wraps from flask import abort from flask_login import current_user from .models import Permission 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 decorator def admin_required(f): return permission_required(Permission.ADMINSTER)(f)
下面示例這些修飾器的使用方法
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() 時都多添加一個模板參數,能夠使用上下文處理器。上下文處理器能讓變量在全部模板中全局可訪問
修改
app/main/__init__.py 把Permission類加入模板上下文
@main.app_context_processor def inject_permission(): return dict(Permission=Permission)