如何使用session¶
Django全面支持匿名session。会话框架允许您以每个站点访问者为基础存储和检索
任
意数据。它将数据存储在服务器端并提取Cookie的发送和接收。Cookie包含会话ID -
不是数据本身(除非您使用基于cookie的后端)。
启用session¶
会话通过一个中间件来实现。
要启用会话功能,请执行以下操作:
编辑MIDDLEWARE
设置并确保它
含 'django.contrib.sessions.middleware.SessionMiddleware'
。settings.py
创建的默认值 已 激活。django-
admin startproject
SessionMiddleware
如果你不想使用会话,你还不如删除 SessionMiddleware
从线MIDDLEWARE
和'django.contrib.sessions'
从
你的INSTALLED_APPS
。它会为你节省一点点的开销。
配置session引擎¶
默认情况下,Django将会话存储在数据库中(使模
型 django.contrib.sessions.models.Session
)。虽然这很方便,但在某些设置中,将会话数据存
储在别处会更快,因此可以将Django配置为将会话数据存储在文件系统或缓存中。
使用数据库支持的session¶
如果您想使用数据库支持的会话,则需要添加 'django.contrib.sessions'
到您的INSTALLED_APPS
设
置中。
配置 完安装后,运行安装存储会话数据的单个数据库表。manage.py migrate
使用缓存session¶
为了获得更好的性能,您可能需要使用基于缓存的会话后端。
要使用Django的缓存系统存储会话数据,首先需要确保已经配置了缓存; 详细信息请参阅缓存文档。
警告
如果您使用Memcached缓存后端,则只应使用基于缓存的会话。本地内存高速缓存后端不会保留足够长的数据作为一个好选择,并且直接使用文件或数据库会话会更快,而不是通过文件或数据库高速缓存后端发送所有内容。此外,本地内存缓存后端不是多进程安全的,因此可能不是生产环境的理想选择。
如果您在中定义了多个缓存CACHES
,Django将使用默认缓存。要使用另一个缓存,请设置SESSION_CACHE_ALIAS
为该缓存的名称。
一旦你的缓存配置完毕,你有两个选择如何将数据存储在缓存中:
- 设置
SESSION_ENGINE
于"django.contrib.sessions.backends.cache"
一个简单的缓存会话存储。会话数据将直接存储在缓存中。但是,会话数据可能不是持久性的:如果缓存填满或缓存服务器重新启动,缓存数据可能会被逐出。 - 对于持久性缓存数据,请设置
SESSION_ENGINE
为"django.contrib.sessions.backends.cached_db"
。这使用直写式高速缓存 - 每写入高速缓存也将写入数据库。如果数据尚未存在于缓存中,则会话读取仅使用数据库。
两个会话存储都非常快,但简单缓存更快,因为它忽略了持久性。在大多数情况下,cached_db
后端速度会很快,但如果您需要最后一点的性能,并且愿意让会话数据不时被删除,则cache
后端会为您提供帮助。
如果您使用cached_db
会话后端,则还需要遵循使用数据库支持的会话的配置说明。
使用基于文件的session¶
要使用基于文件的会话,请将SESSION_ENGINE
设置设置为 "django.contrib.sessions.backends.file"
。
您可能还想设置该SESSION_FILE_PATH
设置(tempfile.gettempdir()
很可能是默认输出/tmp
)来控制Django存储会话文件的位置。务必检查您的Web服务器是否有权读取和写入此位置。
使用基于cookie的session¶
要使用基于cookies的会话,请将SESSION_ENGINE
设置设置为"django.contrib.sessions.backends.signed_cookies"
。会话数据将使用Django的加密签名 和SECRET_KEY
设置工具进行存储。
注意
建议您将SESSION_COOKIE_HTTPONLY
设置保留True
为禁止访问来自JavaScript的存储数据。
警告
如果SECRET_KEY没有保密并且正在使用 PickleSerializer
,则可能导致任意远程代码执行。
拥有该攻击者SECRET_KEY
不仅可以生成伪造的会话数据,您的站点将信任该数据,还可以远程执行任意代码,因为数据是使用pickle序列化的。
如果您使用基于cookie的会话,请特别注意您的密钥始终保持完全保密,以供任何可能远程访问的系统使用。
会话数据已签名但未加密
当使用cookies后端时,会话数据可以被客户端读取。
MAC(消息认证码)用于保护数据免受客户端的改变,以便会话数据在被篡改时将失效。如果存储cookie的客户端(例如您的用户的浏览器)无法存储所有会话cookie并丢弃数据,则会发生相同的失效。尽管Django压缩数据,但仍然完全有可能超过每个cookie 4096个字节的常见限制。
没有新鲜度保证
还要注意的是,尽管MAC可以保证数据的真实性(数据是由您的网站而不是其他人生成的)和数据的完整性(数据的完整性和正确性),但它不能保证数据的真实性,即你正在被送回你发给客户的最后一件东西。这意味着对于会话数据的某些用途,cookie后端可能会让您重新开始重放攻击。与其他会话后端不同,后者保留每个会话的服务器端记录,并在用户注销时使其失效,当用户注销时,基于cookie的会话不会失效。因此,如果攻击者窃取用户的cookie,即使用户注销,他们也可以使用该cookie作为该用户登录。如果Cookie比您的年龄大,Cookie只会被检测为“陈旧”SESSION_COOKIE_AGE
。
性能
在视图中使用会话¶
当SessionMiddleware
被激活时,每个HttpRequest
对象 - 任何Django视图函数的第一个参数 - 都会有一个session
属性,它是一个类似字典的对象。
您可以阅读它并request.session
在您的视图中随时写信。您可以多次编辑它。
-
类
-
这是所有会话对象的基类。它有以下标准字典方法:
-
例:
fav_color = request.session['fav_color']
__getitem__
( 重点 ) ¶-
例:
request.session['fav_color'] = 'blue'
__setitem__
( key , value ) ¶-
例如:。 如果给定尚未在会话中,则会引发此问题。
delrequest.session['fav_color']
KeyError
key
__delitem__
( 重点 ) ¶-
例:
'fav_color' in request.session
__contains__
( 重点 ) ¶-
例:
fav_color = request.session.get('fav_color', 'red')
get
( 键 , 默认=无 ) ¶-
例:
fav_color = request.session.pop('fav_color', 'blue')
pop
( key , default = __ not_given ) ¶keys
() ¶items
() ¶setdefault
() ¶clear
() ¶它也有这些方法:
-
从会话中删除当前会话数据并删除会话cookie。如果要确保先前的会话数据不能从用户的浏览器再次访问(例如,该
django.contrib.auth.logout()
函数调用它),则使用此 方法。
flush
() ¶-
设置测试Cookie以确定用户的浏览器是否支持Cookie。由于Cookie的工作方式,您将无法在用户的下一页请求之前对其进行测试。有关更多信息,请参阅下面的设置测试Cookie
set_test_cookie
() ¶-
返回
True
或者False
,取决于用户的浏览器是否接受测试cookie。由于Cookie的工作方式,您必须拨打set_test_cookie()
先前的单独页面请求。有关更多信息,请参阅下面的设置测试Cookie
test_cookie_worked
() ¶-
删除测试cookie。用它来清理你自己。
delete_test_cookie
() ¶-
设置会话的到期时间。您可以传递许多不同的值:
- 如果
value
是一个整数,那么会话将在多秒钟不活动之后过期。例如,调用request.session.set_expiry(300)
会使会话在5分钟内过期。 - 如果
value
是adatetime
或timedelta
object,会话将在该特定日期/时间到期。请注意,datetime
和timedelta
值仅如果您使用的是序列化PickleSerializer
。 - 如果
value
是0
,用户的会话cookie将在用户的Web浏览器关闭时过期。 - 如果
value
是None
,会话将恢复为使用全局会话到期策略。
阅读会话不被视为有效到期的活动。会话过期是从上次会话被修改时计算出来的。
- 如果
set_expiry
( 价值 ) ¶-
返回此会话到期之前的秒数。对于没有自定义到期的会话(或设置为在浏览器关闭时过期的会话),这将相等
SESSION_COOKIE_AGE
。该函数接受两个可选的关键字参数:
modification
:作为datetime
对象的会话的最后修改 。默认为当前时间。expiry
:会话的过期信息,作为datetime
对象,int
(以秒为单位)或None
。set_expiry()
如果有,则默认为存储在会话中的值None
。
get_expiry_age
() ¶-
返回此会话过期的日期。对于没有自定义过期的会话(或设置为在浏览器关闭时过期的会话),这将等于
SESSION_COOKIE_AGE
从现在起的秒数。这个函数接受和。一样的关键字参数
get_expiry_age()
。
get_expiry_date
() ¶-
返回
True
或者False
,取决于用户的Web浏览器关闭时用户的会话Cookie是否过期。
get_expire_at_browser_close
() ¶-
从会话存储中删除过期的会话。这个类的方法被调用
clearsessions
。
clear_expired
() ¶-
在保留当前会话数据的同时创建新的会话密钥。
django.contrib.auth.login()
称这种方法来减轻会话固定。
cycle_key
() ¶ -
backends.base.
SessionBase
¶
会话序列化¶
默认情况下,Django使用JSON序列化会话数据。您可以使用该 SESSION_SERIALIZER
设置来自定义会话序列化格式。即使在编写自己的序列化程序中描述的警告中,我们强烈建议坚持使用JSON序列化,特别是如果您使用cookie后端。
例如,如果您使用pickle
序列化会话数据,则会出现一个攻击情形。如果您使用签名cookie会话后端且SECRET_KEY
被攻击者知晓(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编码的数据,例如
'\xd9'
(所提出的UnicodeDecodeError
)非UTF8字节的 数据。请参阅编写您自己的序列化程序部分以获取有关JSON序列化限制的更多详细信息。
serializers.
JSONSerializer
¶
-
类
-
支持任意Python对象,但是,如上所述,如果
SECRET_KEY
攻击者知道,则可能导致远程执行代码漏洞。
serializers.
PickleSerializer
¶
编写你自己的序列化程序¶
请注意,不像PickleSerializer
,JSONSerializer
不能处理任意的Python数据类型。通常情况下,便利性和安全性之间有一个折衷。如果你想保存更高级的数据类型,包括datetime
和Decimal
在JSON支持会话,您将需要编写自定义序列(或将它们存储在之前这些值转换成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.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()
实现。
设置测试cookie¶
为了方便起见,Django提供了一种简单的方法来测试用户的浏览器是否接受cookie。只需在视图中调用set_test_cookie()
方法 request.session
,然后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')
使用视图之外的会话¶
注意
本节中的示例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
注意
某些浏览器(例如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设置可以让您控制会话行为:
会话安全¶
站点内的子域可以在客户端上为整个域设置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
或者在使用时使用 任何可拣配的Python对象PickleSerializer
。有关pickle
更多信息,请参阅 模块。 - 会话数据存储在名为的数据库表中
django_session
。 - 如果需要的话,Django只发送一个cookie。如果您未设置任何会话数据,则不会发送会话Cookie。
该SessionStore
对象¶
在内部使用会话时,Django使用来自相应会话引擎的会话存储对象。按照惯例,会话存储对象类被命名SessionStore
并位于由指定的模块中 SESSION_ENGINE
。
SessionStore
Django中可用的所有类都继承 SessionBase
并实现数据操作方法,即:
exists()
create()
save()
delete()
load()
clear_expired()
为了构建自定义会话引擎或定制现有会话引擎,您可以创建一个从SessionBase
其他类继承的新SessionStore
类。
扩展大部分会话引擎非常简单,但在数据库支持的会话引擎中这样做通常需要一些额外的工作(详情请参阅下一节)。
扩展数据库支持的会话引擎¶
创建一个基于Django(即db
和cached_db
)包含的自定义数据库支持的会话引擎可以通过继承AbstractBaseSession
和任一SessionStore
类来完成 。
AbstractBaseSession
并BaseSessionManager
从导入的 django.contrib.sessions.base_session
,以便他们可以在不包括进口django.contrib.sessions
在INSTALLED_APPS
。
-
类
-
抽象的基础会话模型。
-
首要的关键。该字段本身最多可以包含40个字符。当前实现生成一个32个字符的字符串(一个随机的数字和小写ASCII字母序列)。
session_key
¶-
包含编码和序列化会话字典的字符串。
session_data
¶-
指定会话何时到期的日期时间。
过期的会话对用户不可用,但是,在
clearsessions
管理命令运行之前,它们仍可能存储在数据库中。
expire_date
¶-
classmethod
-
返回将用于此会话模型的会话存储类。
get_session_store_class
() ¶-
返回解码的会话数据。
解码由会话存储类执行。
get_decoded
() ¶ -
base_session.
AbstractBaseSession
¶
您也可以通过继承来定制模型管理器 BaseSessionManager
:
-
类
-
-
返回序列化并编码为字符串的给定会话字典。
编码由绑定到模型类的会话存储类执行。
encode
( session_dict ) ¶-
保存提供的会话密钥的会话数据,或者在数据为空时删除会话。
save
( session_key , session_dict , expire_date ) ¶ -
base_session.
BaseSessionManager
¶
SessionStore
通过覆盖下面描述的方法和属性来实现类的定制:
-
类
-
实现数据库支持的会话存储。
-
classmethod
-
如果需要,则覆盖此方法以返回自定义会话模型。
get_model_class
() ¶-
返回表示当前会话状态的会话模型对象的新实例。
重写此方法可以在将会话模型数据保存到数据库之前修改会话模型数据。
create_model_instance
( 数据 ) ¶ -
backends.db.
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'
# ...
URL中的会话ID¶
Django会话框架完全且完全基于cookie。作为PHP的最后手段,它不会退回到将URL中的会话ID。这是一个有意的设计决定。这种行为不仅会使网址变得丑陋,而且会让您的网站容易受到通过“Referer”头的session id窃取