再次理解Flask中登录与用户角色
再次理解 Flask 中登录与用户角色
登录
登录由以下几个部分构成:
- 填写表单
- 验证用户信息是否正确
- 保存登录状态
填写表单
填写表单这个应该没什么好说的,但是可以让表单更高级——加入验证码。
在这里我是用的 GeeTest。
接入方法:
- 在其官网下载对应语言的 SDK,其中 Python 的 SDK 包括一个 geetest 的包以及一个用来注入到 HTML 中的 js 文件
在 HTML 中给验证码留出相应的位置,第一个控标签除就是验证码所在标签
<div class="col s12" id="embed-captcha"></div> <div class="col s12"> <p id="wait" class="show" style="color: #ee6e73">正在加载验证码......</p> <p id="notice" class="hide" style="color: red">请先拖动验证码到相应位置</p> </div>
引入所给 js 文件,并且进行初始化,这部分只用复制下面的结构然后稍微改动即可
<script src="{{ url_for('static', filename='js/gt.js') }}"></script> <script> var handlerEmbed = function (captchaObj) { $("#embed-submit").click(function (e) { var validate = captchaObj.getValidate(); if (!validate) { $("#notice")[0].className = "show"; setTimeout(function () { $("#notice")[0].className = "hide"; }, 2000); e.preventDefault(); } }); // 将验证码加到id为captcha的元素里,同时会有三个input的值:geetest_challenge, geetest_validate, geetest_seccode captchaObj.appendTo("#embed-captcha"); captchaObj.onReady(function () { $("#wait")[0].className = "hide"; }); // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html }; $.ajax({ // 获取id,challenge,success(是否启用failback) url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存 type: "get", dataType: "json", success: function (data) { // 使用initGeetest接口 // 参数1:配置参数 // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件 initGeetest({ gt: data.gt, challenge: data.challenge, product: "embed", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效 offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注 width: '25%' // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config }, handlerEmbed); } }); </script>
验证验证码状态(其实这部分也不用添加,不过添加了能更进一步提升可靠性,具体参阅官方文档)
# 这个视图函数必须添加 @main_blueprint.route('/pc-geetest/register', methods=['GET']) def get_pc_captcha(): gt = GeetestLib(Config.pc_id, Config.pc_key) status = gt.pre_process() session[gt.GT_STATUS_SESSION_KEY] = status response_str = gt.get_response_str() return response_str @main_blueprint.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): # 这下面添加了能提升可靠性,也可以不添加 gt = GeetestLib(Config.pc_id, Config.pc_key) challenge = request.form[gt.FN_CHALLENGE] validate = request.form[gt.FN_VALIDATE] seccode = request.form[gt.FN_SECCODE] status = session[gt.GT_STATUS_SESSION_KEY] if status: result = gt.success_validate(challenge, validate, seccode) else: result = gt.failback_validate(challenge, validate, seccode) ...
验证用户信息
平常我们都是在视图函数中来检验密码是否是正确,但实际上和验证表单内容是否合法一样,这个工作也可以在表单类中完成。
我们在视图函数中通过 form.validate_on_submit 来检查表单是否被成功提交,实际上在返回结果之前我们要先调用 form.validate 函数,也正是在这里面,我们完成对表单内容合法性和密码正确性的检查。
class LoginForm(FlaskForm):
username = StringField('用户名', [DataRequired(), Length(max=255)])
password = PasswordField('密码', [DataRequired()])
remember_me = BooleanField('记住登录状态')
def validate(self): # 在 validate_on_submit 的时候会检查
check_validate = super(LoginForm, self).validate()
if not check_validate:
return False
user = User.query.filter_by(username=self.username.data).first()
if not user:
self.username.errors.append('用户名或密码错误')
return False
if not user.verify_password(self.password.data):
self.username.errors.append('用户名或密码错误')
return False
return True
如果出现了错误则只用把它添加到相应表单的 errors 中去,然后就可以在 HTML 中显示出来。
<div class="input-field col s12">
{{ form.password.label }}
{{ form.password(class_='validate') }}
{% if form.password.errors %}
{% for e in form.password.errors %}
<p class="help-block alert-danger">{{ e }}</p>
{% endfor %}
{% endif %}
</div>
保存登录状态
最基本的方法当然是使用 session 来保存,然后通过判断 session 是否有相应的信息来检查是否登录处于状态。
比较常用也是进阶的就是使用 flask-login 了。
使用 flask-login 需要根据模块来进行一定的配置:
- 对 User 类实现特定的方法,这里可以通过继承 UserMixin 来简化,但是要注意如果用户 id 的格式和默认的不同则还是要重写 get_id 方法
- 定义登陆的视图,load_user 函数等
login_manger = LoginManager() login_manger.login_view = 'main.login' login_manger.session_protection = 'strong' login_manger.login_message = '请登录以访问该页面' login_manger.login_message_category = 'info' @login_manger.user_loader def load_user(userid): from .models import User return User.query.get(userid)
用户角色
要使用用户权限,则要进行用户角色的相应配置,在这里可以使用 flask-principal 模块来对用户权限进行管理。
当然我们要先建立一张表来控制用户和用户角色的多对多关系,在建立数据库之后不要忘记初始化角色表。
flask-principal 的关键名词主要有 Identity,Permission,Need。其中 Identity 和 Permission 都是通过 Need 来实现功能。Need 则是一些 namedtuple(相当于 C 语言中的结构体),包括 method 和 value 两个属性,定义了每种身份可以干什么。
例如 UserNeed 的 method 默认则是 id,key 则应该传入对应用户的 id 值,而 RoleNeed 的 method 的默认值则是 role,key 的默认值应该是 role 的名称。其中 UserNeed 和 RoleNeed 是通过 partical 固定了 Need 的一个参数。点击查看 nametuple 和 partical 用法。
Identity 是通过 user.id 来进行初始化的,然后此时会自动调用自己定义的初始化函数把需要添加的 Need 添加进这个 Identity 中。
在 __init__.py 中定义这个函数
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
identity.user = current_user
if hasattr(current_user, 'id'):
identity.provides.add(UserNeed(current_user.id))
if hasattr(current_user, 'roles'):
for role in current_user.roles:
identity.provides.add(RoleNeed(role.name))
而当用户登录登出时 Identity 应该发生改变,此时应该调用 identity_changed 方法来发送信号,此时就会调用 on_identity_loaded 函数来进行新 Identity 的初始化
identity_changed.send(
current_app._get_current_object(),
identity=Identity(user.id)
)
而 Permission 则是通过 Need 来进行初始化,这些 Need 就表示当前权限所需的角色,只有满足了相应的角色才能达到相应权限。
初始化 Permission
admin_permission = Permission(RoleNeed('admin'))
poster_permission = Permission(RoleNeed('poster'))
default_permission = Permission(RoleNeed('default'))
使用 Permission 的方法:
使用初始化权限的装饰器
@login_required @poster_permission.require(http_exception=403) def edit(id): ...
使用 Permission.can() 来判断是否符合权限
permission = Permission(UserNeed(post.user.id)) # 发布者和管理员都有权限 if permission.can() or admin_permission.can(): ...