在 flask 中,session(会话) 是一种用于在不同请求之间存储用户数据的机制。flask 的 session 默认是基于 客户端 cookie 的,但数据会经过加密签名,防止篡改(但内容本身是可读的,除非额外加密)。
1. flask session 的基本使用
(1) 启用 session
flask 的 session 基于 flask.session
,它是一个类似字典的对象。使用时需要设置 secret_key
(用于签名 session 数据,防止篡改):
from flask import flask, session app = flask(__name__) app.secret_key = 'your-secret-key-here' # 必须设置,否则 session 无法工作
(2) 存储和读取 session
from flask import flask, session, request, redirect, url_for app = flask(__name__) app.secret_key = 'your-secret-key' @app.route('/login') def login(): session['username'] = 'admin' # 存储 session session['logged_in'] = true # session 可以存储任意可序列化的数据 return "logged in!" @app.route('/dashboard') def dashboard(): if session.get('logged_in'): return f"welcome, {session['username']}!" else: return redirect(url_for('login')) @app.route('/logout') def logout(): session.pop('username', none) # 移除 session 中的某个键 session.clear() # 清空整个 session return "logged out!"
(3) session 的持久性(permanent session)
默认情况下,session 在浏览器关闭后失效。如果要让 session 长期有效(基于 permanent_session_lifetime
配置):
from datetime import timedelta app.config['permanent_session_lifetime'] = timedelta(days=7) # 7天后过期 @app.route('/login') def login(): session.permanent = true # 启用持久 session session['username'] = 'admin' return "logged in (persistent)!"
2. flask session 的工作原理
(1) 默认实现(securecookiesession)
flask 的 session 默认使用 securecookiesession
,数据存储在 客户端 cookie 中,但经过签名(防止篡改)。
数据格式:
{ "username": "admin", "_fresh": true, "_id": "abc123...", "_permanent": true }
签名机制:使用 itsdangerous
库进行签名,确保数据不被篡改(但内容可读)。
(2) 如何修改 session 存储方式?
默认的 cookie session 有大小限制(通常 4kb),如果需要存储大量数据,可以改用 服务端 session(如 redis、数据库):
from flask_session import session # 需要安装 flask-session app.config['session_type'] = 'redis' # 可选 redis, memcached, filesystem 等 app.config['session_permanent'] = true app.config['session_use_signer'] = true # 是否签名 cookie app.config['session_key_prefix'] = 'myapp:' # redis 键前缀 session(app) # 替换默认的 session 实现
然后 flask.session
会自动使用 redis 存储数据,而不是 cookie。
3. session 的安全配置
(1) 安全相关的 cookie 设置
app.config.update( session_cookie_name='my_session', # cookie 名称 session_cookie_httponly=true, # 禁止 javascript 访问 session_cookie_secure=true, # 仅 https 传输 session_cookie_samesite='lax', # 防 csrf(strict/lax/none) session_cookie_partitioned=true, # 跨站 cookie 隔离(chips) )
(2) 防止 session 劫持
- 使用
session_use_signer=true
(默认启用)防止篡改。 - 避免在 session 中存储敏感信息(如密码),因为 cookie 内容可被用户读取(即使签名)。
4. 常见问题
(1)runtimeerror: the session is unavailable because no secret key was set.
原因:未设置 secret_key
。
解决:
app.secret_key = 'your-secret-key' # 必须设置
(2) session 数据不持久
原因:默认 session 在浏览器关闭后失效。
解决:
session.permanent = true app.config['permanent_session_lifetime'] = timedelta(days=30)
(3) 如何强制 session 过期?
@app.route('/clear_session') def clear_session(): session.clear() # 清空 session return "session cleared!"
5. 总结
特性 | 说明 |
---|---|
默认存储 | 客户端 cookie(签名防篡改) |
持久性 | session.permanent = true + permanent_session_lifetime |
安全配置 | httponly、secure、samesite |
扩展存储 | 使用 flask-session 支持 redis、数据库等 |
适用场景 | 小型数据(如用户登录状态),大数据需用服务端存储 |
如果需要更安全的 session 方案,建议:
- 使用 服务端 session(redis) 替代 cookie。
- 避免存储敏感数据在 session 中。
- 启用
secure
+samesite
防 csrf。
sessions.py
from __future__ import annotations import collections.abc as c import hashlib import typing as t from collections.abc import mutablemapping from datetime import datetime from datetime import timezone from itsdangerous import badsignature from itsdangerous import urlsafetimedserializer from werkzeug.datastructures import callbackdict from .json.tag import taggedjsonserializer if t.type_checking: # pragma: no cover import typing_extensions as te from .app import flask from .wrappers import request from .wrappers import response class sessionmixin(mutablemapping[str, t.any]): """expands a basic dictionary with session attributes.""" @property def permanent(self) -> bool: """this reflects the ``'_permanent'`` key in the dict.""" return self.get("_permanent", false) @permanent.setter def permanent(self, value: bool) -> none: self["_permanent"] = bool(value) #: some implementations can detect whether a session is newly #: created, but that is not guaranteed. use with caution. the mixin # default is hard-coded ``false``. new = false #: some implementations can detect changes to the session and set #: this when that happens. the mixin default is hard coded to #: ``true``. modified = true #: some implementations can detect when session data is read or #: written and set this when that happens. the mixin default is hard #: coded to ``true``. accessed = true class securecookiesession(callbackdict[str, t.any], sessionmixin): """base class for sessions based on signed cookies. this session backend will set the :attr:`modified` and :attr:`accessed` attributes. it cannot reliably track whether a session is new (vs. empty), so :attr:`new` remains hard coded to ``false``. """ #: when data is changed, this is set to ``true``. only the session #: dictionary itself is tracked; if the session contains mutable #: data (for example a nested dict) then this must be set to #: ``true`` manually when modifying that data. the session cookie #: will only be written to the response if this is ``true``. modified = false #: when data is read or written, this is set to ``true``. used by # :class:`.securecookiesessioninterface` to add a ``vary: cookie`` #: header, which allows caching proxies to cache different pages for #: different users. accessed = false def __init__( self, initial: c.mapping[str, t.any] | c.iterable[tuple[str, t.any]] | none = none, ) -> none: def on_update(self: te.self) -> none: self.modified = true self.accessed = true super().__init__(initial, on_update) def __getitem__(self, key: str) -> t.any: self.accessed = true return super().__getitem__(key) def get(self, key: str, default: t.any = none) -> t.any: self.accessed = true return super().get(key, default) def setdefault(self, key: str, default: t.any = none) -> t.any: self.accessed = true return super().setdefault(key, default) class nullsession(securecookiesession): """class used to generate nicer error messages if sessions are not available. will still allow read-only access to the empty session but fail on setting. """ def _fail(self, *args: t.any, **kwargs: t.any) -> t.noreturn: raise runtimeerror( "the session is unavailable because no secret " "key was set. set the secret_key on the " "application to something unique and secret." ) __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # noqa: b950 del _fail class sessioninterface: """the basic interface you have to implement in order to replace the default session interface which uses werkzeug's securecookie implementation. the only methods you have to implement are :meth:`open_session` and :meth:`save_session`, the others have useful defaults which you don't need to change. the session object returned by the :meth:`open_session` method has to provide a dictionary like interface plus the properties and methods from the :class:`sessionmixin`. we recommend just subclassing a dict and adding that mixin:: class session(dict, sessionmixin): pass if :meth:`open_session` returns ``none`` flask will call into :meth:`make_null_session` to create a session that acts as replacement if the session support cannot work because some requirement is not fulfilled. the default :class:`nullsession` class that is created will complain that the secret key was not set. to replace the session interface on an application all you have to do is to assign :attr:`flask.flask.session_interface`:: app = flask(__name__) app.session_interface = mysessioninterface() multiple requests with the same session may be sent and handled concurrently. when implementing a new session interface, consider whether reads or writes to the backing store must be synchronized. there is no guarantee on the order in which the session for each request is opened or saved, it will occur in the order that requests begin and end processing. .. versionadded:: 0.8 """ #: :meth:`make_null_session` will look here for the class that should #: be created when a null session is requested. likewise the #: :meth:`is_null_session` method will perform a typecheck against #: this type. null_session_class = nullsession #: a flag that indicates if the session interface is pickle based. #: this can be used by flask extensions to make a decision in regards #: to how to deal with the session object. #: #: .. versionadded:: 0.10 pickle_based = false def make_null_session(self, app: flask) -> nullsession: """creates a null session which acts as a replacement object if the real session support could not be loaded due to a configuration error. this mainly aids the user experience because the job of the null session is to still support lookup without complaining but modifications are answered with a helpful error message of what failed. this creates an instance of :attr:`null_session_class` by default. """ return self.null_session_class() def is_null_session(self, obj: object) -> bool: """checks if a given object is a null session. null sessions are not asked to be saved. this checks if the object is an instance of :attr:`null_session_class` by default. """ return isinstance(obj, self.null_session_class) def get_cookie_name(self, app: flask) -> str: """the name of the session cookie. uses``app.config["session_cookie_name"]``.""" return app.config["session_cookie_name"] # type: ignore[no-any-return] def get_cookie_domain(self, app: flask) -> str | none: """the value of the ``domain`` parameter on the session cookie. if not set, browsers will only send the cookie to the exact domain it was set from. otherwise, they will send it to any subdomain of the given value as well. uses the :data:`session_cookie_domain` config. .. versionchanged:: 2.3 not set by default, does not fall back to ``server_name``. """ return app.config["session_cookie_domain"] # type: ignore[no-any-return] def get_cookie_path(self, app: flask) -> str: """returns the path for which the cookie should be valid. the default implementation uses the value from the ``session_cookie_path`` config var if it's set, and falls back to ``application_root`` or uses ``/`` if it's ``none``. """ return app.config["session_cookie_path"] or app.config["application_root"] # type: ignore[no-any-return] def get_cookie_httponly(self, app: flask) -> bool: """returns true if the session cookie should be httponly. this currently just returns the value of the ``session_cookie_httponly`` config var. """ return app.config["session_cookie_httponly"] # type: ignore[no-any-return] def get_cookie_secure(self, app: flask) -> bool: """returns true if the cookie should be secure. this currently just returns the value of the ``session_cookie_secure`` setting. """ return app.config["session_cookie_secure"] # type: ignore[no-any-return] def get_cookie_samesite(self, app: flask) -> str | none: """return ``'strict'`` or ``'lax'`` if the cookie should use the ``samesite`` attribute. this currently just returns the value of the :data:`session_cookie_samesite` setting. """ return app.config["session_cookie_samesite"] # type: ignore[no-any-return] def get_cookie_partitioned(self, app: flask) -> bool: """returns true if the cookie should be partitioned. by default, uses the value of :data:`session_cookie_partitioned`. .. versionadded:: 3.1 """ return app.config["session_cookie_partitioned"] # type: ignore[no-any-return] def get_expiration_time(self, app: flask, session: sessionmixin) -> datetime | none: """a helper method that returns an expiration date for the session or ``none`` if the session is linked to the browser session. the default implementation returns now + the permanent session lifetime configured on the application. """ if session.permanent: return datetime.now(timezone.utc) + app.permanent_session_lifetime return none def should_set_cookie(self, app: flask, session: sessionmixin) -> bool: """used by session backends to determine if a ``set-cookie`` header should be set for this session cookie for this response. if the session has been modified, the cookie is set. if the session is permanent and the ``session_refresh_each_request`` config is true, the cookie is always set. this check is usually skipped if the session was deleted. .. versionadded:: 0.11 """ return session.modified or ( session.permanent and app.config["session_refresh_each_request"] ) def open_session(self, app: flask, request: request) -> sessionmixin | none: """this is called at the beginning of each request, after pushing the request context, before matching the url. this must return an object which implements a dictionary-like interface as well as the :class:`sessionmixin` interface. this will return ``none`` to indicate that loading failed in some way that is not immediately an error. the request context will fall back to using :meth:`make_null_session` in this case. """ raise notimplementederror() def save_session( self, app: flask, session: sessionmixin, response: response ) -> none: """this is called at the end of each request, after generating a response, before removing the request context. it is skipped if :meth:`is_null_session` returns ``true``. """ raise notimplementederror() session_json_serializer = taggedjsonserializer() def _lazy_sha1(string: bytes = b"") -> t.any: """don't access ``hashlib.sha1`` until runtime. fips builds may not include sha-1, in which case the import and use as a default would fail before the developer can configure something else. """ return hashlib.sha1(string) 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(_lazy_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 keys: list[str | bytes] = [] if fallbacks := app.config["secret_key_fallbacks"]: keys.extend(fallbacks) keys.append(app.secret_key) # itsdangerous expects current key at top return urlsafetimedserializer( keys, # type: ignore[arg-type] salt=self.salt, serializer=self.serializer, signer_kwargs={ "key_derivation": self.key_derivation, "digest_method": self.digest_method, }, ) 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() max_age = int(app.permanent_session_lifetime.total_seconds()) try: 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) partitioned = self.get_cookie_partitioned(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, partitioned=partitioned, 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[union-attr] response.set_cookie( name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure, partitioned=partitioned, samesite=samesite, ) response.vary.add("cookie")
1. 会话(session)的核心类
(1)sessionmixin
作用:为字典形式的 session 提供扩展属性。
关键属性:
permanent
:标记 session 是否为持久会话(基于_permanent
键值)。new
:标记 session 是否为新创建的(默认为false
)。modified
:标记 session 是否被修改(默认为true
)。accessed
:标记 session 是否被访问(默认为true
)。
(2)securecookiesession
作用:基于签名 cookie 的默认 session 实现(继承自 callbackdict
和 sessionmixin
)。
特点:
- 自动跟踪修改状态(
modified
)和访问状态(accessed
)。 - 数据变更时会触发回调,确保状态同步。
(3)nullsession
作用:当未配置密钥时,提供一个安全的空 session 替代品。
行为:
- 允许读取操作(返回空值)。
- 拒绝写入操作(直接抛出
runtimeerror
)。
2. 会话接口(sessioninterface)
(1) 核心方法
open_session(app, request)
功能:在请求开始时加载 session。
返回:需实现类字典接口 +sessionmixin
的对象,失败时返回none
(触发nullsession
)。save_session(app, session, response)
功能:在请求结束时保存 session 到响应。
注意:若is_null_session(session)
为true
则跳过。
(2) cookie 配置方法
get_cookie_name()
:从配置session_cookie_name
获取 cookie 名称。get_cookie_domain()
:控制 cookie 的domain
属性(默认无子域名共享)。get_cookie_secure()
:根据session_cookie_secure
决定是否仅 https 传输。get_cookie_samesite()
:配置samesite
防 csrf(strict
/lax
)。get_cookie_partitioned()
:是否启用partitioned
属性(用于跨站 cookie 隔离)。
(3) 辅助逻辑
should_set_cookie()
:决定是否设置set-cookie
头(基于修改状态或session_refresh_each_request
)。make_null_session()
:生成nullsession
实例。is_null_session()
:检查对象是否为无效 session。
3. 默认实现(securecookiesessioninterface)
(1) 安全特性
签名机制:使用 itsdangerous.urlsafetimedserializer
。
- 支持密钥轮换(
secret_key_fallbacks
)。 - 可配置盐值(
salt
)、哈希算法(如sha1
)和密钥派生方式(如hmac
)。
数据序列化:通过 taggedjsonserializer
处理 python 特殊类型(如 datetime
)。
(2) 工作流程
读取 session
- 从请求的 cookie 中加载数据。
- 验证签名和时间戳(防止篡改和过期会话)。
- 失败时返回空 session。
保存 session
- 若 session 为空且被修改 → 删除 cookie。
- 若需更新(
should_set_cookie
为true
)→ 写入签名后的数据到 cookie。 - 自动设置
vary: cookie
头以适配缓存。
(3) 配置示例
app.config.update( secret_key="your-secret-key", session_cookie_name="my_session", session_cookie_secure=true, session_cookie_samesite="lax", permanent_session_lifetime=timedelta(days=7), )
4. 设计思想
- 客户端存储:session 数据默认通过 cookie 存储(节省服务端资源)。
- 安全优先:所有数据签名防篡改,且支持安全相关的 cookie 属性。
- 灵活扩展:通过实现
sessioninterface
可轻松切换为服务端 session(如 redis 存储)。
常见问题
- 密钥缺失:未设置
secret_key
时,所有 session 操作将触发nullsession
的异常。 - 性能注意:cookie 大小受限(通常不超过 4kb),复杂数据需考虑服务端存储方案。
如果需要进一步分析特定部分(如自定义 session 存储),可以深入探讨具体实现方式。
到此这篇关于flask库中sessions.py的使用小结的文章就介绍到这了,更多相关flask sessions.py内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论