如何使用会话

Django完全支持匿名会话。 会话框架允许您以每个站点访问者为基础存储和检索任意数据。 它将数据存储在服务器端,并提取cookie的发送和接收。 Cookies包含一个会话ID,而不是数据本身(除非你使用基于cookie based backend)。

启用会话

会话通过一个middleware实现。

要启用会话功能,请执行以下操作:

  • 编辑MIDDLEWARE设置并确保它包含'django.contrib.sessions.middleware.SessionMiddleware' The default settings.py created by django-admin startproject has SessionMiddleware activated.

如果你不想使用会话,你可以从MIDDLEWARE'django.contrib.sessions'中删除SessionMiddleware你的INSTALLED_APPS 它会为你节省一点点的开销。

配置会话引擎

默认情况下,Django将会话存储在数据库中(使用模型django.contrib.sessions.models.Session)。 虽然这很方便,但是在某些设置中,将会话数据存储在别处会更快,所以可以将Django配置为将会话数据存储在文件系统或缓存中。

使用数据库支持的会话

如果要使用数据库支持的会话,则需要将'django.contrib.sessions'添加到INSTALLED_APPS设置中。

配置好安装后,运行manage.py 迁移来安装存储会话数据的单个数据库表。

使用缓存的会话

为了获得更好的性能,您可能需要使用基于缓存的会话后端。

要使用Django的缓存系统存储会话数据,首先需要确保已经配置了缓存;有关详细信息,请参阅cache documentation

警告

如果您使用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_ENGINE设置为"django.contrib.sessions.backends.file"

