Flask Web开发基础实战-1.1用户角色和权限管理

前言:

用户在程序中的地位是不同的,不同的是拥有对应的权限或角色,那么会有许多分类,例如,常见的:游客,普通用户,管理员,协管员(类似吃鸡游戏的巡查员)。
有多种方法可用于在程序中实现角色。具体采用何种实现方法取决于所需角色的数量和细分程度。简单的程序可能只需要两个角色,一个表示普通用户,一个表示管理员。对于这种情况,在 User 模型中添加一个 is_administrator 布尔值字段就足够了。复杂的程序可能需要在普通用户和管理员之间再细分出多个不同等级的角色。有些程序甚至不能使用分立的角色,这时赋予用户某些权限的组合或许更合适。

一,角色在数据库中的表示

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)
    default = db.Column(db.Boolean, default=False, index=True)
    permissions = db.Column(db.Integer)
    users = db.relationship('User', backref='role', lazy='dynamic')

只有一个角色的 default 字段要设为 True,其他都设为 False。用户注册时,其角色会被设为默认角色。
这个模型的第二处改动是添加了 permissions 字段,其值是一个整数,表示位标志。各操作都对应一个位位置,能执行某项操作的角色,其位会被设为 1。
各操作所需的程序权限是不一样的。常见的各种操作如下表:

在这里插入图片描述
注意:操作的权限使用 8 位表示,现在只用了其中 5 位,其他 3 位可用于将来的扩充。
app/models.py:权限常量

class Permission:
    FOLLOW = 1
    COMMENT = 2
    WRITE = 4
    MODERATE = 8
    ADMIN = 16

用户角色以及定义角色使用的权限位如下表:
在这里插入图片描述
使用权限组织角色,这一做法让你以后添加新角色时只需使用不同的权限组合即可。
将角色手动添加到数据库中既耗时又容易出错。作为替代,我们要在 Role 类中添加一个类方法,完成这个操作!

class Role(db.Model):
	#
    @staticmethod
    def insert_roles():
        roles = {
            'User': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE],
            'Moderator': [Permission.FOLLOW, Permission.COMMENT,
                          Permission.WRITE, Permission.MODERATE],
            'Administrator': [Permission.FOLLOW, Permission.COMMENT,
                              Permission.WRITE, Permission.MODERATE,
                              Permission.ADMIN],
        }
        default_role = 'User'
        for r in roles:
            role = Role.query.filter_by(name=r).first()
            if role is None:
                role = Role(name=r)
            role.reset_permissions()
            for perm in roles[r]:
                role.add_permission(perm)
            role.default = (role.name == default_role)
            db.session.add(role)
        db.session.commit()

insert_roles() 函数并不直接创建新角色对象,而是通过角色名查找现有的角色,然后再进行更新。只有当数据库中没有某个角色名时才会创建新角色对象。如果以后更新了角色列表,就可以执行更新操作了。要想添加新角色,或者修改角色的权限,修改roles 数组,再运行函数即可。
注意,“匿名”角色不需要在数据库中表示出来,这个角色的作用就是为了表示不在数据库中的用户。
角色写入数据库,使用 shell 会话:

(venv) D:\E盘\python\Flask Web应用\社交博客程序实验版>python manage.py shell
>>> Role.insert_roles()
>>> Role.query.all()
[<Role 'Administrator'>, <Role 'User'>, <Role 'Moderator'>]
>>>

二,赋予角色

用户在程序中注册账户时,会被赋予适当的角色。大多数用户在注册时赋予的角色都是“用户”,因为这是默认角色。唯一的例外是管理员,管理员在最开始就应该赋予“管理员”角色。管理员由保存在设置变量 FLASKY_ADMIN 中的电子邮件地址识别,只要这个电子邮件地址出现在注册请求中,就会被赋予正确的角色。示例 9-4 展示了如何在 User 模型的构造函数中完成这一操作。
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(name='Administrator').first()
            if self.role is None:
                self.role = Role.query.filter_by(default=True).first()	

User 类的构造函数首先调用基类的构造函数,如果创建基类对象后还没定义角色,则根据电子邮件地址决定将其设为管理员还是默认角色。

三,角色验证

为了简化角色和权限的实现过程,我们可在 User 模型中添加一个辅助方法,检查是否有指定的权限!
app/models.py:检查用户是否有指定的权限

from flask_login import UserMixin, AnonymousUserMixin
class User(UserMixin, db.Model):
 	# ...
    def can(self, perm):
        return self.role is not None and self.role.has_permission(perm)

    def is_administrator(self):
        return self.can(Permission.ADMIN)
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()。
对具有特定权限的用户开放,可以使用自定义的修饰器。实现了两个修饰器,一个用来检查常规权限,一个专门用来检查管理员权限。
app/decorators.py:检查用户权限的自定义修饰器

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 decorated_function
    return decorator
def admin_required(f):
    return permission_required(Permission.ADMIN)(f)

这两个修饰器都使用了 Python 标准库中的 functools 包,如果用户不具有指定权限,则返回 403 错误码,即 HTTP“禁止”错误。要添加一个 403 错误页面。
如何使用这些修饰器的示例:

from decorators import admin_required, permission_required
from .models import Permission
@main.route('/admin')
@login_required
@admin_required
def for_admins_only():
 	return "管理员专用!"
@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_permissions():
    return dict(Permission=Permission)

新添加的角色和权限可在单元测试中进行测试。如下所示:
tests/test_user_model.py:角色和权限的单元测试

class UserModelTestCase(unittest.TestCase):
	 # ...
	 def test_roles_and_permissions(self):
	 Role.insert_roles()
	 u = User(email='john@example.com', password='cat')
	 self.assertTrue(u.can(Permission.WRITE_ARTICLES))
	 self.assertFalse(u.can(Permission.MODERATE_COMMENTS))
	 def test_anonymous_user(self):
	 u = AnonymousUser()
	 self.assertFalse(u.can(Permission.FOLLOW))

注意:每次修改数据库模型后,都要更新数据库!

python manage.py db migrate
python manage.py db upgrade


四,相关代码模块

1.app/templates/403.html

{% extends "bases.html" %}
{% block title %}Flasky - Forbidden{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>无该权限!</h1>
</div>
{% endblock %}

2.app/main/errors.py

from . import main
@main.app_errorhandler(403)
def forbidden(e):
    return render_template('403.html'), 403
@main.app_errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

3.manage.py

from flask_migrate import Migrate,MigrateCommand
from app import create_app, db
from app.models import User, Role,Permission
from flask_script import Manager, Shell

app = create_app('default')
migrate = Migrate(app, db)
manager = Manager(app)
@app.shell_context_processor
def make_shell_context():
    return dict(db=db, User=User, Role=Role, Permission=Permission)
manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)

if __name__ == '__main__':
    manager.run()

最后,文中如有不足,敬请批评指正!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值