专栏首页用户6517667的专栏测试开发之路--Flask 之旅 (四):登录与权限控制

测试开发之路--Flask 之旅 (四):登录与权限控制

来源:https://testerhome.com

背景

接着上一篇,我们现在拥有了使用数据库的能力并使用Flask-Security针对Flask-SQLAlchemy的扩展完成了对User和Role表的维护。今天我们再讲讲Flask-Security是怎么做权限管理的并介绍Flask-Security是如何扩展Flask-Login做用户管理的

消息闪现

在讲今天的主题前我们先补充一下之前漏掉的一个东西,就是Flask的消息闪现。 它是Flask的定制方法。可以像模板页面传递一条或多条信息而不需要像模板传递任何参数。我们直接就可以在页面中获取名为这段信息。 举个例子,还记得我们之前讲表单的时候,每个form对象都有一个errors属性么。我们之前是将form这个对象传递给模板页面进行渲染。现在我们直接用flush函数进行传递。如下:

@app.route('/config/save', methods=['POST'])
def save_config():
    form = forms.ConfigForm()

    if form.validate_on_submit():
        update_config(form)
    else:
        flash(form.errors)
        return render_template('detail.html', form=form)

    return redirect(url_for('index'))

上面我们在表单验证不通过的时候向用户发送一个闪现消息。 其中flash函数把form的errors封装了起来。 我们来看看在模板页面中是怎么获取这段error信息的。

{% with messages = get_flashed_messages() %}
            {% if messages %}
                <ul class=flashes>
                    {% for message in messages %}
                        <li><font color="red">{{ message }}</font></li>
                    {% endfor %}
                </ul>
            {% endif %}
        {% endwith %}

上面是页面的代码。 我们可以使用Falsk内置的方法获取所有用flash封装的信息。 我们把这段代码放在base.html里面然后在页面上继承base.html即可。

角色保护

好了,回到主线。当我们拥有了用户和角色以后, 就可以很方便的使用Flask-Security的装饰器来保护我们的页面了。 @roles_required('Admin')可以用来保护一个路由方法。只有当前用户拥有Admin的角色的时候才被准许访问(关于当前用户的管理是Flask-Login的内容,我们暂且知道这个事情就好,之后我会详细解释)。例如下面的使用方法:

@app.route('/config/save', methods=['POST'])
@roles_required('Admin')
def save_config():

注:roles_required这个装饰器一定要放在app.route下面。否则会有问题。 当用户没有Admin权限的时候是无法访问这个路由方法的。它会通过flush函数像页面反馈错误信息。如下:

除了roles_required之外,你还可以使用roles_accepted。 他们之前的区别就是前者必须严格的拥有所指定的权限,后者是只要拥有其中一个权限就可以。如下:

@roles_accepted('editor', 'author')

这段的意思就是要求用户至少拥有这两种role其中的一种就可以访问。 当然除了使用装饰器以外,Flask-Security也是支持以编码的方式控制权限的。例如:

@app.route('/')
@app.route('/index', methods=['GET', 'POST'])
@login_required
def index():
    if current_user.has_role('Admin'):
        names = Env.query.all()
        return render_template('index.html', names=names)
    else:
        names = current_user.envs
        return render_template('index.html', names=names)

这段代码我们使用current_user.has_role方法来判断我们当前用户是属于哪一种角色的(current_user是Flask-Security针对Flask-Login做的扩展,作用是在当前session中维护用户的信息,之后会详细说明)。关于Flask-Security提供的更多方法,请参照文档:https://pythonhosted.org/Flask-Security/api.html

Flask-Login

初始化Flask-Login
from flask_login import LoginManager

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

我们使用LoginManager来初始化,并把login_view设置为login方法。 这是为了之后给登录保护使用的。当Flask-Login检测到用户没有登录的时候会把链接重定向到login_view中去。 所以我们设置为login。 一个叫login的路由方法。

登录

终于说到Login了,写帖子的时候我挺纠结的,到底先讲什么后讲什么。因为这三个模块相辅相成。尤其是Flask-Security,它其实就是扩展了其他各种模块。想用它就必须安装其他模块,而且它的封装改变了其他模块的使用方式。 例如Flask-Login有login和logout方法。Flask-Security也有,你需要使用Flask-Security提供的方法进行登录和登出才能做好权限控制,因为原生的Flask-Loing无法保存User和Role相关的信息。你可以这么看待Flask-Security,它的作用就是封装了其他模块。它的底层其实也是调用的其他模块。 举个例子,当你想要做用户登录的时候。我们使用如下的方式:

from flask_security.utils import login_user, logout_user

@app.route('/user/register', methods=['GET', 'POST'])
def register():
    form = forms.RegisterForm()
    if form.validate_on_submit():
        if form.password.data != form.password_again.data:
            errors = '两次输入的密码不同'
            return render_template('register.html', form=form, errors=errors)
        new_user = user_datastore.create_user(email=form.email.data, password=form.password.data)
        normal_role = user_datastore.find_role('User')
        db.session.add(new_user)
        user_datastore.add_role_to_user(new_user, normal_role)
        login_user(new_user)
        return redirect(url_for('index'))
    return render_template('register.html', form=form)