您可能还需要设置SESSION_FILE_PATH设置(默认从tempfile.gettempdir(),最可能是/tmp Django存储会话文件。 请务必检查您的Web服务器是否有权读取和写入此位置。

在视图中使用会话

SessionMiddleware被激活时,每个HttpRequest对象 - 任何Django视图函数的第一个参数 - 都有一个session属性,像对象一样。

你可以读取它,并在你的视图中的任何位置写入request.session 您可以多次编辑它。

backends.base。 SessionBase T0> ¶ T1>

这是所有会话对象的基类。 它有以下标准字典方法:

__的GetItem __ T0>(键 T1>)¶ T2>

示例:fav_color = request.session ['fav_color']

__ setitem __

示例:request.session ['fav_color'] = 'blue'

__ delitem __ T0>(键 T1>)¶ T2>

示例:del request.session ['fav_color'] 如果给定的key尚未在会话中,则会引发KeyError

__包含__ T0>(键 T1>)¶ T2>

例子:'fav_color' in request.session

getkeydefault = None

示例:fav_color = request.session.get('fav_color', 'red')

popkeydefault = __ not_given

示例:fav_color = request.session.pop('fav_color', 'blue') / T0>

键 T0>()¶ T1>
项 T0>()¶ T1>
setdefault T0>()¶ T1>
明确 T0>()¶ T1>

它也有这些方法:

冲洗 T0>()¶ T1>

从会话中删除当前会话数据并删除会话cookie。 如果要确保先前的会话数据不能从用户的浏览器再次访问(例如,django.contrib.auth.logout()函数调用它),则使用此方法。

设置测试cookie以确定用户的浏览器是否支持cookie。 由于Cookie的工作方式,您将无法在用户的下一页请求之前对其进行测试。 请参阅下面的设置测试cookie了解更多信息。

Returns either True or False, depending on whether the user’s browser accepted the test cookie. 由于Cookie的工作方式,您必须在以前的单独页面请求中调用set_test_cookie() 请参阅下面的设置测试cookie了解更多信息。

删除测试cookie。 用它来清理你自己。

set_expiry T0>(值 T1>)¶ T2>

设置会话的到期时间。 您可以传递许多不同的值:

  • 如果value是一个整数,则会话将在多秒钟不活动之后过期。 例如,调用request.session.set_expiry(300)会使会话在5分钟内过期。
  • 如果valuedatetimetimedelta对象,则会话将在该特定日期/时间过期。 Note that datetime and timedelta values are only serializable if you are using the PickleSerializer.
  • 如果value0,用户的会话cookie将在用户的Web浏览器关闭时过期。
  • 如果valueNone,会话将恢复为使用全局会话到期策略。

阅读一个会话不被认为是到期目的的活动。 会话到期时间是从上次会话修改时计算的。

get_expiry_age T0>()¶ T1>

返回此会话过期的秒数。 对于没有自定义过期的会话(或者设置为在浏览器关闭时过期的会话),这将等于SESSION_COOKIE_AGE

该函数接受两个可选的关键字参数:

  • modification:会话的最后修改,作为datetime对象。 默认为当前时间。
  • expiry:会话的到期信息,作为datetime对象,int(以秒为单位)或None 如果存在,则默认为set_expiry()存储在会话中的值,或None
get_expiry_date T0>()¶ T1>

返回此会话将过期的日期。 For sessions with no custom expiration (or those set to expire at browser close), this will equal the date SESSION_COOKIE_AGE seconds from now.

该函数接受与get_expiry_age()相同的关键字参数。

get_expire_at_browser_close T0>()¶ T1>

Returns either True or False, depending on whether the user’s session cookie will expire when the user’s Web browser is closed.

clear_expired T0>()¶ T1>

从会话存储中删除过期的会话。 这个类方法被clearsessions调用。

cycle_key T0>()¶ T1>

创建一个新的会话密钥,同时保留当前的会话数据。 django.contrib.auth.login() calls this method to mitigate against session fixation.

会话序列化

默认情况下,Django使用JSON序列化会话数据。 您可以使用SESSION_SERIALIZER设置自定义会话序列化格式。 即使在Write your own serializer中描述的警告,我们强烈建议坚持使用JSON序列化,特别是如果您使用cookie后端

例如,如果您使用pickle序列化会话数据,则会出现一个攻击情形。 如果您使用signed cookie session backendSECRET_KEY被攻击者所知(在Django中没有可能导致泄漏的固有漏洞),攻击者可以在他们的会话中插入一个字符串,这个字符串在取消时会在服务器上执行任意代码。 这样做的技术在互联网上简单易用。 Although the cookie session storage signs the cookie-stored data to prevent tampering, a SECRET_KEY leak immediately escalates to a remote code execution vulnerability.

捆绑串行器

序列化。 JSONSerializer T0> ¶ T1>

来自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编码的数据,如非UTF8字节(如'\xd9'(引发UnicodeDecodeError))。

有关JSON序列化限制的更多详细信息,请参阅Write your own serializer部分。

序列化。 PickleSerializer T0> ¶ T1>

Supports arbitrary Python objects, but, as described above, can lead to a remote code execution vulnerability if SECRET_KEY becomes known by an attacker.

编写你自己的串行器

请注意,与PickleSerializer不同,JSONSerializer不能处理任意Python数据类型。 通常情况下,便利性和安全性之间有一个折衷。 If you wish to store more advanced data types including datetime and Decimal in JSON backed sessions, you will need to write a custom serializer (or convert such values to a JSON serializable object before storing them in request.session). While serializing these values is fairly straightforward (DjangoJSONEncoder may be helpful), writing a decoder that can reliably get back the same thing that you put in is more fragile. 例如,你冒着返回一个datetime的风险,这实际上是一个恰好与datetime所选择的格式相同的字符串)。

您的序列化程序类必须实现两个方法:转储(self, obj)data),分别对会话数据字典进行序列化和反序列化。

会话对象准则

  • 使用普通的Python字符串作为request.session上的字典键。 这是一个惯例,而不是一个硬性规定。
  • 以下划线开头的会话字典键被保留供Django内部使用。
  • Don’t override request.session with a new object, and don’t access or set its attributes. 像Python字典一样使用它。

