簡體   English   中英

Flask-Login @login-required 裝飾器在會話過期后不會重定向到登錄頁面

[英]Flask-Login @login-required decorator not redirecting to login page after session has expired

更新

下面的 Flask 重定向(響應代碼 302)似乎作為對_dash-update-component請求的響應傳遞:

b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>Redirecting...</title>\n<h1>Redirecting...</h1>\n<p>You should be redirected automatically to target URL: <a href="/login">/login</a>.  If not click the link.'

這解釋了下面 dash_renderer 拋出的 SyntaxError,所以這導致我在server.py添加以下內容:

@server.after_request
def check_response(response):

    redirecting = 'Redirecting...' in response.get_data().decode('utf-8')
    dash_response = request.path=='/_dash-update-component'

    return make_response('', 204) if redirecting and dash_response else response

現在我可以通過向 dash 組件返回“204 No-Content”響應來模擬類似 Dash 的PreventUpdate ,但是我沒有收到重定向回登錄頁面的額外請求。 注釋掉after_request函數,然后跟蹤before_request看到的請求,實際上表明調用了login()路由並返回了render_template('login.html') ,但它根本沒有在瀏覽器中呈現......

下面的原始帖子

過去幾天,我大部分時間都在嘗試修改我們的登錄程序,以增加一些生活質量的更新和修改。 出於這個問題的目的,我有興趣在主 Dash 應用程序中一段時間​​不活動后注銷我們的用戶。

我的方法是為我們的登錄頁面注冊路由,然后將/dashapp的 Flask 路由指向app.index()返回的響應,其中app指向 Dash 應用程序。 一旦他們登錄到 Dash 應用程序,我就有了一個before_request裝飾器,它將更新會話修改屬性和會話過期時間(用於測試目的是 5 秒)。 我還申請了@login_required裝飾這個調用的功能,使login_manager.unauthorized_handler如果觸發,當用戶不再驗證調用before_request裝飾。 我認為我的邏輯在這里是合理的,但我仍然遇到問題,我將在下面描述。

我能夠登錄我的用戶並將他們重定向到位於/dashapp的主 Dash 應用程序,並且我可以/dashapp使用該應用程序。 現在,當我等待 5 秒鍾以允許會話過期時,單擊 Dash 應用程序中觸發 dash 回調的組件會在控制台中產生以下錯誤:

dash_renderer.v1_7_0m1602118443.min.js:20 SyntaxError: Unexpected token < in JSON at position 0

我知道某些函數需要 JSON 響應,並且顯然收到了 HTML 響應,但我無法確定那是什么。 它還阻止我重定向回登錄頁面,當用戶不再通過身份驗證並觸發before_request裝飾器時,我希望調用該before_request

我的代碼結構如下(不是那個config.py只是我的 SQL 連接):

應用程序.py

from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html

from server import app, server as application, User, login_manager
from flask_login import logout_user, current_user, login_user, login_required
from flask import session, redirect, render_template, url_for, request
from views import main

app.layout = html.Div([

    dcc.Location(id='url', refresh=False),

    html.Div(id='page-content')

])

@application.route('/login')
def login():

    return render_template('login.html')

@application.route('/login', methods=['POST'])
def login_post():
    
    if current_user.is_authenticated:

        return redirect('/dashapp')

    user = User.query.filter_by(username=request.form['username']).first()

    #Check if user exists
    if user:

        #Check if password is correct
        if user.password==request.form['password']:

            login_user(user, remember=False)
            return redirect('/dashapp')

@login_manager.unauthorized_handler
def unauthorized():
    
    if request.path!='/login':
        return redirect('/login')

@application.route('/logout')
@login_required
def logout():

    logout_user()
    return redirect('/login')

@application.route('/dashapp')
@login_required
def main_page():

    return app.index()

@app.callback(
    Output('page-content', 'children'),
    [Input('url', 'pathname')])
