Django会话
Django完全支持也匿名会话,简单说就是使用跨网页之间可以进行通讯,比如显示用户名,用户是否已经发表评论。session框架让你存储和获取访问者的数据信息,这些信息保存在服务器上(默认是数据库中),以 cookies 的方式发送和获取一个包含 session ID的值,并不是用cookies传递数据本身(除非使用了基于cookie的会话)。
启用session
想要启动Django的会话功能需要:
- 确保在
MIDDLEWARE
中添加了'django.contrib.sessions.middleware.SessionMiddleware'
- 在
INSTALLED_APPS
添加'django.contrib.sessions'
应用
默认创建的项目是包含这会话服务的,如果确定不需要使用会话功能,也可以移除这两项,为服务器减少开支。
配置session
默认情况下,Django会将会话存储在数据库中(使用模型django.contrib.sessions.models.Session
)虽然这很方便,但在某些设置中,将会话数据存储在其他位置的速度更快,因此可以将Django配置为在文件系统或缓存中存储会话数据。
使用数据库存储会话
这是默认设置,只要在INSTALLED_APPS
添加'django.contrib.sessions'
就可以。
执行manage.py migrate
命令时,就会在数据库创建会话表:
使用Cache存储会话
使用基于缓存后端的会话能获取更好的性能。首先确保项目设置好了缓存,缓存配置。
如果在CACHES
定义了多种缓存,Django会使用默认的缓存存储会话。可以用
SESSION_CACHE_ALIAS
指定使用的缓存后端。缓存配置完成后,有2中方式使用缓存存储会话:
- 简单直接地将
SESSION_ENGINE
设置为'django.contrib.sessions.backends.cache'
,这样会话会直接存储在缓存中。但是,会话数据可能不是持久性的:如果缓存填满或缓存服务器重新启动,缓存数据就会被逐出。 - 对于要持久性的缓存会话数据,将
SESSION_ENGIN
设置为'django.contrib.sessions.backends.cached_db'
,这使用了直写高速缓存:每次写入高速缓存的数据也将会写入数据库。如果数据已经不在缓存中,则只会用数据库读取会话。
两个会话存储都非常快,但使用简单的缓存更快,因为它忽略了持久性。在大多数情况下,cached_db后端速度足够快。但如果需要最后一点性能,并且愿意让会话数据不时被清除,则简单的缓存后端更适合。
如果使用cached_db会话后端,则还需要遵循使用数据库支持的会话的配置说明。
Warning
只有在使用基于Memcached
的缓存后端时,才能使用缓存存储会话。 数据在本地内存缓存后端保存的时间不够长,并且直接使用文件
或数据库
会话比通过文件或数据库缓存后端
发送所有内容会更快。 此外,本地内存缓存后端不是多进程安全的,因此可能不适合生产环境。
使用文件存储会话
使用基于文件的会话,需要将SESSION_ENIGN
设置为'django.contrib.sessions.backends.file'
。还可以设置SESSION_FILE_PATH
,来控制Django存储会话文件的位置,需要检查Web服务器是否有读取和写入该文件的权限。
使用cookie存储会话
使用基于cookie的会话,需要设置SESSION_ENIGN
设置为'django.contrib.sessions.backends.signed_cookies
。会话数据将使用Django的加密签名工具和SECRET_KEY设置进行存储。同时建议设置SESSION_COOKIE_HTTPONLY
为True,防止JS脚本访问存储的数据。
注意:如果SECRET_KEY
没有保密并且正在使用PickleSerializer
,则可能导致任意远程代码执行。
在视图函数中启用session
当SessionMiddleware
被激活时, 每个HttpRequest(每个视图函数的第一个参数)都会有一个类似python 字典表的session
属性。request.session
可以在视图中任何地方使用。
默认情况下,Django使用JSON格式序列化session。可以用SESSION_SERIALIZER
选择其他方式或是自定义的序列化方式,但强烈推荐使用默认的JSON方式。
# 创建或修改 session:
request.session[key] = value
# 获取 session:
request.session.get(key,default=None)
# 删除 session
del request.session[key] # 不存在时报错
session的属性和方法可以查看其基类。
session的使用指南:
- 在request.session上使用普通的Python字符串作为字典键。 这更像是一种惯例而非硬性规则。
- 以下划线开头的会话字典键保留供Django内部使用。
- 不要使用新对象覆盖request.session,也不要访问或设置其属性。 像Python字典一样使用它。
例如使用session来完成最简单的登录、退出功能:
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.")
而事实上标准的用户退出函数django.contrib.auth.logout()
也会调用request.session的flush
方法来清除会话中的数据和会话cookie。
测试cookie
Django 提供了一种非常简单方法来测试用户使用的浏览器是否支持使用cookie,
只要在在视图中调用request.session
的set_test_cookie
方法, 再在随后的视图(不是在同一次请求中)中调用test_cookie_worked
方法。
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')
当确认浏览器可以使用cookie后,删除这个测试用的cookie。
在视图函数外使用session
可以调用API来处理视图外的session:
>>> from django.conf import settings
>>> SessionStore=import_module(settings.SESSION_ENGINE).SessionStore
>>> s = SessionStore()
>>> s.session_key
>>> s['last_login'] = 123456
>>> s.create()
>>> s.session_key
'wcg2thkiwynu2b2ji013x340x61iaf2n'
>>> s_new = SessionStore(session_key='wcg2thkiwynu2b2ji013x340x61iaf2n')
>>> s_new['last_login']
123456
>>>
默认是使用数据库存储会话的,如果使用了其他方式,那么调用SessionStore
的方式需要根据SESSION_ENGINE
设置来。
SessionStore.create()
会创建一个新的会话,此前session_key
还是None,它会调用.save()
方法,而.save()
方法也会调用.create()
方法,它们循环互相调用,直到生成session_key
.
def create(self):
while True:
self._session_key = self._get_new_session_key()
try:
# Save immediately to ensure we have a unique entry in the
# database.
self.save(must_create=True)
# ...
def save(self, must_create=False):
"""
Save the current session data to the database. If 'must_create' is
True, raise a database error if the saving operation doesn't create a
new entry (as opposed to possibly updating an existing entry).
"""
if self.session_key is None:
return self.create()
data = self._get_session(no_load=must_create)
当使用数据库存储会话的时,Session
模型就存储在django/contrib/sessions/models.py
中,而且完全可以用数据库API将它当成普通的数据库模型对象来访问:
>>> s = Session.objects.get(pk='vs10cbje57bcfmbv44603jxgnujp9mg1')
>>> s.expire_date
datetime.datetime(2018, 11, 23, 12, 51, 24, 365464, tzinfo=<UTC>)
>>> s.session_data
'YmVmNGFmZmFhN2M2YzgxNWUwNTAyYTliZT...'
>>> s.get_decoded()
{'_language': 'en', '_auth_user_id': '1', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', ...}
>>>
使用get_decoded
获取解码后的数据。
会话何时进行存储
默认设置下,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
。若不想使用这种方法,可以在settings
中设置SESSION_SAVE_EVERY_REQUEST
,但注意这样设置后会在每次请求
时都发生会话cookie(原本只在会话创建或修改时发送),而且过期时间
也会随着会话的每次发送而更新。
当响应状态码为500 时,会话不会被保存。
会话保持时间
通过设置settings下的SESSION_EXPIRE_AT_BROWSER_CLOSE
参数,可以设置会话框架保存会话的时长,选择持续保存或是在维持直到浏览器关闭。默认设置是False,即会话会在浏览器中持续保存直到到达SESSION_COOKIE_AGE
指定的时间。这项设置是全局的默认能保持时间,可以使用在视图中使用.set_expiry(value)
覆盖。
某些浏览器(例如Chrome)提供的设置允许用户在关闭并重新打开浏览器后继续浏览会话。 在某些情况下,这可能会干扰SESSION_EXPIRE_AT_BROWSER_CLOSE
设置,并阻止会话在浏览器关闭时到期。 在测试启用了SESSION_EXPIRE_AT_BROWSER_CLOSE
设置的Django应用程序时请注意这一点
清除保存的会话
当用户在网站上创建新会话时,会话数据可能会累积在的会话存储中。如果正在使用数据库后端存储会话,则django_session
数据库表将增长。如果正在使用文件后端,那么临时目录下将包含越来越多的文件。
使用数据库作为会话后端时,当用户登录时,Django会在django_session数据库表中添加一行。每次会话数据更改时,Django都会更新此行。如果用户手动注销,Django将删除该行。但是如果用户没有退出,则该行永远不会被删除。文件后端也会发生类似的过程。
Django不会自动清除过期的会话的。因此,您的工作是定期清除过期的会话。 Django为此提供了一个清理管理命令:clearsessions
,建议定期调用此命令,它会清除那些超过有效期限的会话。注意,缓存后端不容易受到此问题的影响,因为缓存会自动删除过时数据。 Cookie后端也不是,因为会话数据是由用户的浏览器存储的。
会话相关的设置
在settings中与会话相关的设置,包括上文提到的:
SESSION_CACHE_ALIAS
:选择使用哪种缓存存储会话(使用缓存作为会话后端);SESSION_COOKIE_AGE
:会话保存时间,默认1209600秒(2周);SESSION_COOKIE_DOMAIN
:允许使用会话的域名,将此设置为字符串,例如example.com
用于跨域cookie,或者None为对标准域使用cookie。SESSION_COOKIE_HTTPONLY
:默认为True,客户端的js脚本无法访问会话cookie。SESSION_COOKIE_NAME
:会话cookie的名字,默认为sessionid
,可以随意设置,只要能与其他cookie区分;SESSION_COOKIE_PATH
:会话cookie上设置的路径。 这应该与Django安装的URL路径匹配,或者是该路径的父路径。如果您在同一主机名下运行多个Django实例,这将非常有用。 他们可以使用不同的cookie路径,每个实例只能看到自己的会话cookie。SESSION_COOKIE_SAMESITE
:会话cookie上的SameSite标志的值。 此标志可防止cookie在跨站点请求中发送,从而防止CSRF攻击并使某些方法无法窃取会话cookie,默认为'Lax'
,详情。SESSION_COOKIE_SECURE
:是否为会话cookie使用安全cookie,默认False,不使用。 如果将其设置为True,则cookie将被标记为“安全”,这意味着浏览器可以确保cookie仅在HTTPS连接下发送。保留此设置并不是一个好主意,因为攻击者可以使用数据包嗅探器捕获未加密的会话cookie,并使用cookie来劫持用户的会话。SESSION_ENGINE
:设置会话后端,可设置
'django.contrib.sessions.backends.db'
,默认
'django.contrib.sessions.backends.file'
'django.contrib.sessions.backends.cache'
'django.contrib.sessions.backends.cached_db'
'django.contrib.sessions.backends.signed_cookies'
SESSION_EXPIRE_AT_BROWSER_CLOSE
:默认False,设置浏览器关闭时会话是否马上过期。SESSION_FILE_PATH
: 使用文件后端存储会话,设置文件的位置,默认(None)SESSION_SAVE_EVERY_REQUEST
:是否每次请求时都保存会话,默认False;SESSION_SERIALIZER
:选择其他方式或是自定义的序列化方式,默认JSON;
会话安全
站点内的子域可以在客户端上为整个域设置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。