如何使用会话sessoins
- 如何使用sessions
- 启用会话(Enabling sessions)
- 配置会话引擎(Configuring the session engine)
- 使用数据库后端会话(Using database-backed sessions)
- 使用缓存后端会话
- 使用基于文件的会话
- 使用基于cookie会话
- 在视图中使用会话
- 会话序列化
- 捆绑序列化器(Bundled serializers)
- 编写自己的序列化器
- 会话对象的使用指导(Session object guidelines)
- 设置测试 cookies
- 在视图外使用sessions
- 什么时候保存sessions.
- 会话存储对象(the sessionstore object)
- 扩展数据库后端会话引擎(Extending database-backed session engines)
- url中的会话id
如何使用sessions
Django完全支持匿名会话.会话框架允许您在每个站点访问者的基础上存储和检索任意数据.它将数据存储在服务器端,并对cookie的发送和接收进行抽象。cookie包含一个会话ID——而不是数据本身(除非使用基于cookie的后端)。
启用会话(Enabling sessions)
会话是通过中间件middleware实现的
要启用会话功能,请执行以下操作:
- 编辑中间件设置,并确保它包含“django.contrib.sessions.middleware.SessionMiddleware”。由django-admin startproject创建的默认settings.py激活了会话中间件。
如果您不想使用会话,您还可以从MIDDLEWARE中删除SessionMiddleware行和INSTALLED_APPS中删除django.contrib.sessions,这将为您节省一点开销。
配置会话引擎(Configuring the session engine)
默认情况下,Django将会话存储在数据库中(使用模型Django .contrib.sessions.models. session)。虽然这很方便,但是在某些设置中,在其他地方存储会话数据更快,因此Django可以配置为在文件系统或缓存中存储会话数据。
配置好安装之后,运行management .py迁移来安装存储会话数据的单个数据库表
使用数据库后端会话(Using database-backed sessions)
如果希望使用数据库后端会话,需要在INSTALLED_APPS设置里添加django.contrib.sessions。
配置好安装之后,运行management .py迁移来安装存储会话数据的单个数据库表
使用缓存后端会话
为了获得更好的性能,您可能需要使用基于缓存的会话后端。
要使用Django的缓存系统存储会话数据,首先需要确保配置了缓存;有关详细信息,请参阅缓存文档 cache documentation
注意
只有在使用Memcached缓存后端时,才应该使用基于缓存的会话。本地内存缓存后端保存数据的时间不够长,不足以成为一个好的选择,而且直接使用文件或数据库会话比通过文件或数据库缓存后端发送所有内容更快。此外,本地内存缓存后端不是多进程安全的,因此可能不适合生产环境
如果在缓存中定义了多个缓存,Django将使用默认缓存。要使用另一个缓存,请将SESSION_CACHE_ALIAS设置为该缓存的名称。
一旦配置了缓存,就有两种选择来选择如何在缓存中存储数据
-
将SESSION_ENGINE设置为“django.contrib.sessions.backends”。用于一个简单的缓存会话存储。会话数据将直接存储在缓存中。然而,会话数据可能不是持久性的:缓存的数据可以在缓存填满或缓存服务器重新启动时被删除
-
对于持久缓存的数据,将SESSION_ENGINE设置为“django.contrib.sessions.backends.cached_db”。这使用了一个write-through缓存——对缓存的每次写入也将写入数据库。会话读取仅在缓存已经不存在该会话数据时才使用数据库。
这两个会话存储都非常快,但是简单缓存更快,因为它不考虑持久性。在大多数情况下,cached_db后端足够快,但是如果您需要最后一点性能,并且愿意不时地删除会话数据,那么缓存后端(cache backend)就是为您准备的
如果使用cached_db会话后端,还需要遵循使用数据库支持的会话(using database-backed sessions)的配置说明
使用基于文件的会话
要使用基于文件的会话,请将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不保持机密,而您正在使用PickleSerializer,这可能导致任意远程代码执行.
拥有SECRET_KEY的攻击者不仅可以生成伪造的会话数据(站点将信任这些数据),还可以远程执行任意代码,因为数据是使用pickle序列化的.
如果您使用基于cookie的会话,请格外注意您的密钥对任何能是远程访问的系统总是完全保密。
会话数据已签名,但未加密
当使用cookie后端时,客户端可以读取会话数据。
MAC(消息身份验证代码)用于保护数据不受客户机更改的影响,以便会话数据在被篡改时失效。如果存储cookie的客户机(例如用户的浏览器)不能存储所有会话cookie并删除数据,也会发生同样的失效。即使Django压缩了数据,仍然完全有可能超过每个cookie 4096字节的常见限制
不要求最新有效
还请注意,而MAC可以保证数据的真实性(这是由你的网站,而不是别人),和数据的完整性(它是所有正确的),它不要求最新的有效才是有效的,即被送回来是你发送给客户端的的最近消息事件。这意味着,对于某些会话数据的使用,cookie后端使你遭受重播攻击。不像其他会话后端保存每个会话的服务器端记录并在用户注销时使其失效,基于cookie的会话在用户注销时不会失效。因此,如果攻击者窃取了用户的cookie,即使用户注销,他们也可以使用该cookie作为该用户登录。cookie只有在比您的SESSION_COOKIE_AGE更老时才会被检测为“陈旧”。
性能
最后,cookie的大小会影响站点的速度。
在视图中使用会话
当SessionMiddleware被激活时,每个HttpRequest对象(任何Django视图函数的第一个参数)都将有一个会话属性,这是一个类字典的对象
你可以在您的视图中的任何位置读写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’]. 如果给定的键不在会话中,则会引发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。用这个来清洁自己 -
set_expiry(value)
设置会话的过期时间。您可以传递许多不同的值
如果value是整数,会话将在那么多秒不活动之后过期。例如,调用request.session. set_expires(300)将使会话在5分钟内过期
如果值是datetime或timedelta对象,会话将在指定的日期/时间过期。注意,只有在使用PickleSerializer时,datetime和timedelta值才是可序列化的
如果值为0,则当关闭用户的Web浏览器时,用户的会话cookie将过期。
如果值为空(None),会话将返回到使用全局会话过期策略。
出于过期目的,读取会话不被视为活动。会话过期从最后一次修改会话开始计算 -
get_expiry_age()
返回此会话过期前的秒数。对于没有自定义过期的会话(或那些设置为在浏览器关闭时过期的会话),这将等于SESSION_COOKIE_AGE
此函数接受两个可选关键字参数:
modification:会话的最后修改时间是datetime对象。默认为当前时间
expiry:会话的过期信息,如datetime对象、int(以秒为单位)或None。默认值为set_expires()存储在会话中的值(如果有一个,或者没有) -
get_expiry_date()
返回此会话将过期的日期。对于没有自定义过期的会话(或那些设置为在浏览器关闭时过期的会话),这将等于从现在开始的日期到SESSION_COOKIE_AGE 秒数。
这个函数接受与get_expiry_age()相同的关键字参数 -
get_expire_at_browser_close()
返回True或False,这取决于在关闭用户的Web浏览器时,用户的会话cookie是否会过期 -
clear_expired()
从会话存储区中移除过期的会话。这个类方法由clearsessions调用 -
cycle_key()
创建一个新的会话密钥,同时保留当前会话数据。login()调用此方法来防止会话固定。
会话序列化
默认情况下,Django使用JSON序列化会话数据。您可以使用SESSION_SERIALIZER设置来定制会话序列化格式。即使有**Write your own serializer**中描述的注意事项,我们仍然强烈建议坚持使用JSON序列化,尤其是在使用cookie后端时。
例如,如果您使用pickle来序列化会话数据,下面是一个攻击场景。如果您正在使用签名cookie会话后端,并且攻击者知道SECRET_KEY (Django中没有导致泄漏的固有漏洞),那么攻击者可以将一个字符串插入到他们的会话中,反序列化时,该会话将在服务器上执行任意代码。这样做的技术很简单,在互联网上很容易找到。尽管cookie会话存储对cookie存储的数据进行签名以防止篡改,但SECRET_KEY泄漏立即升级为远程代码执行漏洞。
捆绑序列化器(Bundled serializers)
class serializers.JSONSerializer
来自django.core. signing的JSON序列化器的包装器。只能序列化基本数据类型.
另外,由于JSON只支持字符串键,请注意在请求中使用非字符串键。会话不会像预期的那样工作
>>> # 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序列化的限制的详细信息,请参阅“编写自己的序列化器”一节
class serializers.PickleSerializer
支持任意Python对象,但是,如上所述,如果攻击者知道SECRET_KEY,则可能导致远程代码执行漏洞
编写自己的序列化器
注意,与PickleSerializer不同,JSONSerializer不能处理任意Python数据类型。通常情况下,在方便和安全之间存在权衡。如果希望在JSON支持的会话中存储更高级的数据类型,包括datetime和Decimal,则需要编写一个定制的序列化器(或者在将这些值存储在request.session中之前将其转换为JSON序列化对象)。虽然序列化这些值相当简单(DjangoJSONEncoder可能会有帮助),但是编写一个能够可靠地返回您输入的相同内容的解码器则更加脆弱。例如,您可能会返回一个datetime,而这个datetime实际上是一个字符串,恰好与为datetimes选择的格式相同)。
序列化器类必须实现两个方法,dump (self, obj)和load (self, data),分别对会话数据字典进行序列化和反序列化
会话对象的使用指导(Session object guidelines)
- 在request.session上使用普通Python字符串作为字典键。这更像是一种惯例,而不是硬性规定
- 以下划线开头的会话字典键保留给Django内部使用
- 不要用一个新对象覆盖request.session,并且不访问或设置其属性。只需像使用Python字典一样使用它
例如:
这个简单的视图在用户发布评论后将has_comments变量设置为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.")
上面登入用户退出:
def logout(request):
try:
del request.session['member_id']
except KeyError:
pass
return HttpResponse("You're logged out.")
设置测试 cookies
标准的django.contrib.auth.logout()函数实际上要做的比这更多一些,以防止意外的数据泄漏。它调用request.session的flush()方法。我们使用这个例子来演示如何使用会话对象,而不是完整的logout()实现。
为了方便起见,Django提供了一种简单的方法来测试用户的浏览器是否接受cookie。只需调用请求的set_test_cookie()方法。在随后的视图中调用test_cookie_working()——不是在同一个视图调用中。
由于cookie的工作方式,set_test_cookie()和test_cookie_working()之间的这种尴尬分割是必要的。当您设置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')
在视图外使用sessions
提示:
下面的例子是直接从django.contrib.sessions.backends.db 后端导入SessionStore对象。在你自已的代码中,应该确认SessionStore对象是从自已设计中用的SESSION_ENGINE中设置的session engine中导入的,就像下面:
>>> from importlib import import_module
>>> from django.conf import settings
>>> SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
一个方便从在视图外操作session数据的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(也就是说,不是从会话存储区加载的,session_key=None). **save()**是用于保存一个存在的session(如,一个从会话存储区加载的)。在一个新的session上调用save()该方法也可以正常工作,不过在生成session_key方面的一点点不同,session_key可能会出现冲突。**create()**调用save()并循环,直到生成一个未使用的session_key。
如果你用的是 django.contrib.sessions.backends.db 后端,那个每个session都是一个正常的Django model.这个Session model在django/contrib/sessions/models.py中定义。由于是正常的model,所以你可以用丄常的Django 数据库API来访问sessions.
>>> 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()来获得session字典。因为session字典以一定的编码格式存储:
>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}
什么时候保存sessions.
Django默认只会在session的值被修改时(也就是说他的任一字典的值被赋值或删除)才会保存session到数据库:
# Session is modified.
request.session['foo'] = 'bar'
# Session is modified.
del request.session['foo']
# Session is modified.
request.session['foo'] = {}
# 此处的Session没有被修改,因为修改的是request.session['foo'], 而不是request.session。
# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'
对于上面的例子,最后一个情况,可以通过设置session对象的modified这个属性,我们明确的告诉session对象已经被修改了:
request.session.modified = True
为了改变这个默认的行为,在setting中设置 SESSION_SAVE_EVERY_REQUEST 配置为True.当配置为True时,Django将在每次请求时保存会话到数据库中。
同理,每次发送会话cookie时,都会更新会话cookie的有效期。
如果响应的状态码是500的话,会话不会被保存。
当用户访问你的网站,创建新会话时,会话数据会在会话存储中累积。如果使用数据库后端,django_session数据库表的记录将会增长。如果使用文件后端,则临时目录将包含越来越多的文件。
要理解这个问题,请考虑数据库后端发生了什么.当用户登录时,Django向django_session数据库表添加一行。Django在每次会话数据更改时更新这一行。如果用户手动退出,Django将删除该行。但如果用户没有注销,则永远不会删除该行。文件后端也会发生类似的过程。
Django不提供自动清除过期会话的功能。因此,定期清除过期的会话是您的工作。Django为此提供了一个清理管理命令:clearsessions。建议定期调用此命令,例如作为日常cron作业
注意,缓存后端不会受到这个问题的影响,因为缓存会自动删除陈旧数据。cookie后端也不会,因为会话数据是由用户的浏览器存储的
Settings
Django settings 为我们提供了一些控制会话的行为。
- 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
会话安全(Session security)
一个站点的子域名是可以为整个域名在客户端设置cookies。如果cookies的使用来自不受信任用户控制的子域,这使得会话固定成为可能
比如,如果攻击者可以登入 good.example.com ,且获得了他们帐户的合法session. 如果攻击者已经控制住了bad.example.com,由于一个站点的子域名是可以为整个域名 ** .example.com在客户端设置cookies, 他们可以使用它发送session key给你.当你访问good.example.com*,你将以攻击者的身份登入,且可能无意输入个人敏感信自(如,信用卡信息)到攻击者的帐户里。
技术细节
- session字典接受任何使用JSONSerializer序列化的json数据或任何用 PickleSerializer序列化的对象。具体请看pickle模块信息
- session data 保存在名为django_session的数据库表中
- Django 只在需要的时候发送一个cookie.如果你没有设置任何session data,那么就不会发送session cookie.
会话存储对象(the sessionstore object)
在内部处理会话时,Django使用来自相应会话引擎的会话存储对象。按照惯例,会话存储对象类名为SessionStore,位于SESSION_ENGINE指定的模块中
Django中所有可用的SessionStore类都继承自SessionBase并实现数据操作方法,即
- exists()
- create()
- save()
- delete()
- load()
- clear_expired()
为了构建自定义会话引擎或自定义现有的会话引擎,您可以创建一个继承自SessionBase或任何其他现有SessionStore类的新类
扩展大多数会话引擎非常简单,但是使用数据库支持的会话引擎通常需要一些额外的工作(有关详细信息,请参阅下一节)
扩展数据库后端会话引擎(Extending database-backed session engines)
可以通过继承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.SessionStor
实现数据库支持的会话存储
- 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的攻击