def display_page(pathname):

    if current_user.is_authenticated:
        content = main.get_layout()
    else:
        content = dcc.Location(pathname='/login', id='redirect-id')

    return content

if __name__ == '__main__':
    app.run_server()

視圖/登錄.html

<html>
  <head>
    <title>Flask Intro - login page</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="static/bootstrap.min.css" rel="stylesheet" media="screen">
  </head>
  <body>
    <div class="container">
      <h1>Please login</h1>
      <br>
      <form action="" method="post">
        <input type="text" placeholder="Username" name="username" value="{{
          request.form.username }}">
         <input type="password" placeholder="Password" name="password" value="{{
          request.form.password }}">
        <input class="btn btn-default" type="submit" value="Login">
      </form>
      {% if error %}
        <p class="error"><strong>Error:</strong> {{ error }}
      {% endif %}
    </div>
  </body>
</html>

服務器.py

import dash, os, datetime
from flask_login import LoginManager, UserMixin, current_user, login_required
from config import connection_string
import dash_bootstrap_components as dbc
from credentials import db, User as base
from flask import session, g, redirect, url_for, request, flash, render_template
import flask

external_stylesheets = [dbc.themes.BOOTSTRAP]

app_flask = flask.Flask(__name__)

app = dash.Dash(
    __name__,
    server=app_flask,
    external_stylesheets=external_stylesheets,
    update_title=None,
    url_base_pathname='/'
)

app.title = 'Login Testing Interface'
server = app_flask
app.config.suppress_callback_exceptions = True

server.config.update(
    SECRET_KEY=os.urandom(12),
    SQLALCHEMY_DATABASE_URI=connection_string,
    SQLALCHEMY_TRACK_MODIFICATIONS=False
)

db.init_app(server)

#Setup the LoginManager for the server
login_manager = LoginManager()
login_manager.init_app(server)
login_manager.login_view = 'login'

#Create User class with UserMixin
class User(UserMixin, base):

    def get_id(self):

        return self.user_id

#Reload the user object
@login_manager.user_loader
def load_user(user_id):

    return User.query.get(user_id)

@server.before_request
@login_required
def check_authentication():

    session.permanent = True
    server.permanent_session_lifetime = datetime.timedelta(seconds=5)
    session.modified = True
    g.user = current_user

主文件

from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc

from flask_login import current_user

from server import app, server

def get_layout():

    return html.Div([

        dcc.Location(id='url-main', refresh=False),

        dbc.Button('Click me', id='test-click', n_clicks_timestamp=0),

        html.Div(id='testing')

    ])

@app.callback(
    Output('testing', 'children'),
    [Input('test-click', 'n_clicks_timestamp')])
def update_test_div(clicks):

    return f'Last clicked: {clicks}'

憑證.py

from flask_sqlalchemy import SQLAlchemy
from config import engine

db = SQLAlchemy()

db.Model.metadata.reflect(engine)

class User(db.Model):

    __table__ = db.Model.metadata.tables['my_sql_table_with_user_details']

在此先感謝您的任何指導!

我建議將您的登錄和登錄后路由寫為單個功能

@app.route('/login', methods=['POST','GET'])
def login():
    if current_user.is_authenticated :
        return redirect('/')
    if request.method == 'POST':
        user_name = request.form.get('username')
        password_entered =request.form.get('password')
        
        present_user=User.query.filter_by(username=user_name).first()
        if present_user.password == password_entered:
            login_user(present_user)
            next_page= request.args.get('next')
            print(next_page)
            return redirect(next_page)  if next_page else redirect('/')
        else:
            flash('Incorrect  Password',category='danger')
            return render_template('user_login.html')
    else:
        return render_template('user_login.html')

如果您從login_required函數重定向到登錄頁面,您可能會注意到頂部的 /url 鏈接說

/login?next=%2FpathofFunction

當我們寫

next_page= request.args.get('next')

我們在 ?next 之后得到剩余的 URL,然后將用戶重定向到它來自的位置

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM