获取上个页面的url

要重定向回上一个页面,最关键的是获取上一个页面的URL。上一个页面的URL一般可以通过两种方式获取:

(1)HTTP referer

HTTP referer(起源为referrer在HTTP规范中的错误拼写)是一个用来记录请求发源地址的HTTP首部字段(HTTP_REFERER),即访问来源。当用户在某个站点单击链接,浏览器向新链接所在的服务器发起请求,请求的数据中包含的HTTP_REFERER字段记录了用户所在的原站点URL。

这个值通常会用来追踪用户,比如记录用户进入程序的外部站点,以此来更有针对性地进行营销。在Flask中,referer的值可以通过请求对象的referer属性获取,即request.referrer。

@app.route('/do_something')
def do_something():
    #return redirect(url_for('hello'))
    return redirect(request.referrer)

但是在很多种情况下,referrer字段会是空值,比如用户在浏览器的地址栏输入URL,或是用户处于保护隐私的考虑使用了防火墙软件或使用浏览器设置自动清除或修改了referrer字段。我们需要添加一个备选项:

return redirect(request.referrer or url_for('hello'))

(2)查询参数

除了自动从referrer获取,另一种更常见的方式是在URL中手动加入包含当前页面URL的查询参数,这个查询参数一般命名为next。

@app.route('/foo')
def foo():
    return '<h1>Foo page</h1><a href="%s">Do Something</a>' % url_for('do_something', next='do_something')

@app.route('/bar')
def bar():
    return '<h1>Bar page</h1><a href="%s">Do Something</a>' % url_for('do_something', next='do_something')

在程序内部只需要使用相对URL,所以这里使用request.full_path获取当前页面的完整路径。在do_something视图中,我们获取这个next值,然后重定向到对应的路径:

return redirect(request.args.get('next'))

用户在浏览器的地址栏直接访问时可以轻易地修改查询参数,为了避免next参数为空的情况,我们也要添加备选项,如果为空就重定向到hello视图:

return redirect(request.args.get('next', url_for('hello')))

为了覆盖更全面,我们可以将这两种方式搭配起来一起使用:首先获取next参数,如果为空就尝试获取referer,如果仍然为空,那么就重定向到默认的hello视图。因为在不同视图执行这部分操作的代码完全相同,我们可以创建一个通用的redirect_back()函数。

def redirect_back(default='hello', **kwargs):
    for target in request.args.get('next'), request.referrer:
        if target:
            return redirect(target)
    return redirect(url_for(default, **kwargs))

通过设置默认值,我们可以在referer和next为空的情况下重定向到默认的视图函数。

@app.route('/do_something_and_redirect')
def do_something():
    return redirect_back()

2.对URL进行安全验证

虽然我们已经实现了重定向回上一个页面的功能,但安全问题不容小觑,鉴于referer和next容易被篡改的特性,如果我们不对这些值进行验证,就会形成开放重定向漏洞。

以URL中的next参数为例,next变量以查询字符串的方式写在URL里,因此任何人都可以发给某个用户一个包含next变量指向任何站点的链接。如果你访问下面的URL

http://localhost:5000/do-something?next=http://helloflask.com

程序会被重定向到http://helloflask.com。也就是说,如果我们不验证next变量指向的URL地址是否属于我们的应用内,那么程序很容易就会被重定向到外部地址。

确保URL安全的关键就是判断URL是否属于程序内部,我们创建了一个URL验证函数is_safe_url(),用来验证next变 量值是否属于程序内部URL。

from urlparse import urlparse, urljoin # Python3需要从urllib.parse导入
from flask import request

def is_safe_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

这个函数接收目标URL作为参数,并通过request.host_url获取程序 内的主机URL,然后使用urljoin()函数将目标URL转换为绝对URL。 接着,分别使用urlparse模块提供的urlparse()函数解析两个URL,最 后对目标URL的URL模式和主机地址进行验证,确保只有属于程序内部 的URL才会被返回。在执行重定向回上一个页面的redirect_back()函 数中,我们使用is_safe_url()验证next和referer的值

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

0 Comments latest

No comments.