menu Chancel's blog
rss_feed
Chancel's blog
我就是这样的人

Flask用户登录两步验证码设计(flask-login)

作者:Chancel, 更新:2021 Jan 31, 字数:3944, 已阅:840

这篇文章更新于 856 天前,文中部分信息可能失效,请自行甄别无效内容。

1. 背景

开发一个博客程序,对于登录这一块的要求与一般的程序不太一样,一般只有一个用户登录需求

Flask本身提供了非常简洁的登录模块 Flask-Login模块方便我们快速生成登录程序模块

利用这个模块我们可以快速开发一个登录逻辑,再搭配Google Authenticator的二次验证来提高登录安全强度

以下开发环境测试均基于Python3.6.9

2. 登录设计

2.1. Flask-Login登录设计

首先安装 flask-login 扩展

pip3 install flask-login

然后写一个最简单的Flask应用例子,访问首页返回 hello world

from flask import Flask

app = Flask(__name__)

@app.route('/'):
    return 'hello world'

在初始化你的APP时初始化flask-login登录模块,看起来像这样

from flask_login import LoginManager

login_manager = LoginManager()
login_manager.init_app(app)

然后根据flask-login的文档,我们需要设计一个用户类,这个类必须实现以下3种属性1种方法

  • is_authenticated
  • is_active
  • is_anonymous
  • get_id()

这几个属性以及方法用于告诉Flask应用当前用户的状态,我们也可以选择直接继承 UserMixin 类,这样我们的类就具备这些属性方法的默认实现了。

class User(UserMixin):
    pass

接着声明一个用户加载方法,用于用户登录后访问你的Web应用时告诉Flask应用当前用户是谁

如果返回为空则说明用户不存在或者登录状态无效

@login_manager.user_loader
def load_user(user_id):
    # User应为登录用户对象的集合
    return User.get(user_id)

添加一个验证用户的蓝图方法

# 用户如果没有登录会被导向这个蓝图进行登录
login_manager.login_view = "manages_blueprint.login"
@manages_blueprint.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        # 请求方法为GET返回登录界面的HTML文档
        return render_template('login.html', title='登录')
    if request.method == 'POST':
        # 请求方法为POST则做登录校验
        username = request.json.get('username', None)
        password = request.json.get('password', None)
        # 判断用户密码是否正确,正确则调用 login_user()方法将用户信息添加到会话中
        if username and password:
            user = User()
            login_user(user, remember=False)
            return flask.redirect(next or flask.url_for('index'))
        return '认证失败'

上述提到的所有代码整合

from flask_login import LoginManager
from flask import Flask

class User(UserMixin):
    pass

app = Flask(__name__)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "manages_blueprint.login"

@login_manager.user_loader
def load_user(user_id):
    # User应为登录用户对象的集合
    return User.get(user_id)

@app.route('/'):
    return 'hello world'

@manages_blueprint.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        # 请求方法为GET返回登录界面的HTML文档
        return render_template('login.html', title='登录')
    if request.method == 'POST':
        # 请求方法为POST则做登录校验
        username = request.json.get('username', None)
        password = request.json.get('password', None)
        # 判断用户密码是否正确,正确则调用 login_user()方法将用户信息添加到会话中
        if username and password:
            user = User()
            login_user(user, remember=False)
            return flask.redirect(next or flask.url_for('index'))
        return '认证失败'

可以看出来Flask的登录设计非常简洁,做博客的登录设计也非常简单

在校验用户信息完成后,只需要调用 login_user方法便可以实现所有登录相关的信息

2.2. Google Authenticator

二步验证,可以参考Google的二步验证说明

借助两步验证,您可以通过密码和手机为帐户提供双重保护 -- By Google

简单讲,就是利用一些特定算法(对称算法)实现服务器与客户端在1分钟内计算出来一个相同的6位数,现在许多的网站都已经支持这种登录绑定类

二步验证可以达到一个非常变态的安全级别,除非别人同时拿到你的密码+你的设备验证码,否则不可能登录你的账户

使用Python来完成一个简单的二步验证只需要引入一个 pyotp 的库即可,一个简单的例子如下

首先安装pyotp

pip3 install pyotp

pyotp的标准实现

import pyotp

SECRET_KEY = pyotp.random_base32()
totp = pyotp.totp.TOTP(SECRET_KEY,interval=60)

print('当前验证码->%s' % totp.now())

qrcode_text = totp.provisioning_uri(name='chancel', issuer_name='Chancel\'s blog')

引入pyotp库之后创建一个KEY,使用这个KEY我们就可以生成一个totp对象

调用这个对象的provisioning_uri我们就可以生成一段二维码的文本,使用任意文本转二维码工具后获得一个二维码

使用Google出品的Google 身份验证器扫描并绑定这个二维码,则可以看到一个动态的6位数,这个动态码将与 totp.now()输出保持一致

totp也支持使用 totp.verify('397231') 这种方法来验证验证码是否正确

3. 总结

使用Flask-login + pyotp我们可以非常简单的实现一个相当安全的登录机制

如果是博客类型的程序,甚至只需要在配置文件中存放一个Secert字符串即可,既安全又方便

唯一美中不足的是,登录时都需要校验一次本地验证码,不过可以适当延长登录有效期来环节这个问题


[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]
目录