flask中的session介绍
在Flask中,session是一个用于存储特定用户会话数据的字典对象。它在不同请求之间保存数据。它通过在客户端设置一个签名的cookie,将所有的会话数据存储在客户端。以下是如何在Flask应用中使用session的基本步骤:
首先,你需要设置一个秘钥,这是为了加密你的session数据:
from flask import Flask, session
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
然后,你可以像操作字典一样操作session对象。以下是一个登录的例子:
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
#from flask import escape
# 假设 session['username'] 是 "<script>alert('hacked!');</script>"
#safe_username = escape(session['username'])
# safe_username 现在是 "<script>alert('hacked!');</script>"
# escape(session['username'])是在做HTML转义
@app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if valid_login(username, password):
session['username'] = request.form['username']
return redirect(url_for('index'))
else:
error = 'Invalid username or password'
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
Flask的session实现涉及到几个关键的组件:session
对象、session_interface
对象以及secure_cookie
模块。以下是这些组件是如何工作以实现Flask的session的:
- Session对象:在Flask中,session被表示为一个名为
session
的字典对象。它是LocalProxy
的实例,LocalProxy
是一种可以动态引用当前运行环境(比如请求或应用上下文)的特定对象的代理类。当你尝试访问session
对象的属性或方法时,LocalProxy
会将这些操作转发到实际的会话对象,这个实际的会话对象由session_interface
创建。 - SessionInterface对象:
SessionInterface
是一个抽象基类,定义了用于处理session的接口。Flask自带的SecureCookieSessionInterface
实现了这个接口,使用安全的签名cookie来存储session数据。当一个请求开始时,SecureCookieSessionInterface
会从请求的cookies中提取出session数据,并创建一个新的SecureCookieSession
对象。当请求结束时,如果SecureCookieSession
对象被修改,SecureCookieSessionInterface
会把它序列化并签名,然后存回到客户端的cookies中。【文末附源码解释】 - SecureCookie模块:这个模块实现了
SecureCookieSession
类,SecureCookieSession
是一个用于存储实际session数据的字典子类,它的工作方式和普通的字典一样。
整体来看,Flask的session实现工作流程是这样的:
- 当一个请求开始时,Flask会创建一个新的请求上下文,并通过
SecureCookieSessionInterface
从请求的cookies中提取出session数据,然后创建一个新的SecureCookieSession
对象。 - 当你在你的视图函数中操作
session
对象(比如设置session['username'] = 'John'
)时,实际上你是在操作这个SecureCookieSession
对象。 - 当请求结束时,Flask会检查
SecureCookieSession
对象是否被修改。如果被修改,Flask会通过SecureCookieSessionInterface
和SecureCookie
将SecureCookieSession
对象序列化并签名,然后把它存回到响应的cookies中。 - 当下一个请求来到时,这个过程会再次重复。
通常流程总结
- 当一个新用户(没有任何session数据的用户)首次访问你的Flask应用时,他们的请求中不会包含任何session数据。在这种情况下,Flask会为这个用户创建一个新的、空的session对象。这个新的session对象在初始状态下是空的,也就是说,它不包含任何数据。
- 如果在处理这个请求的过程中,你的代码修改了session对象(例如,通过设置
session['username'] = 'John'
),那么当请求结束时,Flask会把这个session对象序列化并签名,然后存入一个新的Cookie中。这个新的Cookie会被发送到客户端,一起与响应一起传送。 - 当这个用户下次访问你的Flask应用时,他们的请求将会携带这个包含了session数据的Cookie。Flask会在接收到这个请求时,从Cookie中提取出session数据,并创建一个新的session对象。这样,你的代码就可以继续访问和修改这个session对象了。
需要注意的是,如果一个请求没有修改session对象,那么Flask就不会在响应中设置新的Cookie。这是因为,没有必要把一个没有变化的session数据再次发送到客户端。
因此,即使一个新用户的首次请求中没有包含任何session数据,Flask也能正确地处理
SecureCookieSessionInterface
class SecureCookieSessionInterface(SessionInterface):
"""The default session interface that stores sessions in signed cookies
through the :mod:`itsdangerous` module.
"""
#: the salt that should be applied on top of the secret key for the
#: signing of cookie based sessions.
salt = "cookie-session"
#: the hash function to use for the signature. The default is sha1
digest_method = staticmethod(hashlib.sha1)
#: the name of the itsdangerous supported key derivation. The default
#: is hmac.
key_derivation = "hmac"
#: A python serializer for the payload. The default is a compact
#: JSON derived serializer with support for some extra Python types
#: such as datetime objects or tuples.
serializer = session_json_serializer
session_class = SecureCookieSession
def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None:
if not app.secret_key:
return None
signer_kwargs = dict(
key_derivation=self.key_derivation, digest_method=self.digest_method
)
return URLSafeTimedSerializer(
app.secret_key,
salt=self.salt,
serializer=self.serializer,
signer_kwargs=signer_kwargs,
)
def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None:
s = self.get_signing_serializer(app)
if s is None:
return None
val = request.cookies.get(self.get_cookie_name(app))
if not val:
return self.session_class()
# 获取session的最大有效期,单位为秒。
max_age = int(app.permanent_session_lifetime.total_seconds())
try:
# 尝试使用序列化器s的loads方法,对session cookie的值val进行反序列化和签名验证。如果反序列化和验证成功,就用这些数据创建一个新的session对象,并返回
data = s.loads(val, max_age=max_age)
return self.session_class(data)
except BadSignature:
return self.session_class()
def save_session(
self, app: Flask, session: SessionMixin, response: Response
) -> None:
name = self.get_cookie_name(app)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
httponly = self.get_cookie_httponly(app)
# Add a "Vary: Cookie" header if the session was accessed at all.
if session.accessed:
response.vary.add("Cookie")
# If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie.
if not session:
if session.modified:
response.delete_cookie(
name,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
httponly=httponly,
)
response.vary.add("Cookie")
return
if not self.should_set_cookie(app, session):
return
expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore
response.set_cookie(
name,
val, # type: ignore
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)
response.vary.add("Cookie")
SecureCookieSessionInterface
类:这个类实现了session接口,使用安全的签名cookies来存储session数据。- 类属性:
salt
:加盐值,用于混淆session的加密过程,增加安全性。digest_method
:哈希函数,用于签名过程中对数据进行哈希处理,默认为sha1。key_derivation
:关键字派生,设置为"hmac",表示使用HMAC进行签名。serializer
:序列化器,用于将Python对象转换为可以在网络上传输的格式,这里使用的是JSON序列化器。session_class
:表示session的类,默认为SecureCookieSession。
get_signing_serializer
方法:用于获取一个签名序列化器,其作用是用来签名和反签名cookies的。如果应用没有设置秘钥app.secret_key
,则返回None。open_session
方法:在处理每个请求时调用,从请求的cookies中提取出session数据,反序列化并验证签名,得到session的数据。如果签名不合法,就会抛出BadSignature
异常,然后返回一个空的session。save_session
方法:在每个请求处理完后调用,将session数据序列化,签名,然后存入到响应的cookies中。如果session为空且已被修改,则删除cookie。只有当session被访问过或被修改,才会设置Vary: Cookie
头。
在使用SecureCookieSessionInterface
处理session时,Flask会保证session的安全性,即使session数据存储在客户端的cookies中,也无法被篡改,因为每个session cookie都被签名了。除非知道服务器的秘钥,否则无法伪造有效的session cookie。