Как мне перейти по следующему URL-адресу с помощью Flask и Flask-login?

В документации для 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.

Я предполагаю, что причина в том, что параметр "next", который был доступен во входящем URL-адресе, не включен в форму входа и поэтому не передается как переменная при обработке формы. Но как правильно решить это?

Кажется, работает одно решение - измените тег <form> с

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

to:

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

После удаления атрибута «действие» браузер (по крайней мере, Firefox 45 в Windows) автоматически использует текущий URL-адрес, заставляя его наследовать строку запроса ?next=%2Fsecret, которая успешно отправляет ее обработчику обработки формы.

Но является ли опускание атрибута формы "действие" и позволяет браузеру заполнить его правильным решением? Работает ли он во всех браузерах и на всех платформах?

Или Flask или Flask-login намереваются обработать это по-другому?


person David White    schedule 28.03.2016    source источник


Ответы (4)


Если вам нужно указать другой атрибут 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")
person timakro    schedule 29.03.2016
comment
В форме, которую я использую: next = request.args.get (next). Также в redirect_dest, вероятно, должно быть dest = request.args.get (next) - person 2ni; 27.02.2017
comment
@ 2ni В моем примере форма отображается поверх каждой страницы, если вам нужна отдельная страница входа в систему, вы должны использовать next=request.args.get("next"). Исправлено ваше второе упоминание, спасибо. - person timakro; 27.02.2017
comment
Используйте request.path, если ваши URL-адреса могут содержать переменные. - person Patrick Mutuku; 28.07.2020

@timakro предлагает отличное решение. Если вы хотите обрабатывать динамическую ссылку, например

index / ‹user›

затем вместо этого используйте 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")
person Kurumi Tokisaki    schedule 05.04.2017
comment
В этом подходе полностью отсутствует автоматическая проверка ответа @timakro. В ответе @timakro значения для next по своей сути ограничены зарегистрированными конечными точками приложения, и, таким образом, вредоносные / сторонние перенаправления автоматически предотвращаются по дизайну. Честно говоря, я не вижу преимущества вашего настраиваемого неавторизованного обработчика над поведением по умолчанию во Flask-Login. - person user1402242; 10.05.2018
comment
@ user1402242 спасибо, что указали на проблему уязвимости Open Redirect, проверьте мой обновленный код выше, который решает эту проблему. - person Kurumi Tokisaki; 14.05.2018
comment
@KurumiTokisaki Я не верю, что ваш обновленный код открывает перенаправления. Вы по-прежнему используете аргумент next без изменений - я могу создать вредоносный URL www.example.com/login?next=http%3A%2F%2Fmyevilwebsite.com/phishing - person Wacov; 10.03.2019

На всякий случай, если кто-то пытается пройти по URL-адресу «next» с помощью Flask-Login, но с помощью Flask_Restful, я использовал обходной путь, передавая « следующий "аргумент из метода GET в метод POST. С flask_restful для аргумента «next_page» устанавливается значение «None» после нажатия кнопки входа в 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)
person Santiago    schedule 17.03.2020

Из всех вышеперечисленных ответов я не нашел для себя ничего полезного. Но то, что я нашел, поделюсь с вами ..

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

И это отлично сработало для меня ...

Спасибо ...

person Vishal N    schedule 14.04.2021