如何使用 Flask 和 Flask-login 传递“下一个”URL?

新手上路,请多包涵

Flask-login 的文档讨论了如何处理“下一个”URL。这个想法似乎是:

  1. 用户转到 /secret
  2. 用户被重定向到登录页面(例如 /login
  3. 登录成功后,用户被重定向回 /secret

我发现的唯一使用 Flask-login 的半完整示例是 https://gist.github.com/bkdinoop/6698956 。它很有用,但由于它不包含 HTML 模板文件,我想看看是否可以重新创建它们作为自我训练练习。

这是 /secret/login 部分的简化版本:

 @app.route("/secret")
@fresh_login_required
def secret():
    return render_template("secret.html")

@app.route("/login", methods=["GET", "POST"])
def login():
    <...login-checking code omitted...>
    if user_is_logged_in:
        flash("Logged in!")
        return redirect(request.args.get("next") or url_for("index"))
    else:
        flash("Sorry, but you could not log in.")
        return render_template("login.html")

这是 login.html

 <form name="loginform" action="{{ url_for('login') }}" method="POST">
Username: <input type="text" name="username" size="30" /><br />
Password: <input type="password" name="password" size="30" /><br />
<input type="submit" value="Login" /><br />

现在,当用户访问 /secret 时,他会被重定向到 /login?next=%2Fsecret 。到目前为止,还不错——“下一个”参数在查询字符串中。

但是,当用户提交登录表单时,他将被重定向回索引页面,而不是返回到 /secret URL。

我猜原因是因为传入 URL 上可用的“下一个”参数没有合并到登录表单中,因此在处理表单时没有作为变量传递。但是正确的方法是什么解决这个?

一种解决方案似乎有效 - 更改 <form> 标签

<form name="loginform" action="{{ url_for('login') }}" method="POST">

到:

 <form name="loginform" method="POST">

删除“action”属性后,浏览器(至少是 Windows 上的 Firefox 45)自动使用当前 URL,使其继承 ?next=%2Fsecret 查询字符串,成功将其发送到表单处理程序。

但是省略“action”表单属性并让浏览器填写正确的解决方案吗?它适用于所有浏览器和平台吗?

或者 Flask 或 Flask-login 是否打算以不同的方式处理这个问题?

原文由 David White 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 418
2 个回答

如果您需要在表单中指定不同的 操作 属性,则不能使用 Flask-Login 提供的下一个参数。无论如何,我建议将端点而不是 url 放入 url 参数中,因为它更容易验证。这是我正在处理的应用程序的一些代码,也许这可以帮助你。

覆盖 Flask-Login 的未授权处理程序以在下一个参数中使用端点而不是 url:

 @login_manager.unauthorized_handler
def handle_needs_login():
    flash("You have to be logged in to access this page.")
    return redirect(url_for('account.login', next=request.endpoint))

在您自己的网址中也使用 request.endpoint

 {# login form #}
<form action="{{ url_for('account.login', next=request.endpoint) }}" method="post">
...
</form>

如果存在且有效,则重定向到下一个参数中的端点,否则重定向到回退。

 def redirect_dest(fallback):
    dest = request.args.get('next')
    try:
        dest_url = url_for(dest)
    except:
        return redirect(fallback)
    return redirect(dest_url)

@app.route("/login", methods=["GET", "POST"])
def login():
    ...
    if user_is_logged_in:
        flash("Logged in!")
        return redirect_dest(fallback=url_for('general.index'))
    else:
        flash("Sorry, but you could not log in.")
        return render_template("login.html")

原文由 timakro 发布,翻译遵循 CC BY-SA 3.0 许可协议

@timakro 提供了一个简洁的解决方案。如果你想处理一个动态链接,比如

索引/<用户>

然后使用 url_for(request.endpoint,**request.view_args) 因为 request.endpoint 将不包含动态可用信息:

  @login_manager.unauthorized_handler
 def handle_needs_login():
     flash("You have to be logged in to access this page.")
     #instead of using request.path to prevent Open Redirect Vulnerability
     next=url_for(request.endpoint,**request.view_args)
     return redirect(url_for('account.login', next=next))

以下代码应更改为:

 def redirect_dest(home):
    dest_url = request.args.get('next')
    if not dest_url:
        dest_url = url_for(home)
    return redirect(dest_url)

@app.route("/login", methods=["GET", "POST"])
def login():
    ...
    if user_is_logged_in:
        flash("Logged in!")
        return redirect_dest(home=anyViewFunctionYouWantToSendUser)
    else:
        flash("Sorry, but you could not log in.")
        return render_template("login.html")

原文由 Kurumi Tokisaki 发布,翻译遵循 CC BY-SA 4.0 许可协议

推荐问题