12、用户角色
1、用户在数据库中的表示
app/models.py 修改roles表,添加角色的权限
只要有一个角色的defalut字段要设为True,其他都设为False。用户注册时,其角色会被设为默认角色
添加了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
2、权限常量
各个操作所需的程序权限是不一样的。Flask中,各个操作如下所示,其中,操作的权限使用8位表示,现在只用了其中5位,其他3位可用于将来的补充
使用权限组织角色,在以后添加新角色时,只需执行不同的权限组合即可
app/models.py 添加权限常量
#权限常量 class Permission: FOLLOW = 0x01 COMMENT = 0x02 WRITE_ARTICLES = 0x04 MODERATE_COMMENTS = 0x08 ADMINSTER = 0x80
3、在数据库中使用创建角色
将角色添加到手动添加到数据库既耗时又容易出错,作为替代,我们在Role类中添加一个类方法,完成这个操作
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会话:
3、赋予角色
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
4、角色验证
为简化角色和权限的实现过程,在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
4、检查用户权限的自定义修饰器
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)