第二章 Flask day2

本文介绍Flask框架中Cookie和Session的使用方法,包括用户登录、注销功能的实现,以及上下文全局变量current_app和g的应用。同时探讨了HTTP进阶实践,如重定向回上一个页面和对URL进行安全验证。

Cookie

Cookie作为存储在浏览器中的信息,如果是明文状态,则可以随意修改,于是为了进行加密。Flask提供了session对象。

Session

在编程中,session指用户会话(user session),又称为对话 (dialogue),即服务器和客户端/浏览器之间或桌面程序和用户之间建立的交互活动。在Flask中,session对象用来加密Cookie。默认情况下,它会把数据存储在浏览器上一个名为session的cookie里。
session通过密钥对数据进行签名以加密数据,因此,我们得先设置一个密钥。这里的密钥就是一个具有一定复杂度和随机性的字符串,比 如“Drmhze6EPcv0fN_81Bj-nA”。
程序的密钥可以通过Flask.secret_key属性或配置变量SECRET_KEY设置,比如:

app.secret_key = 'secret string'

session模拟用户登入功能

app.secret_key = 'Drmhze6EPcv0fN_81Bj-nA'
@app.route('/login')
def login():
    #写入session
    session['logged_in'] = True
    return redirect('/hello')

session对象可以像字典一样操作,我们向session中添加一个logged-in 的cookie,将它的值设为True,表示用户已认证。
当我们使用session对象添加cookie时,数据会使用程序的密钥对其进行签名,加密后的数据存储在一块名为session的cookie里,此时根据下图可以看到,除非知道密钥,用户是无法看出来数据具体的值的,于是便实现了加密。
在这里插入图片描述

根据cookie和session内容不同而返回不同内容

值得注意的是:session中的数据可以像字典一样通过键读取,或是使用get()方法。这里我们只是判断session中是否包含logged_in键,如果有则表示用户已经登录。

app.secret_key = 'Drmhze6EPcv0fN_81Bj-nA'
@app.route('/')
@app.route('/hello')
def hello():
    #开始查找cookie
    name = request.args.get('name')
    #没有名字的情况下 name直接赋为Human
    if name is None:
        name = request.cookies.get('name', 'Human')
        response = '<h1>Hello, %s!</h1>' % name
    #如果在之前的login界面logged_in成功了的话
    if 'logged_in' in session:
        response += '[Authenticated]'
    else:
        response += '[Not Authenticated]'
    return response

在这里插入图片描述

登入账户

from flask import session, abort 
@app.route('/admin') 
def admin(): 
	if 'logged_in' not in session: 
		abort(403)
	return 'Welcome to admin page.'

这段代码的意思是通过判断logged_in是否在session中,可以实现:如果用户已经认证,会返回一行提示文字,否则会返回403错误响应。

登出用户

登出用户的logout视图也非常简单,登出账户对应的实际操作其实就是把代表用户认证的logged-in cookie删除,这通过session对象的pop方法实现

from flask import session 
@app.route('/logout') 
def logout(): 
	if 'logged_in' in session: 
		session.pop('logged_in') 
	return redirect(url_for('hello'))

注意

尽管session对象会对Cookie进行签名并加密,但这种方式仅能够确保session的内容不会被篡改,加密后的数据借助工具仍然可以轻易读取 (即使不知道密钥)。
因此,绝对不能在session中存储敏感信息,比如用户密码

Flask上下文

每一个视图函数都需要上下文信息,在前面我们学习过Flask如何将请求报文封装在request对象中。
那么如果我们要在视图函数中使用它,就得把它作为参数传入视图函数,就像我们接收URL变量一样。
但是这样一来就会导致大量的重复,而且增加了视图函数的复杂度。
而在之前,程序直接从Flask里import一个全局request对象,就能够在各个视图函数里直接调用request的属性来获取数据。这是因为Flask的特殊特性。
为了方便获取这两种上下文环境中存储的信息,Flask提供了四个上下文全局变量。
在这里插入图片描述

current_app

在不同的视图函数中,request对象都表示和视图函数对应的请求,也就是当前请求(current request)。
而程序也会有多个程序实例的情况,为了能获取对应的程序实例,而不是固定的某一个程序实例,我们就需要使用current_app变量,后面会详细介绍。

g

