3人参与 • 2025-07-27 • Python
在 flask 中,session(会话) 是一种用于在不同请求之间存储用户数据的机制。flask 的 session 默认是基于 客户端 cookie 的,但数据会经过加密签名,防止篡改(但内容本身是可读的,除非额外加密)。
flask 的 session 基于 flask.session
,它是一个类似字典的对象。使用时需要设置 secret_key
(用于签名 session 数据,防止篡改):
from flask import flask, session app = flask(__name__) app.secret_key = 'your-secret-key-here' # 必须设置,否则 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!"
默认情况下,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)!"
flask 的 session 默认使用 securecookiesession
,数据存储在 客户端 cookie 中,但经过签名(防止篡改)。
数据格式:
{ "username": "admin", "_fresh": true, "_id": "abc123...", "_permanent": true }
签名机制:使用 itsdangerous
库进行签名,确保数据不被篡改(但内容可读)。
默认的 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。
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) )
session_use_signer=true
(默认启用)防止篡改。原因:未设置 secret_key
。
解决:
app.secret_key = 'your-secret-key' # 必须设置
原因:默认 session 在浏览器关闭后失效。
解决:
session.permanent = true app.config['permanent_session_lifetime'] = timedelta(days=30)
@app.route('/clear_session') def clear_session(): session.clear() # 清空 session return "session cleared!"
特性 | 说明 |
---|---|
默认存储 | 客户端 cookie(签名防篡改) |
持久性 | session.permanent = true + permanent_session_lifetime |
安全配置 | httponly、secure、samesite |
扩展存储 | 使用 flask-session 支持 redis、数据库等 |
适用场景 | 小型数据(如用户登录状态),大数据需用服务端存储 |
如果需要更安全的 session 方案,建议:
secure
+ samesite
防 csrf。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")
作用:为字典形式的 session 提供扩展属性。
关键属性:
permanent
:标记 session 是否为持久会话(基于 _permanent
键值)。new
:标记 session 是否为新创建的(默认为 false
)。modified
:标记 session 是否被修改(默认为 true
)。accessed
:标记 session 是否被访问(默认为 true
)。作用:基于签名 cookie 的默认 session 实现(继承自 callbackdict
和 sessionmixin
)。
特点:
modified
)和访问状态(accessed
)。作用:当未配置密钥时,提供一个安全的空 session 替代品。
行为:
runtimeerror
)。open_session(app, request)
功能:在请求开始时加载 session。
返回:需实现类字典接口 + sessionmixin
的对象,失败时返回 none
(触发 nullsession
)。
save_session(app, session, response)
功能:在请求结束时保存 session 到响应。
注意:若 is_null_session(session)
为 true
则跳过。
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 隔离)。should_set_cookie()
:决定是否设置 set-cookie
头(基于修改状态或 session_refresh_each_request
)。make_null_session()
:生成 nullsession
实例。is_null_session()
:检查对象是否为无效 session。签名机制:使用 itsdangerous.urlsafetimedserializer
。
secret_key_fallbacks
)。salt
)、哈希算法(如 sha1
)和密钥派生方式(如 hmac
)。数据序列化:通过 taggedjsonserializer
处理 python 特殊类型(如 datetime
)。
读取 session
保存 session
should_set_cookie
为 true
)→ 写入签名后的数据到 cookie。vary: cookie
头以适配缓存。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), )
sessioninterface
可轻松切换为服务端 session(如 redis 存储)。secret_key
时,所有 session 操作将触发 nullsession
的异常。如果需要进一步分析特定部分(如自定义 session 存储),可以深入探讨具体实现方式。
到此这篇关于flask库中sessions.py的使用小结的文章就介绍到这了,更多相关flask sessions.py内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论