接着上一篇,我们现在拥有了使用数据库的能力并使用 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
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 了。
好了,经过这么一折腾,我们的环境管理平台终于可以给人用了。之后我会再优化完善一些东西,会继续更新一些内容