Flask-login のドキュメントでは、「次の」URLの処理について説明しています。アイデアは次のようです:
/secret
にアクセスします/login
)/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」パラメーターがログインフォームに組み込まれていないため、フォームの処理時に変数として渡されないためだと思います。しかし、正しい方法はこれを解決しますか?
1つの解決策が機能しているようです-<form>
タグを
<form name="loginform" action="{{ url_for('login') }}" method="POST">
に:
<form name="loginform" method="POST">
「action」属性を削除すると、ブラウザー(少なくともWindowsのFirefox 45)は自動的に現在のURLを使用し、?next=%2Fsecret
クエリ文字列を継承して、フォーム処理ハンドラーに正常に送信します。
しかし、「アクション」フォーム属性を省略して、ブラウザに適切なソリューションに入力させていますか?すべてのブラウザとプラットフォームで動作しますか?
またはFlaskまたはFlask-loginはこれを別の方法で処理することを意図していますか?
フォームで別の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))
使用する request.endpoint
独自のURLでも:
{# 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は、きちんとしたソリューションを提供します。次のようなダイナミックリンクを処理する場合
index/<ユーザー>
次に、代わりに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")
誰かがFlask-LoginでFlask_Restfulを使用して「next」URLを通過しようとしている場合に備えて、回避策I 「next」引数をGETメソッドからPOSTメソッドに渡しています。 「next_page」引数は、「なし」に設定されます。 login.htmlのログインボタン
login.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)