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

问题描述 投票:0回答:7

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

    用户转到
  1. /secret
    
    
  2. 用户被重定向到登录页面(例如
  3. /login
  4. 成功登录后,用户将被重定向回
  5. /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 上可用的“next”参数未合并到登录表单中,因此在处理表单时不会作为变量传递。但是正确的方法是什么解决这个问题?

一个解决方案似乎有效 - 将

<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 是否打算以不同的方式处理这个问题?

python forms flask flask-login
7个回答
40
投票
如果您需要在表单中指定不同的

action 属性,则不能使用 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))

也在您自己的 URL 中使用

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")
    

4
投票
在上述所有答案中,我没有发现任何对我有帮助的内容。 但我发现了什么,我会与你分享..

login.html

中只需添加以下代码

<input type="hidden" name="next" value="{{ request.args.get('next', '') }}" />
并且登录定义更改如下

@app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") next_url = request.form.get("next") if username in users and users[username][1] == password: session["username"] = username if next_url: return redirect(next_url) return redirect(url_for("profile")) return render_template("login.html")
这对我来说非常有效......

谢谢你...


3
投票
@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")
    

2
投票
以防万一有人尝试使用 Flask-Login 但使用

Flask_Restful 传递“next”URL,我一直在使用的解决方法是将“next”参数从 GET 方法传递到 POST方法。使用flask_restful,单击login.html中的登录按钮后,“next_page”参数设置为“None

登录.html

... <!-- next_page came from "render_template(next_page=request.args.get('next') ...)" in the get() function --> <!-- And also from render_template('login.html', next_page=next_page) in the post() function --> <form action="{{ url_for('login', next=next_page) }}" method="POST" > <div class="field"> <div class="control"> <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus=""> </div> </div> <div class="field"> <div class="control"> <input class="input is-large" type="password" name="password" placeholder="Your Password"> </div> </div> <div class="field"> <label class="checkbox"> <input type="checkbox" name="remember_me"> Remember me </label> </div> <button class="button is-block is-info is-large is-fullwidth">Login</button> </form> ...

auth.py

class Login(Resource): def get(self): if current_user.is_authenticated: return redirect(url_for('home')) headers = {'Content-Type': 'text/html'} #1 --> # Here I pass the "next_page" to login.html return make_response(render_template('login.html', next_page=request.args.get('next')), 200, headers) def post(self): #2 --> # After the user clicks the login button, I retrieve the next_page saved in the GET method next_page = request.args.get('next') if current_user.is_authenticated: return redirect(url_for('home')) # Check if account exists in the db existing_account = Account.objects(email=request.form.get('email')).first() # Only redirects when the URL is relative, which ensures that the redirect # stays within the same site as the application. if existing_account: if existing_account.check_password(request.form.get('password')): login_user(existing_account, remember=request.form.get('remember_me')) if not next_page or url_parse(next_page).netloc != '': return redirect(url_for('home')) #3 --> # Here I use the retrieved next_page argument return redirect(url_for(next_page)) # Account not recognized flash('Please check your login details and try again.') headers = {'Content-Type': 'text/html'} #4 --> # I also pass the "next_page" here in case the user-provided data is wrong return make_response(render_template('login.html', next_page=next_page), 200, headers)
    

1
投票
这对我有用,这是对 @timakro 和 @Kurumi Tokisaki 在之前的帖子中提供的内容的补充。

@login_manager.unauthorized_handler def handle_needs_login(): return redirect(url_for('login', next_page=request.endpoint)) @app.route('/login/<next_page>', methods=['GET', 'POST']) def login(next_page): if current_user.is_authenticated: return redirect(url_for(next_page)) if is_a_successfull_login(): redirect(url_for(next_page))
    

1
投票
简单示例

@app.route('/login', methods=['GET', 'POST']) def login(): // do your stuff next_page = request.form.get('next_page') # next_page either be 'http://localhost:5000/None or 'http://localhost:5000/some_endpoints return redirect(next_page) if 'None' not in next_page else redirect(url_for('default_action'))
注意


Flask文档中提到的 不要忘记在
next_url

 中传递 
html

<input type="hidden" value="{{ request.args.get('next') }}" name="next_page" id="next_page"/>
    

0
投票
我知道这是一个较旧的问题,但标记为正确的答案对我来说并不完全有效。

使用 Flask-Login 在我的网站上设置 API 后,我的“下一个”参数就停止工作。这是我为解决问题所做的事情。

def setup_login_manager(app, login_manager): login_manager.init_app(app) login_manager.login_message_category = "warning" @login_manager.unauthorized_handler def setup_unauthorized(): if "api" in request.full_path: abort(401) else: next_path = request.path if next_path and next_path != "/": return redirect(url_for("users.login", next=url_for(request.endpoint))) else: return redirect(url_for("users.login"))
    
© www.soinside.com 2019 - 2024. All rights reserved.