首页 > 解决方案 > 在不丢失会话的情况下从用户那里获取双因素身份验证代码(Flask 服务器)

问题描述

我有一个在命令行中运行良好的第三方网站的 API 请求(来自https://github.com/haochi/personalcapital):

pc = PersonalCapital()

try:
    pc.login(email, password)
except RequireTwoFactorException:
    pc.two_factor_challenge(TwoFactorVerificationModeEnum.SMS)
    pc.two_factor_authenticate(TwoFactorVerificationModeEnum.SMS, input('code: '))
    pc.authenticate_password(password)

accounts_response = pc.fetch('/newaccount/getAccounts')
accounts = accounts_response.json()['spData']

当我在命令行中运行上述命令时,我会按预期返回一个 JSON。

但是,我想在 Flask 服务器上的 Web 应用程序中使用它。所以,我需要删除input('code: ')用于短信确认的命令行。我想我会通过“POST”使用表单来获取用户输入。

但是,如果我redirect()render_template()将用户发送到表单,它会中断我的 API 会话,并且我会从 API 返回“会话未通过身份验证”响应。

服务器逻辑。有问题的路线是/update(电子邮件和密码优先)和/authenticate(短信确认表):

@app.route("/update", methods=["GET", "POST"])
@login_required
def update():

    # Via post:
    if request.method == "POST":

        # Ensure userentered email
        if not request.form.get("pc_email"):
            return apology("Please enter username", 400)

        # Ensure user entered password
        elif not request.form.get("pc_password"):
            return apology("Please enter password", 400)

        # Save email & password
        email = request.form.get("pc_email")
        password = request.form.get("pc_password")

        # Try to log in
        try:
            pc.login(email, password)

        # If 2-factor is required, send sms & redirect
        except RequireTwoFactorException:
            pc.two_factor_challenge(TwoFactorVerificationModeEnum.SMS)
            return redirect("/authenticate")

        # Get data:
        else:
            # Get accounts data
            accounts_response = pc.fetch('/newaccount/getAccounts')
            accounts = accounts_response.json()['spData']

            # TODO - update database w/ data from accounts & transactions

            return redirect("/")


@app.route("/authenticate", methods=["GET","POST"])
@login_required
def authenticate():

        # Via POST:
        if request.method == "POST":

            # SMS authentication
            pc.two_factor_authenticate(TwoFactorVerificationModeEnum.SMS, \
                request.form.get(sms))
            pc.authenticate_password(password)

            # Get accounts data
            accounts_response = pc.fetch('/newaccount/getAccounts')
            accounts = accounts_response.json()

            # TODO - update database w/ data from accounts & transactions

            # Redirect to "/"
            return render_template("test.html", accounts=accounts)

        # Via GET:
        else:
            return render_template("authenticate.html")

项目的源代码在这里:https ://github.com/bennett39/budget/blob/stackoverflow/01/application.py

如何在等待用户使用 SMS 代码响应时阻止代码执行?或者,我应该以不同的方式解决这个问题吗?

标签: pythonpython-3.xflaskservertwo-factor-authentication

解决方案


您遇到的错误实际上是由于您尝试使用全局变量在请求之间保持状态的方式。您最初将密码定义为模块级变量,然后password = request.form.get("pc_password")在更新函数中进行设置。由于 pythons 关于全局和局部变量的规则https://docs.python.org/3/faq/programming.html#id9这将创建一个包含密码值的新局部变量,并且模块级变量保持不变。然后,您在身份验证函数中访问原始全局密码变量,该函数失败,因为此密码变量仍设置为其原始值 ''。快速解决方法是添加global password在更新功能开始时,但这忽略了这种持久状态方法的其他问题。您的所有全局变量都在使用您网站的每个人之间共享,因此如果有多个人登录,那么他们都将登录到同一个个人资本账户。最好使用会话对象来保存这些数据,因为每个用户将只能访问他们自己的会话对象,并且不会有人们访问彼此帐户的风险。您对 PersonalCapital 对象的使用会使事情稍微复杂化,因为它使用实例变量来保持状态,这适用于命令行应用程序,但不适用于 Web 应用程序。然而,它是一个非常简单的对象,只有 2 个实例变量。


推荐阅读