Django完全支持匿名会话。会话框架允许您在每个站点访问者的基础上存储和检索任意数据。它将数据存储在服务器端,并抽象发送和接收cookie。cookie包含会话ID——而不是数据本身(除非您使用 cookie based backend )
会话通过一个 middleware .
要启用会话功能,请执行以下操作:
编辑 MIDDLEWARE
设置并确保它包含 'django.contrib.sessions.middleware.SessionMiddleware'
.违约 settings.py
创建的 django-admin startproject
有 SessionMiddleware
激活。
如果不想使用会话,您还可以删除 SessionMiddleware
线从 MIDDLEWARE
和 'django.contrib.sessions'
从你 INSTALLED_APPS
. 这样可以节省一点开销。
默认情况下,Django将会话存储在数据库中(使用模型 django.contrib.sessions.models.Session
)虽然这很方便,但在某些设置中,将会话数据存储在其他位置会更快,因此可以将django配置为将会话数据存储在文件系统或缓存中。
如果要使用数据库支持的会话,则需要添加 'django.contrib.sessions'
对你 INSTALLED_APPS
设置。
配置好安装后,运行 manage.py migrate
安装存储会话数据的单个数据库表。
为了获得更好的性能,您可能需要使用基于缓存的会话后端。
要使用django的缓存系统存储会话数据,首先需要确保已配置缓存;请参见 cache documentation 有关详细信息。
警告
如果您使用的是Memcached或Redis缓存后端,则应该只使用基于缓存的会话。本地内存缓存后端不会将数据保留足够长的时间,因此不是一个好的选择,而且直接使用文件或数据库会话会比通过文件或数据库缓存后端发送所有内容更快。此外,本地内存缓存后端不是多进程安全的,因此可能不是生产环境的好选择。
如果在中定义了多个缓存 CACHES
,django将使用默认缓存。要使用另一个缓存,请设置 SESSION_CACHE_ALIAS
到缓存的名称。
一旦配置了缓存,您就必须在数据库支持的缓存和非持久缓存之间进行选择。
缓存的数据库后端 (cached_db
)使用直写缓存--会话写入按该顺序同时应用于数据库和缓存。如果写入缓存失败,则通过 sessions logger 以避免使原本成功的写入操作失败。
添加了对写入缓存时异常的处理和日志记录。
会话读取使用缓存,如果数据已从缓存中逐出,则使用数据库。要使用此后端,请设置 SESSION_ENGINE
至 "django.contrib.sessions.backends.cached_db"
,并按照的配置说明操作 using database-backed sessions 。
缓存后端 (cache
)仅在缓存中存储会话数据。这样做速度更快,因为它避免了数据库持久化,但您必须考虑当缓存数据被逐出时会发生什么。如果缓存已满或缓存服务器重新启动,可能会发生驱逐,这将意味着会话数据丢失,包括注销用户。要使用此后端,请设置 SESSION_ENGINE
至 "django.contrib.sessions.backends.cache"
。
缓存后端可以通过使用持久化缓存来实现持久化,比如配置适当的Redis。但是,除非您的缓存确实配置了足够的持久性,否则请选择缓存数据库后端。这避免了由于生产中不可靠的数据存储而导致的边缘情况。
要使用基于文件的会话,请设置 SESSION_ENGINE
设置为 "django.contrib.sessions.backends.file"
.
您可能还希望将 SESSION_FILE_PATH
设置(默认为从以下位置输出 tempfile.gettempdir()
,最有可能 /tmp
)来控制Django存储会话文件的位置。请确保您的Web服务器具有对此位置的读写权限。
什么时候? SessionMiddleware
激活,每个 HttpRequest
对象(任何Django视图函数的第一个参数)将具有 session
属性,它是类似字典的对象。
你可以读也可以写 request.session
在你看来的任何一点上。您可以多次编辑它。
这是所有会话对象的基类。它有以下标准字典方法:
例子: fav_color = request.session['fav_color']
例子: request.session['fav_color'] = 'blue'
例子: del request.session['fav_color']
. 这提出 KeyError
如果给定 key
尚未在会话中。
例子: 'fav_color' in request.session
Asynchronous version : aget()
例子: fav_color = request.session.get('fav_color', 'red')
aget()
添加了功能。
示例: await request.session.aset('fav_color', 'red')
Asynchronous version : aupdate()
示例: request.session.update({'fav_color': 'red'})
aupdate()
添加了功能。
Asynchronous version : apop()
例子: fav_color = request.session.pop('fav_color', 'blue')
apop()
添加了功能。
Asynchronous version : akeys()
akeys()
添加了功能。
Asynchronous version : avalues()
avalues()
添加了功能。
Asynchronous version : ahas_key()
ahas_key()
添加了功能。
Asynchronous version : aitems()
aitems()
添加了功能。
Asynchronous version : asetdefault()
asetdefault()
添加了功能。
它还具有以下方法:
Asynchronous version : aflush()
从会话中删除当前会话数据并删除会话cookie。如果要确保无法从用户浏览器再次访问上一个会话数据(例如, django.contrib.auth.logout()
函数调用它)。
aflush()
添加了功能。
Asynchronous version : aset_test_cookie()
设置测试cookie以确定用户的浏览器是否支持cookie。由于cookies的工作方式,在用户的下一个页面请求之前,您将无法测试这个问题。见 Setting test cookies 详情请参见下文。
aset_test_cookie()
添加了功能。
Asynchronous version : atest_cookie_worked()
返回以下任一 True
或 False
取决于用户的浏览器是否接受测试Cookie。由于曲奇的工作方式,你必须打电话给 set_test_cookie()
或 aset_test_cookie()
在上一个单独的页面请求上。看见 Setting test cookies 有关更多信息,请点击下面的链接。
atest_cookie_worked()
添加了功能。
Asynchronous version : adelete_test_cookie()
删除测试cookie。用这个来清理你自己。
adelete_test_cookie()
添加了功能。
返回设置的值 SESSION_COOKIE_AGE
。这可以在自定义会话后端中被覆盖。
Asynchronous version : aset_expiry()
设置会话的过期时间。可以传递多个不同的值:
如果 value
是一个整数,会话将在数秒的不活动后过期。例如,调用 request.session.set_expiry(300)
将使会话在5分钟内过期。
如果 value
是一种 datetime
或 timedelta
对象,则会话将在该特定日期/时间到期。
如果 value
是 0
,则用户的会话Cookie将在用户的Web浏览器关闭时过期。
如果 value
是 None
,会话将恢复为使用全局会话到期策略。
出于过期目的,读取会话不被视为活动。会话过期是从上次会话 被改进的 .
aset_expiry()
添加了功能。
Asynchronous version : aget_expiry_age()
返回此会话过期前的秒数。对于没有自定义过期的会话(或设置为在浏览器关闭时过期的会话),这将等于 SESSION_COOKIE_AGE
.
此函数接受两个可选关键字参数:
modification
:会话的最后修改,作为 datetime
对象。默认为当前时间。
expiry
:会话的到期信息,作为 datetime
object, an int
(in seconds), or None
. Defaults to the value stored in the session by set_expiry()
/aset_expiry()
,如果有的话,或者 None
。
备注
会话后端使用此方法在保存会话时以秒为单位确定会话过期时间。它实际上并不打算在该上下文之外使用。
特别是,虽然它是 possible 确定会话的剩余生存期 just when 你答对了 modification
价值 and 这个 expiry
被设置为 datetime
对象,其中您确实拥有 modification
值,则手工计算到期时间更为直观:
expires_at = modification + timedelta(seconds=settings.SESSION_COOKIE_AGE)
aget_expiry_age()
添加了功能。
Asynchronous version : aget_expiry_date()
返回此会话的到期日期。对于没有自定义过期的会话(或设置为在浏览器关闭时过期的会话),这将等于 SESSION_COOKIE_AGE
几秒钟后。
此函数接受与相同的关键字参数 get_expiry_age()
,类似的用法说明也适用。
aget_expiry_date()
添加了功能。
Asynchronous version : aget_expire_at_browser_close()
返回以下任一 True
或 False
,取决于用户的会话Cookie在用户的Web浏览器关闭时是否会过期。
aget_expire_at_browser_close()
添加了功能。
Asynchronous version : aclear_expired()
从会话存储中删除过期的会话。类方法由调用 clearsessions
.
aclear_expired()
添加了功能。
Asynchronous version : acycle_key()
在保留当前会话数据的同时创建新会话密钥。 django.contrib.auth.login()
调用此方法以减轻会话固定。
acycle_key()
添加了功能。
默认情况下,Django使用JSON序列化会话数据。你可以使用 SESSION_SERIALIZER
用于自定义会话序列化格式的设置。即使有中描述的警告 编写自己的序列化程序 ,我们强烈建议坚持JSON序列化 尤其是在使用cookie后端时 .
例如,以下是一个攻击场景,如果使用 pickle
序列化会话数据。如果您正在使用 signed cookie session backend 和 SECRET_KEY
(或任何密钥 SECRET_KEY_FALLBACKS
)是攻击者所知道的(Django中没有导致泄漏的固有漏洞),攻击者可以将一个字符串插入到他们的会话中,当未选中该字符串时,将在服务器上执行任意代码。这样做的技术很简单,很容易在互联网上找到。尽管Cookie会话存储对Cookie存储的数据进行签名以防止篡改,但是 SECRET_KEY
泄漏会立即升级为远程代码执行漏洞。
JSON序列化程序的包装 django.core.signing
. 只能序列化基本数据类型。
此外,由于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中编码的数据,如非UTF8字节 '\xd9'
(提出 UnicodeDecodeError
)无法存储。
见 编写自己的序列化程序 有关JSON序列化限制的详细信息,请参阅。
请注意, JSONSerializer
无法处理任意的Python数据类型。就像经常发生的情况一样,在便利性和安全性之间存在权衡。如果您希望存储更高级的数据类型,包括 datetime
和 Decimal
在JSON支持的会话中,您将需要编写一个定制的序列化程序(或者在将这些值存储到 request.session
)。虽然序列化这些值通常很简单 (DjangoJSONEncoder
可能是有帮助的),编写一个解码器,可以可靠地得到你放入的相同的东西是更脆弱的。例如,您冒着返回一个 datetime
这实际上是一个字符串,其格式恰好与 datetime
S)。
序列化程序类必须实现两个方法, dumps(self, obj)
和 loads(self, data)
,分别对会话数据字典进行序列化和反序列化。
使用普通的python字符串作为字典键 request.session
. 这与其说是一个硬性的、快速的规则,不如说是一种惯例。
以下划线开头的会话字典键保留供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()
函数实际上做的比这多一点,以防止意外的数据泄漏。它叫 flush()
方法 request.session
. 我们使用这个例子来演示如何使用会话对象,而不是完整的 logout()
实施。
备注
本节中的示例导入 SessionStore
对象直接来自 django.contrib.sessions.backends.db
后端。在您自己的代码中,应该考虑导入 SessionStore
从会话引擎指定 SESSION_ENGINE
,如下:
>>> 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将在每次请求时发送。
同样, expires
每次发送会话cookie时都会更新会话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在用户关闭浏览器后立即过期。如果您希望人们每次打开浏览器时都必须登录,请使用此选项。
此设置是全局默认设置,可以通过显式调用 set_expiry()
方法 request.session
如上所述 using sessions in views .
备注
一些浏览器(例如Chrome)提供了一些设置,允许用户在关闭和重新打开浏览器后继续浏览会话。在某些情况下,这可能会干扰 SESSION_EXPIRE_AT_BROWSER_CLOSE
设置并防止会话在浏览器关闭时过期。请在测试Django应用程序时注意这一点 SESSION_EXPIRE_AT_BROWSER_CLOSE
设置已启用。
当用户在您的网站上创建新会话时,会话数据可以累积在会话存储中。如果您使用的是数据库后端, django_session
数据库表将增长。如果您使用的是文件后端,临时目录将包含越来越多的文件。
为了理解这个问题,考虑数据库后端会发生什么。当用户登录时,Django向 django_session
数据库表。每次会话数据更改时,Django都会更新此行。如果用户手动注销,Django将删除该行。但是如果用户这样做了 not 注销,该行永远不会被删除。文件后端也会发生类似的过程。
达吉奥 not 提供对过期会话的自动清除。因此,定期清除过期会话是您的工作。Django为此提供了一个清理管理命令: clearsessions
. 建议定期调用此命令,例如作为每日cron作业。
请注意,缓存后端不易受到此问题的影响,因为缓存会自动删除过时的数据。cookie后端也不是,因为会话数据由用户的浏览器存储。
少许 Django settings 允许您控制会话行为:
站点中的子域能够在客户端上为整个域设置cookie。如果允许来自不受信任用户控制的子域的cookie,这就使得会话固定成为可能。
例如,攻击者可以登录 good.example.com
并为其帐户获取有效会话。如果攻击者控制了 bad.example.com
,他们可以使用它向您发送会话密钥,因为允许子域名设置cookie *.example.com
.当您访问 good.example.com
,您将以攻击者身份登录,并且可能会无意中将您的敏感个人数据(例如信用卡信息)输入到攻击者的帐户中。
另一个可能的攻击是 good.example.com
设置其 SESSION_COOKIE_DOMAIN
到 "example.com"
这将导致来自该站点的会话cookie发送到 bad.example.com
.
会话词典接受任何 json
使用时可序列化的值 JSONSerializer
。
会话数据存储在名为 django_session
.
Django只在需要时发送一个cookie。如果不设置任何会话数据,则不会发送会话cookie。
SessionStore
对象¶在内部处理会话时,Django使用来自相应会话引擎的会话存储对象。按照惯例,会话存储对象类的名称为 SessionStore
位于模块中 SESSION_ENGINE
.
全 SessionStore
Django中可用的子类实现了以下数据操作方法:
exists()
create()
save()
delete()
load()
这些方法的异步接口是通过用 sync_to_async()
。如果有可用的异步本机实施,则可以直接实施:
aexists()
acreate()
asave()
adelete()
aload()
为了构建自定义会话引擎或自定义现有会话引擎,可以创建继承自的新类 SessionBase
或任何其他现有的 SessionStore
类。
您可以扩展会话引擎,但使用数据库支持的会话引擎这样做通常需要一些额外的工作(请参阅下一节了解详细信息)。
aexists()
, acreate()
, asave()
, adelete()
, aload()
,以及 aclear_expired()
增加了方法。
创建一个基于django中包含的自定义数据库支持的会话引擎(即 db
和 cached_db
)可以通过继承 AbstractBaseSession
而且 SessionStore
类。
AbstractBaseSession
和 BaseSessionManager
可从导入 django.contrib.sessions.base_session
以便进口时不包括 django.contrib.sessions
在里面 INSTALLED_APPS
.
抽象的基本会话模型。
主键。字段本身最多可包含40个字符。当前的实现生成32个字符串(数字和小写ASCII字母的随机序列)。
包含已编码和序列化会话字典的字符串。
指定会话到期时间的日期时间。
过期的会话对用户不可用,但是,在 clearsessions
管理命令已运行。
返回要与此会话模型一起使用的会话存储类。
返回已解码的会话数据。
解码由会话存储类执行。
您还可以通过子类化定制模型管理器。 BaseSessionManager
:
返回已序列化并编码为字符串的给定会话字典。
编码由绑定到模型类的会话存储类执行。
为提供的会话密钥保存会话数据,或者在数据为空时删除会话。
定制 SessionStore
类是通过重写下面描述的方法和属性来实现的:
实现数据库支持的会话存储。
如果需要,请重写此方法以返回自定义会话模型。
返回会话模型对象的新实例,该实例表示当前会话状态。
重写此方法提供了在会话模型数据保存到数据库之前对其进行修改的能力。
下面的示例显示了一个自定义的数据库支持会话引擎,该引擎包含一个额外的数据库列来存储帐户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"
# ...
Django会话框架完全且完全基于cookie。它不会像php那样回到把会话ID放在URL中作为最后手段。这是一个有意设计的决定。这种行为不仅使URL变得丑陋,而且还使您的站点容易通过“referer”头盗窃会话ID。
7月 22, 2024