实例¶ T0>

这个简单的视图在用户发表评论之后将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.")

The standard django.contrib.auth.logout() function actually does a bit more than this to prevent inadvertent data leakage. 它调用request.sessionflush()方法。 我们使用这个例子来演示如何使用会话对象,而不是完整的logout()实现。

设置测试cookie

为了方便,Django提供了一个简单的方法来测试用户的浏览器是否接受cookie。 Just call the set_test_cookie() method of request.session in a view, and call test_cookie_worked() in a subsequent view – not in the same view call.

由于cookie的工作方式,set_test_cookie()test_cookie_worked()之间的这种尴尬分裂是必要的。 当你设置一个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() is designed to create a new session (i.e. one not loaded from the session store and with session_key=None). save() is designed to save an existing session (i.e. one loaded from the session store). 在新会话中调用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'

在上例的最后一个例子中,我们可以通过在session对象上设置modified属性来显式地告诉session对象:

request.session.modified = True

要更改此默认行为,请将SESSION_SAVE_EVERY_REQUEST设置为True 当设置为True时,Django将在每个请求中将会话保存到数据库。

请注意,会话cookie仅在创建或修改会话时发送。 如果SESSION_SAVE_EVERY_REQUESTTrue,会话cookie将在每个请求中发送。

同样,每次发送会话cookie时,会话cookie的expires部分都会更新。

如果响应的状态码是500,则会话不保存。

浏览器长度会话与持久会话

您可以使用SESSION_EXPIRE_AT_BROWSER_CLOSE设置来控制会话框架是使用浏览器长度会话还是持久会话。

By default, SESSION_EXPIRE_AT_BROWSER_CLOSE is set to False, which means session cookies will be stored in users’ browsers for as long as SESSION_COOKIE_AGE. 如果您不希望每次打开浏览器时都必须登录,请使用此选项。

如果SESSION_EXPIRE_AT_BROWSER_CLOSE设置为True,Django将使用浏览器长度的cookie - 一旦用户关闭浏览器就会过期的cookie。 如果您希望每次打开浏览器时都必须登录,请使用此选项。

This setting is a global default and can be overwritten at a per-session level by explicitly calling the set_expiry() method of request.session as described above in using sessions in views.

注意

某些浏览器(例如Chrome)提供的设置允许用户在关闭并重新打开浏览器后继续浏览会话。 在某些情况下,这会干扰SESSION_EXPIRE_AT_BROWSER_CLOSE设置,并阻止会话在浏览器关闭时过期。 在测试启用了SESSION_EXPIRE_AT_BROWSER_CLOSE设置的Django应用程序时,请注意这一点。

清除会话存储

当用户在您的网站上创建新会话时,会话数据可能会累积到您的会话存储中。 如果您使用数据库后端,那么django_session数据库表将会增长。 如果您使用的是后端文件,临时目录将包含越来越多的文件。

要理解这个问题,请考虑数据库后端会发生什么情况。 当用户登录时,Django将一行添加到django_session数据库表中。 每次会话数据更改时,Django都会更新此行。 如果用户手动注销,则Django删除该行。 但是,如果用户不注销,行不会被删除。 文件后端发生类似的过程。

Django does not provide automatic purging of expired sessions. 因此,定期清理过期会话是您的工作。 Django为此提供了一个清理管理命令:clearsessions 建议定期调用此命令,例如作为每日cron作业。

请注意,缓存后端不容易出现此问题,因为缓存会自动删除陈旧的数据。 会话数据由用户的浏览器存储,因此也不是cookie后端。

会话安全性

站点内的子域可以在客户端上为整个域设置Cookie。 如果允许来自不受信任用户控制的子域的cookie,这使得会话固定成为可能。

例如,攻击者可以登录good.example.com并获取其帐户的有效会话。 如果攻击者控制了bad.example.com,他们可以使用它来发送他们的会话密钥给你,因为一个子域允许在*.example.com 当您访问good.example.com时,您将以攻击者身份登录,并可能无意中将敏感的个人数据(例如信用卡信息)输入到攻击者帐户中。