g存储在程序上下文中,而程序上下文会随着每一个请求的进入而激活,随着每一个请求的处理完毕而销毁,所以每次请求都会重设这个值。
我们通常会使用它结合请求Hook来保存每个请求处理前所需要 全局变量,比如当前登入的用户对象,数据库连接等。
在前面的示例中,我们在hello视图中从查询字符串获取name的值,如果每一个视图都需要这个值,那么就要在每个视图重复这行代码。
为了简化这种操作,我们可以借助g将这个操作移动到before_request处理函数中执行,然后保存到g的任意属性上:

from flask import g 
@app.before_request 
	def get_name(): 
		g.name = request.args.get('name')

设置这个函数后,在其他视图中可以直接使用g.name获取对应的值。
另外,g也支持使用类似字典的get()、pop()以及setdefault() 方法进行操作。

上下文钩子

Flask也为上下文提供了一个teardown_appcontext钩子,使用它注册的回调函数会在程序上下文被销毁时调用,而且通常也会在请求上下文被销毁时调用。
比如,你需要在每个请求处理结束后销毁数据库连接的话:

@app.teardown_appcontext
def teardown_db(exception): 
	...
	db.close()

HTTP进阶实践(重定向回上一个页面)

在复杂的应用场景下,我们需要在用户访问某个URL后重定向到上一个页面。最常见的情况是,用户单击某个需要登录才能访问的链接,这时程序会重定向到登录页面,当用户登录后合理的行为是重定向到用户登录前浏览的页面,以便用户执行未完成的操作,而不是直接重定向到主页。
现在,我们有两个页面:Foo和Bar,这两个页面都会指向同一个视图,我们希望在Foo上点击进去那个视图后,会重定向回Foo页面,从Bar页面点击视图后,会重定向会Bar页面。
先写出预备代码

@app.route('/')
def Hello_world():
    return 'This is main interface'
#那个两个都能进的的视图
@app.route('/do_something')
def do_something():
    #应该让它返回点进来的那个链接
    return 'yeah'

@app.route('/foo')
def foo():
    #href是外链
    return '<h1>Foo page</h1><a href="%s">Do something</a>' %url_for('do_something')
@app.route('/bar')
def bar():
    #href是外链
    return '<h1>Bar page</h1><a href="%s">Do something</a>' %url_for('do_something')

获取上一个界面的链接

一般来说,有两种方式来获取上个界面的链接

  1. HTTP referer:即访问来源。当用户在某个站点单击链接,浏览器向新链接所在的服务器发起请求,请求的数据中包含的HTTP_REFERER字段记录了用户所在的原站点URL。

在Flask中,referer的值可以通过请求对象的referrer属性获取,即request.referrer(正确拼写形式)
那么此时do_something可以这样写

#那个两个都能进的的视图
@app.route('/do_something')
def do_something():
    #应该让它返回点进来的那个链接
    return redirect(request.referrer)
  1. 查询参数:除了自动从referrer获取,另一种更常见的方式是在URL中手动加入包含当前页面URL的查询参数,这个查询参数一般命名为next。
@app.route('/foo')
def foo():
    #href是外链
    return '<h1>Foo page</h1><a href="%s">Do something</a>' %url_for('do_something',next=request.full_path)

在程序内部只需要使用相对URL,所以这里使用request.full_path获取当前页面的完整路径。于是do_something中的代码可以这样修改

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

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

def redirect_back(default = 'hello',**kwargs):
    #看next后面是否存在 以及referrer里是否有东西,有的话这些都是target
    for target in request.args.get('next'),request.referrer:
        if target:
            return redirect(target)
    #否则就是都没有 这个时候转回默认的网址hello
    return redirect(url_for(default,**kwargs))

然后do_something中这样操作就可以了

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

HTTP进阶实践(对URL进行安全验证)

从上面的实验可以发现,referer和next都很容易被篡改,如果我们不对这些值进行验证,则会形成开放重定向(Open Redirect)漏洞。
以URL中的next参数为例,next变量以查询字符串的方式写在URL 里,因此任何人都可以发给某个用户一个包含next变量指向任何站点的链接。
故确保URL安全的关键就是判断URL是否属于程序内部。
在代码中,我们创建了一个URL验证函数is_safe_url(),用来验证next里的变量值是否属于程序内部URL。

from urllib.parse import urlparse, urljoin
from flask import Flask, url_for, redirect, request
#接收的参数是目标url
def is_safe_url(target):
    #urlparse()函数用来解析两个url

    #host_url是程序的主机url
    ref_url = urlparse(request.host_url)
    #urljoin是将目标URL转换为绝对URL
    test_url = urlparse(urljoin(request.host_url, target))
    #最后对目标URL的URL模式和主机地址进行验证,确保只有程序内部的URL才会被返回
    return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值