如何使用会话
Django 提供了对匿名会话的全面支持。会话框架允许你在每个站点访问者的基础上存储和检索任意数据。它将数据存储在服务器端并抽象了 cookie 的发送和接收。Cookies 包含会话 ID - 而不是数据本身(除非你使用 基于 cookie 的后端)。
启用会话
会话是通过一段 中间件 实现的。
要启用会话功能,请执行以下操作:
- 编辑 MIDDLEWARE 设置,并确保其中包含
'django.contrib.sessions.middleware.SessionMiddleware'
。由django-admin startproject
创建的默认settings.py
已经激活了SessionMiddleware
。
如果你不想使用会话,你也可以从 MIDDLEWARE 中删除 SessionMiddleware
行,并从 INSTALLED_APPS 中删除 'django.contrib.sessions'
。这将节省一些小额开销。
配置会话引擎
默认情况下,Django 将会话存储在数据库中(使用模型 django.contrib.sessions.models.Session
)。尽管这很方便,但在某些设置中,将会话数据存储在其他地方可能更快,因此可以配置 Django 将会话数据存储在文件系统或缓存中。
使用基于数据库的会话
如果要使用基于数据库的会话,需要将 'django.contrib.sessions'
添加到你的 INSTALLED_APPS 设置中。
一旦配置完成,运行 manage.py migrate
来安装存储会话数据的单个数据库表。
使用缓存会话
为了获得更好的性能,你可能想要使用基于缓存的会话后端。
要使用 Django 的缓存系统存储会话数据,首先需要确保你已经配置了缓存;请查看 缓存文档 获取详细信息。
警告
只有当你使用 Memcached 或 Redis 缓存后端时,才应该使用基于缓存的会话。本地内存缓存后端不会保留数据足够长的时间,因此不是一个好选择,直接使用文件或数据库会话可能会更快,而不是通过文件或数据库缓存后端发送所有数据。此外,本地内存缓存后端不是多进程安全的,因此在生产环境中可能不是一个好选择。
如果在 CACHES 中定义了多个缓存,Django 将使用默认缓存。要使用另一个缓存,将 SESSION_CACHE_ALIAS 设置为该缓存的名称。
一旦配置了缓存,你需要在数据库支持的缓存和非持久性缓存之间进行选择。
缓存的数据库后端(cached_db
)使用写透缓存 - 会话写入同时应用于缓存和数据库。会话读取使用缓存,或者如果数据已从缓存中逐出,则使用数据库。要使用此后端,请将 SESSION_ENGINE 设置为 "django.contrib.sessions.backends.cached_db"
,并按照 使用基于数据库的会话 的配置说明进行配置。
缓存后端(cache
)只在缓存中存储会话数据。这更快,因为它避免了数据库持久性,但你需要考虑当缓存数据被逐出时会发生什么情况。逐出可能发生在缓存填满或缓存服务器重新启动时,这将意味着会话数据丢失,包括用户的注销状态。要使用此后端,请将 SESSION_ENGINE 设置为 "django.contrib.sessions.backends.cache"
。
缓存后端可以通过使用具有适当配置的持久缓存(例如 Redis)来变成持久性的。但除非你的缓存明确配置为具有足够的持久性,否则选择缓存数据库后端是更安全的选择。这可以避免由于生产中不可靠的数据存储引起的边缘情况。
使用基于文件的会话
要使用基于文件的会话,请将 SESSION_ENGINE 设置为 "django.contrib.sessions.backends.file"
。
你可能还想设置 SESSION_FILE_PATH 配置(默认为 tempfile.gettempdir()
的输出,很可能是 /tmp
)来控制 Django 存储会话文件的位置。请确保你的 Web 服务器具有读写此位置的权限。
使用基于 cookie 的会话
要使用基于 cookie 的会话,请将 SESSION_ENGINE 设置为 "django.contrib.sessions.backends.signed_cookies"
。会话数据将使用 Django 的 加密签名 工具和 SECRET_KEY 设置存储。
备注
建议将 SESSION_COOKIE_HTTPONLY 设置为 True
,以防止 JavaScript 访问存储的数据。
警告
**如果 SECRET_KEY
或 SECRET_KEY_FALLBACKS
** 没有保密,并且你正在使用 django.contrib.sessions.serializers.PickleSerializer
,这可能导致任意远程代码执行。
拥有 SECRET_KEY 或 SECRET_KEY_FALLBACKS 的攻击者不仅可以生成伪造的会话数据,你的网站将信任这些数据,还可以远程执行任意代码,因为数据是使用 pickle 进行序列化的。
如果你使用基于 cookie 的会话,请格外注意确保你的密钥始终被完全保密,对于任何可能远程访问的系统。
会话数据被签名但未加密
在使用基于 cookie 的后端时,会话数据可以被客户端读取。
使用 MAC(消息认证码)用于保护数据免受客户端的更改,以便在被篡改时会话数据将被作废。如果存储 cookie 的客户端(例如你用户的浏览器)无法存储全部会话 cookie 并丢弃数据,会发生相同的作废。尽管 Django 对数据进行了压缩,但仍然完全有可能超过每个 cookie 的 常见限制 4096 字节。
不保证新鲜度
还要注意,虽然 MAC 可以保证数据的真实性(即它是由你的站点生成的,而不是其他人生成的),以及数据的完整性(即它都在那里且正确),但它不能保证新鲜度,也就是不能保证你被发送回客户端的是最后发送给客户端的内容。这意味着对于一些使用会话数据的情况,基于 cookie 的后端可能会使你容易受到 重放攻击 的威胁。与其他会话后端不同,这些后端会保留每个会话的服务器端记录,并在用户注销时使其失效,基于 cookie 的会话在用户注销时不会失效。因此,如果攻击者窃取了用户的 cookie,即使用户注销,他们仍然可以使用该 cookie 以该用户的身份登录。只有当 cookie 的寿命超过 SESSION_COOKIE_AGE 时,它们才会被检测为 ‘过时’。
性能
最后,cookie 的大小可能会影响你的网站的速度。
在视图中使用会话
当激活了 SessionMiddleware
时,每个 HttpRequest 对象 — 作为任何 Django 视图函数的第一个参数 — 都将具有一个 session
属性,它是一个类似于字典的对象。
你可以在视图的任何时候读取并写入 request.session
。你可以多次编辑它。
class backends.base.SessionBase
这是所有会话对象的基类。它具有以下标准的字典方法:
__getitem__
(key)比如:
fav_color = request.session['fav_color']
__setitem__
(key, value)比如:
request.session['fav_color'] = 'blue'
__delitem__
(key)比如:
del request.session['fav_color']
。如果给定的key
不在会话里,会引发KeyError
。__contains__
(key)比如:
'fav_color' in request.session
get
(key, default=None)比如:
fav_color = request.session.get('fav_color', 'red')
pop
(key, default=\_not_given_)比如:
fav_color = request.session.pop('fav_color', 'blue')
keys
()items
()setdefault
()clear
()
它也有以下方法:
flush
()从会话中删除当前会话数据并删除会话 cookie。如果你想确保用户的浏览器无法再次访问以前的会话数据(例如, django.contrib.auth.logout() 函数会调用它)。
set_test_cookie
()设置一个测试 cookie 来确定用户的浏览器是否支持 cookie。由于 cookie 的工作方式,你将无法在用户的下一个页面请求之前进行测试。有关更多信息,请参阅下面的 设置测试 cookie。
test_cookie_worked
()返回
True
或False
,取决于用户的浏览器是否接受了测试 cookie。由于 cookie 的工作方式,你必须在前一个独立的页面请求上调用set_test_cookie()
。有关更多信息,请参阅下面的 设置测试 cookie。delete_test_cookie
()删除测试 cookie。使用它来清理。
get_session_cookie_age
()返回 SESSION_COOKIE_AGE 设置的值。这可以在自定义会话后端中进行覆盖。
set_expiry
(value)设置会话的过期时间。你可以传递多种不同的值:
- 如果
value
是一个整数,会话将在多少秒的不活动后过期。例如,调用request.session.set_expiry(300)
会使会话在5分钟后过期。 - 如果
value
是一个datetime
或timedelta
对象,会话将在特定的日期/时间过期。 - 如果
value
是0
,用户的会话 cookie 将在用户关闭 Web 浏览器时过期。 - 如果
value
是None
,会话将恢复使用全局会话过期策略。
读取会话不被视为用于过期目的的活动。会话过期是根据会话上次被 修改 的时间计算的。
- 如果
get_expiry_age
()返回会话过期前剩余的秒数。对于没有自定义过期时间的会话(或设置为在关闭浏览器时过期的会话),这将等于 SESSION_COOKIE_AGE。
此函数接受两个可选的关键字参数:
modification
:会话的最后修改时间,作为一个 datetime 对象。默认为当前时间。expiry
:会话的过期信息,作为一个 datetime 对象、一个int`(以秒为单位)或 ``None`
。默认为由 set_expiry() 存储在会话中的值(如果存在),否则为None
。
备注
这个方法用于会话后端在保存会话时确定会话过期时间的秒数。它实际上并不打算在那种上下文之外使用。
特别是,尽管在你拥有正确的
modification
值 且expiry
设置为datetime
对象的情况下可以 可能 确定会话的剩余寿命,但在你有modification
值的情况下,手动计算到期时间更加直接:expires_at = modification + timedelta(seconds=settings.SESSION_COOKIE_AGE)
get_expiry_date
()返回会话将过期的日期。对于没有自定义过期时间的会话(或设置为在关闭浏览器时过期的会话),这将等于从现在开始 SESSION_COOKIE_AGE 秒后的日期。
此函数接受与 get_expiry_age() 相同的关键字参数,并适用类似的用法注意事项。
get_expire_at_browser_close
()根据用户的会话 cookie 是否在用户关闭 Web 浏览器时过期,返回
True
或False
。clear_expired
()从会话存储中删除已过期的会话。这个类方法由 clearsessions 调用。
cycle_key
()在保留当前会话数据的同时创建一个新的会话密钥。 django.contrib.auth.login() 调用这个方法以减轻会话固定攻击。
会话序列化
默认情况下,Django使用 JSON 对会话数据进行序列化。你可以使用 SESSION_SERIALIZER 设置来自定义会话序列化格式。尽管在 编写你自己的序列化器 中描述了一些注意事项,但我们强烈建议 特别是如果你使用的是基于 cookie 的后端,坚持使用 JSON 序列化。
例如,如果你使用 pickle 来序列化会话数据,以下是一个攻击场景。如果你使用的是 签名 cookie 会话后端 并且 SECRET_KEY (或 SECRET_KEY_FALLBACKS 的任何密钥)被攻击者知道(Django 本身没有内在的漏洞会导致其泄露),攻击者可以在他们的会话中插入一个字符串,当反序列化时,它在服务器上执行任意代码。这种技巧很简单,而且在互联网上很容易找到。尽管 cookie 会话存储对 cookie 中存储的数据进行了签名以防止篡改,但 SECRET_KEY 泄漏会立即升级为远程代码执行漏洞。
捆绑的序列化器
class serializers.JSONSerializer
一个包装了 django.core.signing 中的 JSON 序列化器的包装器。只能序列化基本数据类型。
此外,由于 JSON 只支持字符串键,因此请注意在 request.session
中使用非字符串键不会按预期工作:
>>> # initial assignment
>>> request.session[0] = "bar"
>>> # subsequent requests following serialization & deserialization
>>> # of session data
>>> request.session[0] # KeyError
>>> request.session["0"]
'bar'
类似地,不能编码为 JSON 的数据,例如不能编码为 UTF-8 的字节,如 '\xd9'
(会引发 UnicodeDecodeError),也无法存储。
请参阅 编写你自己的序列化器 部分以获取关于 JSON 序列化的限制的更多详细信息。
class serializers.PickleSerializer
支持任意的 Python 对象,但如上所述,如果 SECRET_KEY 或 SECRET_KEY_FALLBACKS 的任何密钥被攻击者知道,可能会导致远程代码执行漏洞。
4.1 版后已移除: 由于远程代码执行的风险,这个序列化器已经被弃用,并将在 Django 5.0 中删除。
编写你自己的序列化器
请注意, JSONSerializer 不能处理任意的 Python 数据类型。通常情况下,方便和安全之间存在权衡。如果你希望在 JSON 支持的会话中存储更高级的数据类型,包括 datetime
和 Decimal
,你需要编写一个自定义的序列化器(或在存储到 request.session
之前将这些值转换为可 JSON 序列化的对象)。尽管序列化这些值通常很简单(DjangoJSONEncoder 可能会有所帮助),但编写一个可以可靠地获取与放入相同的东西的解码器更加脆弱。例如,你可能会冒险返回一个实际上是一个字符串的 datetime
,只是碰巧与为 datetime
选择的相同格式。
你的序列化器类必须实现两个方法,dumps(self, obj)
和 loads(self, data)
,分别用于序列化和反序列化会话数据的字典。
会话对象指南
- 在
request.session
上使用普通的 Python 字符串作为字典键。这更像是一种约定而不是硬性规则。 - 以下划线开头的会话字典键被保留供 Django 内部使用。
- 不要用新对象覆盖
request.session
,也不要访问或设置它的属性。像使用 Python 字典一样使用它。
示例
这个简单的视图在用户发布评论后将一个变量 has_commented
设置为 True
。它不允许用户发布多次评论:
def post_comment(request, new_comment):
if request.session.get("has_commented", False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=new_comment)
c.save()
request.session["has_commented"] = True
return HttpResponse("Thanks for your comment!")
这个简单的视图登录了站点的一个“成员”:
def login(request):
m = Member.objects.get(username=request.POST["username"])
if m.check_password(request.POST["password"]):
request.session["member_id"] = m.id
return HttpResponse("You're logged in.")
else:
return HttpResponse("Your username and password didn't match.")
…而这一个则根据上面的 login()
方法将成员注销:
def logout(request):
try:
del request.session["member_id"]
except KeyError:
pass
return HttpResponse("You're logged out.")
标准的 django.contrib.auth.logout() 函数实际上做了更多的工作,以防止意外的数据泄露。它调用 request.session
的 flush() 方法。我们在这个示例中只是演示如何使用会话对象,而不是完整的 logout()
实现。
设置测试 cookie
作为一种便利,Django 提供了一种测试用户浏览器是否接受 cookie 的方法。在视图中调用 request.session
的 set_test_cookie() 方法,并在后续的视图中调用 test_cookie_worked() — 不要在同一个视图调用中执行。
这种 set_test_cookie()
和 test_cookie_worked()
之间的分离方式是由于 cookie 的工作方式所必需的。当你设置一个 cookie 时,你实际上无法知道浏览器是否接受了它,直到浏览器的下一个请求。
使用 delete_test_cookie() 来清理。在验证了测试 cookie 有效后执行这个操作。
这里是一个典型的用法示例:
from django.http import HttpResponse
from django.shortcuts import render
def login(request):
if request.method == "POST":
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponse("You're logged in.")
else:
return HttpResponse("Please enable cookies and try again.")
request.session.set_test_cookie()
return render(request, "foo/login_form.html")
在视图外使用会话
备注
本节中的示例直接从 django.contrib.sessions.backends.db
后端导入 SessionStore
对象。在你自己的代码中,你应该考虑从由 SESSION_ENGINE 指定的会话引擎中导入 SessionStore
,如下所示:
>>> from importlib import import_module
>>> from django.conf import settings
>>> SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
在视图之外,有一个 API 可用于操作会话数据:
>>> from django.contrib.sessions.backends.db import SessionStore
>>> s = SessionStore()
>>> # stored as seconds since epoch since datetimes are not serializable in JSON.
>>> s["last_login"] = 1376587691
>>> s.create()
>>> s.session_key
'2b1189a188b44ad18c35e113ac6ceead'
>>> s = SessionStore(session_key="2b1189a188b44ad18c35e113ac6ceead")
>>> s["last_login"]
1376587691
SessionStore.create()
用于创建一个新的会话(即,一个未从会话存储加载并且 session_key=None
的会话)。save()
用于保存一个已存在的会话(即,从会话存储加载的会话)。在新会话上调用 save()
也可能有效,但有小概率生成与现有会话冲突的 session_key
。create()
调用 save()
并循环直到生成一个未使用的 session_key
。
如果你使用的是 django.contrib.sessions.backends.db
后端,每个会话都是一个普通的 Django 模型。Session
模型在 django/contrib/sessions/models.py 中定义。因为它是一个普通的模型,所以你可以使用普通的 Django 数据库 API 访问会话:
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk="2b1189a188b44ad18c35e113ac6ceead")
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)
请注意,你需要调用 get_decoded() 来获取会话字典。这是必要的,因为字典是以编码格式存储的:
>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}
当会话被保存时
默认情况下,只有当会话被修改时,Django 才会保存到会话数据库,也就是说,如果它的字典值中有任何一个被赋值或删除:
# Session is modified.
request.session["foo"] = "bar"
# Session is modified.
del request.session["foo"]
# Session is modified.
request.session["foo"] = {}
# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session["foo"]["bar"] = "baz"
在上面的示例的最后一种情况中,我们可以通过在会话对象上设置 modified
属性来明确告诉会话对象它已被修改:
request.session.modified = True
要更改这个默认行为,将 SESSION_SAVE_EVERY_REQUEST 设置为 True
。当设置为 True
时,Django 将在每个请求上将会话保存到数据库中。
请注意,只有在创建或修改会话时才会发送会话 cookie。如果 SESSION_SAVE_EVERY_REQUEST 是 True
,则会在每个请求上发送会话 cookie。
类似地,会话 cookie 的 expires
部分在每次发送会话 cookie 时都会更新。
如果响应状态代码为 500,会话不会被保存。
浏览器长度会话与持久会话
你可以使用 SESSION_EXPIRE_AT_BROWSER_CLOSE 设置来控制会话框架是否使用浏览器长度会话还是持久会话。
默认情况下, SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 False
,这意味着会话 cookie 将在用户的浏览器中存储,时间为 SESSION_COOKIE_AGE 的值。如果你不希望用户每次打开浏览器时都要登录,可以使用这个设置。
如果 SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 True
,Django 将使用浏览器长度的 cookie — 即当用户关闭浏览器时立即过期的 cookie。如果你希望用户每次打开浏览器时都需要登录,可以使用这个设置。
这个设置是全局默认设置,可以通过在每个会话级别上显式调用 request.session
的 set_expiry() 方法来覆盖,如上面在 在视图中使用会话 部分所述。
备注
一些浏览器(例如Chrome)提供了允许用户在关闭并重新打开浏览器后继续浏览会话的设置。在某些情况下,这可能会影响到 SESSION_EXPIRE_AT_BROWSER_CLOSE 设置,并阻止会话在关闭浏览器时过期。在测试启用了 SESSION_EXPIRE_AT_BROWSER_CLOSE 设置的 Django 应用程序时,请注意这一点。
清除会话存储
随着用户在你的网站上创建新的会话,会话数据可能会在你的会话存储中累积。如果你使用的是数据库后端,django_session
数据库表会增长。如果你使用的是文件后端,你的临时目录会包含越来越多的文件。
要理解这个问题,考虑一下数据库后端的情况。当用户登录时,Django 会向 django_session
数据库表添加一行记录。每当会话数据发生更改时,Django 会更新此行记录。如果用户手动注销,Django 会删除这行记录。但如果用户没有注销,这行记录永远不会被删除。文件后端也有类似的过程。
Django 不会 自动清除过期的会话。因此,你需要定期清除过期的会话。Django 为此提供了一个清理管理命令:clearsessions。建议定期调用这个命令,例如作为每日的 cron 作业。
请注意,缓存后端不会受到这个问题的影响,因为缓存会自动删除过时的数据。同样,cookie 后端也不会受到影响,因为会话数据是由用户的浏览器存储的。
配置
一些 Django 设置 允许你控制会话的行为:
- SESSION_CACHE_ALIAS
- SESSION_COOKIE_AGE
- SESSION_COOKIE_DOMAIN
- SESSION_COOKIE_HTTPONLY
- SESSION_COOKIE_NAME
- SESSION_COOKIE_PATH
- SESSION_COOKIE_SAMESITE
- SESSION_COOKIE_SECURE
- SESSION_ENGINE
- SESSION_EXPIRE_AT_BROWSER_CLOSE
- SESSION_FILE_PATH
- SESSION_SAVE_EVERY_REQUEST
- SESSION_SERIALIZER
会话安全
站点内的子域名可以在整个域上为客户端设置 cookie。如果允许来自不受信任用户控制的子域的 cookie,这将可能导致会话固定。
例如,攻击者可以登录到 good.example.com
并获得他们账户的有效会话。如果攻击者控制 bad.example.com
,他们可以使用它来将他们的会话密钥发送给你,因为子域名被允许在 *.example.com
上设置 cookie。当你访问 good.example.com
时,你将以攻击者的身份登录,并可能不小心将你的敏感个人数据(如信用卡信息)输入到攻击者的帐户中。
另一个可能的攻击是,如果 good.example.com
将其 SESSION_COOKIE_DOMAIN 设置为 "example.com"
,这将导致该站点的会话 cookie 被发送到 bad.example.com
。
技术细节
- 当使用 JSONSerializer 时,会话字典接受任何可以进行 json 序列化的值。
- 会话数据保存在名为
django_session
的数据库表中。 - Django 只有它需要的时候才会发送 cookie 。如果你不想设置任何会话数据,它将不会发送会话 cookie 。
SessionStore
对象
在内部处理会话时,Django 使用相应会话引擎的会话存储对象。按照约定,会话存储对象类的名称为 SessionStore
,并位于由 SESSION_ENGINE 指定的模块中。
Django 中的所有 SessionStore
类都继承自 SessionBase 并实现了数据操作方法,具体如下:
exists()
create()
save()
delete()
load()
- clear_expired()
为了构建自定义的会话引擎或自定义现有的引擎,你可以创建一个继承自 SessionBase 或任何其他现有的 SessionStore
类的新类。
你可以扩展会话引擎,但通常在使用数据库后端的会话引擎时需要额外的努力(详见下一节的详细信息)。
扩展数据库后端的会话引擎
可以通过继承 AbstractBaseSession 和任何 SessionStore
类来创建一个基于 Django 中包含的数据库后端会话引擎(即 db
和 cached_db
)的自定义会话引擎。
AbstractBaseSession
和 BaseSessionManager
可以从 django.contrib.sessions.base_session
中导入,这样可以在 INSTALLED_APPS 中不包括 django.contrib.sessions
的情况下导入它们。
class base_session.AbstractBaseSession
抽象的基础会话模型。
session_key
主键。该字段本身最多可包含 40 个字符。当前的实现生成一个 32 个字符的字符串(由数字和小写 ASCII 字母的随机序列组成)。
session_data
一个包含编码和序列化的会话字典的字符串。
expire_date
一个指示会话过期时间的日期时间。
已过期的会话对用户不可用,但它们仍然可能存储在数据库中,直到运行 clearsessions 管理命令。
classmethod
get_session_store_class
()返回一个与此会话模型一起使用的会话存储类。
get_decoded
()返回解码后的会话数据。
解码由会话存储类执行。
你还可以通过继承 BaseSessionManager 来自定义模型管理器:
class base_session.BaseSessionManager
encode
(session_dict)将给定的会话字典序列化并编码为字符串后返回。
编码由与模型类相关联的会话存储类执行。
save
(session_key, session_dict, expire_date)保存提供的会话键的会话数据,如果数据为空,则删除该会话。
通过覆盖下面描述的方法和属性来实现对 SessionStore
类的定制:
class backends.db.SessionStore
实现基于数据库的会话存储。
classmethod
get_model_class
()如果需要,覆盖此方法以返回自定义的会话模型。
create_model_instance
(data)返回一个新的会话模型对象实例,该对象表示当前会话状态。
覆盖此方法可以在数据保存到数据库之前修改会话模型数据。
class backends.cached_db.SessionStore
实现了基于缓存的数据库后端的会话存储。
cache_key_prefix
用于构建缓存键字符串的会话键前缀。
例如
下面的示例展示了一个自定义的基于数据库的会话引擎,其中包括一个额外的数据库列来存储账户 ID(从而提供了查询账户的所有活动会话的选项):
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.contrib.sessions.base_session import AbstractBaseSession
from django.db import models
class CustomSession(AbstractBaseSession):
account_id = models.IntegerField(null=True, db_index=True)
@classmethod
def get_session_store_class(cls):
return SessionStore
class SessionStore(DBStore):
@classmethod
def get_model_class(cls):
return CustomSession
def create_model_instance(self, data):
obj = super().create_model_instance(data)
try:
account_id = int(data.get("_auth_user_id"))
except (ValueError, TypeError):
account_id = None
obj.account_id = account_id
return obj
如果你正在从 Django 内置的 cached_db
会话存储迁移到一个基于 cached_db
的自定义存储,你应该覆盖缓存键前缀以防止命名空间冲突:
class SessionStore(CachedDBStore):
cache_key_prefix = "mysessions.custom_cached_db_backend"
# ...
URL 中的会话 ID
Django 会话框架完全且仅基于 cookie。与 PHP 不同,它不会作为最后的手段将会话 ID 放在 URL 中。这是一个有意的设计决策。这种行为不仅会使 URL 变得难看,还会使你的网站容易受到通过”Referer”头部进行会话 ID 窃取的攻击。