这是一个用户注册的代码。表单验证的部分我先不看了。 我们首先使用Flask-Security的方法在数据库中创建用户信息,给用户添加为普通用户的权限。然后调用login_user方法进行登录。这里需要注意的是,请看我一开始import的是flask_security.utiles中的login_user方法而不是Flask-Login的。就像刚我才说的Flask-Security是封装了其他各种模块的存在。所以现在我们是完全使用Flask-Security的方式来进行登录。 当我们登录了以后,我们的用户信息,也就是User对象会自动的保存在session中。 我们可以通过引入current_user的方式获取当前的用户。如下:

from flask_security import  current_user

@app.route('/user/login', methods=['POST', 'GET'])
def login():
    if not current_user.is_anonymous:
        return redirect(url_for('index'))
    form = forms.LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user is None:
            form.errors['username_error'] = 'user does not exit'
            return render_template('login.html', form=form)
        if not user.password == form.password.data:
            form.errors['password_error'] = 'password is not right'
            return render_template('login.html', form=form)
        login_user(user, remember=True)
        return redirect(url_for('index'))
    return render_template('login.html', form=form)

上面是登录的代码。我们可以看到我们import的仍然是Flask-Security的current_user而不是Flask-Login的。 我们先判断当前的user是不是匿名用户(未登录的就是匿名用户)。如果不是,说明已经登录了,重定向到首页。这里我们就是引用了current_user。它其实就是我们使用Flask-Security创建的User对象。它包含了所有的User对象的属性和方法。可以看到我们发现用户未登录后,首先判断是不是表单提交以及表单提交是否通过。如果通过了就从数据库中查询出用户的信息。 判断用户是否存在以及填写的密码是否正确。登录后,这个User对象就赋值给current_user了。 我们甚至可以在模板页面中直接使用current_user。如下:

<div class="col-md-12 column">
            <h4>
                你好 {{ current_user.email }}
            </h4>
        </div>

Flask-Login自动会把current_user传递给模板页面。 所以我们就这样直接在页面引用就可以了。 我们把上面的代码写入到base.html,提取每个用户的邮箱。 并显示

登录保护

Flask-Security提供了roles_required这种装饰器来进行权限的保护。自然也就提供了login_required的装饰器来进行登录保护。如下:

@app.route('/user/logout')
@login_required
def logout():

当Flask发现用户并没有登录的时候,就会把链接重定向到我们一开始设置的login_view的页面上了。也就是登录页面。

Flask-Login和Flask-Security有个巨坑无比的事情就是,大家尽量不要把Flask-Login的版本升级到0.4.0, 使用0.3.2就可以了。 因为Flask-Security在封装Flask-Login 0.4.0的时候会报一个找不到token_loader的错误。因为Flask-Login在0.4.0的时候已经不使用这个token了。

总结

好了,经过这么一折腾,我们的环境管理平台终于可以给人用了。之后我会再优化完善一些东西,会继续更新一些内容

欢迎参加众测:

https://wap.ztestin.com/site/register?usercode=FAAAQwMQGAAXAwQBA3QhExcDHAQDPjVaABMIQg%3D%3D

本文分享自微信公众号 - 软件测试培训(iTestTrain),作者:软件测试培训

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Cypress简易入门教程

    1)安装node.js(https://nodejs.org/en/download/),根据版本选择32位或64位。

    小老鼠
  • Jmeter购物车自动化实例

      右击Threadgroup,新建观察树ViewResultsTree(观察树是全局的,可在TestPlan下的任意地方新建)

    小老鼠
  • 软件安全性测试(连载12)

    除了XSS注入外,还存在XML注入、JSON注入和XPath注入三种代码注入情形。

    小老鼠
  • WPScan使用完整攻略:如何对WordPress站点进行安全测试

    WPScan是Kali Linux默认自带的一款漏洞扫描工具,它采用PHP编写,能够扫描WordPress网站中的多种安全漏洞,其中包括主题漏洞、插件漏洞和Wo...

    FB客服
  • flask 富文本编辑器(flask 22)

    from flask_ckeditor import CKEditor, upload_success, upload_fail

    用户5760343
  • 我曾经左手砍70人,右手聘70人-李开复给主管的领导课

    我是攻城师
  • 如何通过CM为HDFS启用Federation

    本文主要讲述如何通过CM为HDFS启用Federation。如果你对HDFS的Federation不太了解或者想知道Federation能够解决什么问题的话,建...

    Fayson
  • 干货 | X-Series企业级开发实践

    作者简介 赫杰辉,携程框架研发部高级研发经理,负责携程DAL组件开发与推广。在开发一线奋战多年的老兵,热爱中国传统文化和推广开源软件,希望用自己开发的工具为大家...

    携程技术
  • mycat安装使用 原

        github地址:https://github.com/MyCATApache/Mycat-Server/wiki

    尚浩宇
  • 漫谈建造者模式

    解决构造器赋值的最简单的方式就是用setter函数来给成员变量赋值,以替代冗长的构造函数。

    汐楓

扫码关注云+社区

领取腾讯云代金券