web-dev-qa-db-ja.com

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で利用可能だった「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はこれを別の方法で処理することを意図していますか?

15
David White

フォームで別の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")
20
timakro

@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")
2
Kurumi Tokisaki

誰かが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)
1
Santiago