重定向回上一个页面失败


#1

安全重定向的代码:

def is_save_url(target):
    ref_url = urlparse(request.host_url)
    test_url = urlparse(urljoin(request.host_url, target))
    return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc


def safe_redirect(default='index', **kwargs):
    for target in request.args.get('next'), request.referrer:
        if not target:
            continue
        if is_save_url(target):
            return redirect(target)
    return redirect(default, **kwargs)

视图函数:

@app.route('/login', methods=['POST', 'GET'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = LoginForm()
    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        remeber = form.remember.data
        user = User.query.filter_by(username=username).first() or User.query.filter_by(email=username).first()
        if user and user.validate_password(password):
            login_user(user, remeber)
            flash('欢迎回来{}!'.format(user.username), 'success')
            return safe_redirect()
        else:
            flash('无效的用户或者密码错误!', 'danger')
    return render_template('login.html', form=form)

@app.route('/register', methods=['POST', 'GET'])
def register():
    if current_user.is_authenticated:
        return safe_redirect()
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(
            username=form.username.data,
            email=form.email.data,
            password=form.password.data
        )
        db.session.add(user)
        db.session.commit()
        flash('恭喜你,帐号注册成功!', 'success')
        return safe_redirect('login')
    return render_template('register.html', form=form)

问题:

  • login 登录帐号后无限重定向到 login, 而不是想要的跳转到 index

  • register 注册成功后没有跳转到 login

我想了下,是不是因为 is_safe_url(’login’) 是 True, 所以 safe_redirect() 又会跳转到 login! 如果是这样的话,那这种情况应该如何处理,或者说只能使用 redirecit(url__for(‘index’)) 这种方式??


#2

redirect函数的第一个参数应该为目标url值,所以应该改成这样

def safe_redirect(default='index', **kwargs):
    for target in request.args.get('next'), request.referrer:
        if not target:
            continue
        if is_save_url(target):
            return redirect(url_for(target))
    return redirect(  url_for(default)  , **kwargs)

url_for()函数的导入如下

from flask import url_for

这个函数的作用是接受一个端点值,返回该端点所对应的url


#4

请参考这个帖子了解如何标记解决方案。


#5

我试过了 OQJ 的方案,似乎还是有问题!
我这里通过点击主页的登录按钮登录之后,我 print request.args.get(‘next’) 或者 reqruest.referrer
或者 target
输出都是 ([log] here: 的内容)

[Log] here: http://localhost:5000/login
127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 -
127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 -
[Log] here: None http://localhost:5000/login
[Log] here: http://localhost:5000/login
127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 -
[Log] here: None http://localhost:5000/login
[Log] here: http://localhost:5000/login
127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 -
[Log] here: None http://localhost:5000/login
[Log] here: http://localhost:5000/login
[Log] here: None http://localhost:5000/login

也就是说,is_safe_url(target) 这里存在无限重定向!
如果

return redirecit(target)

改成

return redirect(url_for(target))

明显是错误的,因为 target 不是一个端点而是 url, 会报错!


#6

我那里的确错了:sweat_smile:,从request得到的next值和referrer值都是url,所以不用url_for函数处理,直接redirect即可

def safe_redirect(default='index', **kwargs):
    for target in request.args.get('next'), request.referrer:
        if not target:
            continue
        if is_save_url(target):
            return redirect(target)
    return redirect(  url_for(default)  , **kwargs)

#7

你仔细看下我的代码和输出!
书本上和源代码都是

redirect(targe)

但是会无限重定向到 login
具体的内容我打印出来了
如果 is_safe_url(target) 是 True, 那必然无限重定向到 login
我的问题就是这个!不知道怎么回事


#8

如果可以的话,请把前端的那部分代码也发上来,或者你也可以把项目上传到github上,把地址私信给我,有更多的信息,才能更有效的解决问题:grinning:


#9

主页和登录页面


{% extends 'base.html' %}

{% block title %}
    主页
{% endblock %}

{% block content %}
    <div class="col-8 offset-2">
        {{ super() }}
        <h3 class="text-primary text-center">瓜哥留言板</h3>
        <p class="text-info text-center">
            <small>Version 1.0.0</small>
        </p>
        {% if current_user.is_authenticated: %}
            <form method="post">
                {{ form.csrf_token }}
                {% for message in form.body.errors %}
                    <small class="text-danger">{{ message }}</small>
                {% endfor %}

                {{ form.body(class='form-control', placeholder='你想留下点什么内容呢?') }}

                {{ form.submit(class='btn btn-info btn-block') }}
            </form>
            <hr>
            <br>
        {% endif %}
        <h5>{{ messages|length }} 条留言
            <small class="float-right">
                <a href="#bottom" title="Go Bottom">&darr;</a>
            </small>
        </h5>
        <div class="list-group">
            {% for message in messages %}
                <a class="list-group-item list-group-item-action flex-column">
                    <div class="d-flex w-100 justify-content-between">
                        <h5 class="mb-1 text-success">{{ message.user.username }}
                            <small class="text-muted"> #{{ loop.revindex }}</small>
                        </h5>
                        {% if message.edit_time %}
                        <small>修改过{{ message.edit_time }}次</small>
                        {% endif %}
                        <small data-toggle="tooltip" data-placement="top"
                               data-timestamp="{{ message.timestamp.strftime('%Y-%m-%dT%H:%M:%SZ') }}"
                               data-delay="500">
                            {{ moment(message.timestamp).fromNow(refresh=True) }}
                        </small>
                    </div>
                    <p class="mb-1">{{ message.body }}</p>
                </a>

            {% endfor %}
        </div>

    </div>
{% endblock %}


{% extends 'base.html' %}

{% block title %}
    登录
{% endblock %}

{% block content %}
    <div class="col-4 offset-4">
        {{ super() }}
        <h3 class="text-center"><i class="text-info fa fa-user-circle-o fa-5x"></i></h3>
        <p class="text-center">
            <small><a href="{{ url_for('register') }}">没有帐号?</a></small>
        </p>
        <form method="post">
            {{ form.csrf_token }}
            {% for message in form.username.errors %}
                <small class="text-danger">{{ message }}</small>
            {% endfor %}
            <div class="input-group mb-3">
                <div class="input-group-prepend">
                    <span class="input-group-text">邮箱:</span>
                </div>
                {{ form.username(class='form-control', placeholder='也可以使用昵称登录') }}
            </div>
            {% for message in form.password.errors %}
                <small class="text-danger">{{ message }}</small>
            {% endfor %}
            <div class="input-group mb-3">
                <div class="input-group-prepend">
                    <span class="input-group-text">密码:</span>
                </div>
                {{ form.password(class='form-control', placeholder='输入你的密码') }}
            </div>
            <div class="text-right">
                {{ form.remember(class='form-check-input') }}
                {{ form.remember.label }}
            </div>
            {{ form.submit(class='btn btn-info btn-block') }}
        </form>
    </div>
{% endblock %}

视图



@app.route('/', methods=['POST', 'GET'])
def index():
    messages = Message.query.order_by(Message.timestamp.desc()).all()
    if current_user.is_authenticated:
        form = MessageForm()
        if form.validate_on_submit():
            message = Message(
                body=form.body.data,
                user=current_user
            )    
            db.session.add(message)        
            db.session.commit()
            flash('你的留言发布成功!', 'success')
            return safe_redirect()
        return render_template('index.html', form=form, messages=messages)
    else:
        return render_template('index.html', messages=messages)

@app.route('/login', methods=['POST', 'GET'])
def login():
    if current_user.is_authenticated:
        return safe_redirect()
    form = LoginForm()
    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        remeber = form.remember.data
        user = User.query.filter_by(username=username).first() or User.query.filter_by(email=username).first()
        if user and user.validate_password(password):
            login_user(user, remeber)
            flash('欢迎回来{}!'.format(user.username), 'success')
            return safe_redirect()
        else:
            flash('无效的用户或者密码错误!', 'danger')
    return render_template('login.html', form=form)

表单:


class LoginForm(FlaskForm):
    username = StringField('昵称[邮箱]:', validators=[DataRequired('注册邮箱地址不能为空'), Length(1, 128)])
    password = PasswordField('密码:', validators=[DataRequired('密码不能为空')])
    remember = BooleanField('保持登录')
    submit = SubmitField('登录')

#10
def login():
    if current_user.is_authenticated:
        return safe_redirect()

改为

def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))

