文章目录
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')
获取上一个界面的链接
一般来说,有两种方式来获取上个界面的链接
- HTTP referer:即访问来源。当用户在某个站点单击链接,浏览器向新链接所在的服务器发起请求,请求的数据中包含的HTTP_REFERER字段记录了用户所在的原站点URL。
在Flask中,referer的值可以通过请求对象的referrer属性获取,即request.referrer(正确拼写形式)
那么此时do_something可以这样写
#那个两个都能进的的视图
@app.route('/do_something')
def do_something():
#应该让它返回点进来的那个链接
return redirect(request.referrer)
- 查询参数:除了自动从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
本文介绍Flask框架中Cookie和Session的使用方法,包括用户登录、注销功能的实现,以及上下文全局变量current_app和g的应用。同时探讨了HTTP进阶实践,如重定向回上一个页面和对URL进行安全验证。
66

被折叠的 条评论
为什么被折叠?