如果good.example.com将其SESSION_COOKIE_DOMAIN设置为"example.com",则会导致来自该网站的会话Cookie被发送到bad.example.com

技术细节

  • 当使用JSONSerializer时,会话字典接受任何json可序列化的值,或者在使用PickleSerializer时接受任何可供选择的Python对象。 有关更多信息,请参阅pickle模块。
  • 会话数据存储在名为django_session的数据库表中。
  • 如果需要的话,Django只发送一个cookie。 如果您不设置任何会话数据,则不会发送会话Cookie。

SessionStore对象

在内部使用会话时,Django使用来自相应会话引擎的会话存储对象。 按照惯例,会话存储对象类被命名为SessionStore,并位于由SESSION_ENGINE指定的模块中。

在Django中可用的所有SessionStore类都从SessionBase继承并实现数据操作方法,即:

为了构建自定义会话引擎或定制现有会话引擎,您可以创建一个从SessionBase或任何其他现有的SessionStore类继承的新类。

扩展大部分的会话引擎是非常简单的,但是在数据库支持的会话引擎中这样做通常需要一些额外的工作(详见下一节)。

扩展数据库支持的会话引擎

Creating a custom database-backed session engine built upon those included in Django (namely db and cached_db) may be done by inheriting AbstractBaseSession and either SessionStore class.

AbstractBaseSession and BaseSessionManager are importable from django.contrib.sessions.base_session so that they can be imported without including django.contrib.sessions in INSTALLED_APPS.

base_session。 AbstractBaseSession T0> ¶ T1>

抽象的基础会话模型。

session_key可以 T0> ¶ T1>

首要的关键。 该字段最多可以包含40个字符。 当前实现生成一个32个字符的字符串(一个随机的数字和小写ASCII字母序列)。

session_data是 T0> ¶ T1>

包含编码和序列化会话字典的字符串。

EXPIRE_DATE T0> ¶ T1>

指定会话何时到期的日期时间。

过期的会话对用户不可用,但是,在执行clearsessions管理命令之前,它们仍可能存储在数据库中。

类方法 get_session_store_class T0>()¶ T1>

返回将用于此会话模型的会话存储类。

get_decoded T0>()¶ T1>

返回解码的会话数据。

会话存储类进行解码。

您还可以通过继承BaseSessionManager来自定义模型管理器:

base_session。 BaseSessionManager T0> ¶ T1>
编码 T0>( session_dict T1>)¶ T2>

返回序列化并编码为字符串的给定会话字典。

编码由绑定到模型类的会话存储类执行。

savesession_keysession_dictexpire_date

保存提供的会话密钥的会话数据,或者在数据为空时删除会话。

通过覆盖下面描述的方法和属性来实现SessionStore类的自定义:

backends.db。 SessionStore T0> ¶ T1>

实现数据库支持的会话存储。

类方法 get_model_class T0>()¶ T1>

重写此方法以在需要时返回自定义会话模型。

create_model_instance T0>(数据 T1>)¶ T2>

返回表示当前会话状态的会话模型对象的新实例。

重写此方法可以在将会话模型数据保存到数据库之前修改会话模型数据。

backends.cached_db。 SessionStore T0> ¶ T1>

实现高速缓存的数据库支持的会话存储。

cache_key_prefix T0> ¶ T1>

添加到会话密钥以构建缓存密钥字符串的前缀。

实施例¶ T0>

下面的示例显示了一个自定义的数据库支持的会话引擎,其中包含一个额外的数据库列以存储帐户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。 作为最后的手段,它不会退回到将URL中的会话标识中,正如PHP所做的那样。 这是一个有意的设计决定。 这种行为不仅会使URL变得丑陋,而且会使得您的站点容易受到“Referer”头的会话ID盗用。