#11

如果直接用 redirect(url_for())
那还又必要写个 is_safe_url 么?

我的问题是登录之后无限重定向。。。。。。
不是当前已经登录的情况下去请求 login


#12

我是这样想的,如果当前用户已经登录了,他又再一次请求登录视图,那么就把他重定向到主视图,正常情况下,已登录用户已经不再需要点击功能,页面上也不应该显示登录按钮给登录用户。你可以试试这样可以解决无限重定向没有,我还没想到其他办法,我也不太明白为什么不能重定向到正确的页面,求助


#13

在程序埋点,看看有没有按预期打印,例如:

@app.route('/login', methods=['POST', 'GET'])
def login():
    if current_user.is_authenticated:
        print('1')
        return safe_redirect()
    form = LoginForm()
    if form.validate_on_submit():
        print('2')
        username = form.username.data
        password = form.password.data
        remeber = form.remember.data
        user = User.query.filter_by(username=username).first() or User.query.filter_by(email=username).first()
        if user and user.validate_password(password):
            print('3')
            login_user(user, remeber)
            flash('欢迎回来{}!'.format(user.username), 'success')
            return safe_redirect()
        else:
            print('4')
            flash('无效的用户或者密码错误!', 'danger')
    print('5')
    return render_template('login.html', form=form)

def safe_redirect 函数也尽量多打印出自己想看的信息,比如跳转的连接


#14

大多数情况下都是粗心大意,或者条件分支没有处理好。


#15

我这里已经输出了 request.args.get(‘next’) 和 request.referrer的信息, 就是前面带 [log] here: 字样的内容,全部都是 login


[Log] here: http://localhost:5000/login 127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 - 127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 - [Log] here: None http://localhost:5000/login [Log] here: http://localhost:5000/login 127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 - [Log] here: None http://localhost:5000/login [Log] here: http://localhost:5000/login 127.0.0.1 - - [09/Mar/2019 14:53:42] "GET /login HTTP/1.1" 302 - [Log] here: None http://localhost:5000/login [Log] here: http://localhost:5000/login [Log] here: None http://localhost:5000/login


#16

继续往前追溯。

按道理,正常登录会进入到这里吧

        if user and user.validate_password(password):
            login_user(user, remeber)
            flash('欢迎回来{}!'.format(user.username), 'success')
            print('test')
            return safe_redirect()

然后进入到这里:

def safe_redirect(default='index', **kwargs):
    for target in request.args.get('next'), request.referrer:
        print(target)
        if not target:
            continue
        if is_save_url(target):
            return redirect(target)
    print(default)
    return redirect(default, **kwargs)

print()打印出来看看。


debug这事不能总是指望别人。


#17

这些地方都没有问题,我试过了!
主要是在

if current_user.is_authenticated:
        # return redirect(url_for('index') 是正确的

        return safe_redirect() # 就不行了!

我找到问题就是出在这里,改过来就正确了!
非常感谢各位的耐心帮助!
我是个新手,问题总是又多又可笑…
浪费大家时间,非常抱歉!