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)

 

posted @ 2019-11-06 22:18  换头怪  阅读(263)  评论(0编辑  收藏  举报