Как использовать сессии

Django полностью поддерживает сессии для анонимных пользователей, позволяет сохранять и получать данные для каждой посетителя сайта. Механизм сессии сохраняет данные на сервере и самостоятельно управляет сессионными куками. Куки содержат ID сессии, а не сами данные (если только вы не используете бэкенд на основе кук).

Активируем сессии

Сессии реализованы через промежуточный слой.

Чтобы активировать сессии, выполните следующие действия:

  • Убедитесь, что MIDDLEWARE_CLASSES содержит 'django.contrib.sessions.middleware.SessionMiddleware'. settings.py по умолчанию, созданный django-admin startproject, уже содержит SessionMiddleware.

Если вы не собираетесь использовать сессии, вы можете удалить SessionMiddleware из MIDDLEWARE_CLASSES и 'django.contrib.sessions' из INSTALLED_APPS. Это немного повысит производительность.

Настройка сессий

По умолчанию, Django хранит сессии в базе данных (используя модель django.contrib.sessions.models.Session). В некоторых случаях лучше хранить данные сессии в других хранилищах, поэтому Django позволяет использовать файловую систему или кэш.

Использование базы данных для хранения сессии

Если вы хотите использовать базу данных для хранения сессии, укажите 'django.contrib.sessions' в настройке INSTALLED_APPS.

После настройки выполните manage.py migrate, чтобы добавить таблицу в базу данных.

Использование кэша для хранения сессии

Для улучшения производительности вы можете использовать кэш для хранения сессии.

Для этого вы должны настроить кэш, смотрите раздел о кэше.

Предупреждение

Вам следует использовать кэш только при использовании Memcached. Кэш в памяти не хранит данные достаточно долго, и лучше использовать файлы или базу данных для сессии, чем каждый раз обращаться к кэшу в файловой системе или базе данных. Также кэш в памяти использует различные экземпляры кэша для разных процессов.

Если вы указали несколько кэшей в CACHES, Django будет использовать кэш по умолчанию. Чтобы использовать другой кэш, укажите его название в SESSION_CACHE_ALIAS.

После настройки кэша у вас есть две опции, определяющие как хранить данные в кэше:

  • Указать "django.contrib.sessions.backends.cache" в SESSION_ENGINE. Данные сессии будут храниться непосредственно в кэше. Однако, данные могут быть удалены при переполнении кэша или перезагрузке сервера кэша.

  • Для постоянного хранения закэшированных данных укажите "django.contrib.sessions.backends.cached_db" в SESSION_ENGINE. Все записи в кэш будут продублированы в базу данных. База данных будет использоваться, если данные не найдены в кэше.

Оба варианта работают достаточно быстро, но первый немного быстрее. Для большинства случаев cached_db будет достаточно быстрым, но если производительность для вас важнее, чем надежное хранение сессии, используйте бэкенд cache.

Если вы используете cached_db, вам необходимо настроить и бэкенд базы данных.

Использование файловой системы для хранения сессии

Чтобы использовать файловую систему, укажите "django.contrib.sessions.backends.file" в SESSION_ENGINE.

Вы также можете указать SESSION_FILE_PATH (по умолчанию tempfile.gettempdir(), обычно это /tmp), чтобы указать Django, где сохранять сессионные файлы. Убедитесь, что ваш сервер имеет права на чтение и запись указанного каталога.

Использование сессии в представлениях

Когда SessionMiddleware активный, каждый объект HttpRequest – первый аргумент представления в Django – будет содержать атрибут session, который является объектом с интерфейсом словаря.

Вы можете читать и менять 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, если key ещё не в сессии.

__contains__(key)

Например: 'fav_color' in request.session

get(key, default=None)

Например: fav_color = request.session.get('fav_color', 'red')

pop(key, default=None)

Example: fav_color = request.session.pop('fav_color', 'blue')

keys()
items()
setdefault()
clear()

Также содержит следующие методы:

flush()

Deletes the current session data from the session and deletes the session cookie. This is used if you want to ensure that the previous session data can’t be accessed again from the user’s browser (for example, the django.contrib.auth.logout() function calls it).

Изменено в Django 1.8:

Удаление сессионной куки было добавлено в Django 1.8. Ранее значение сессионной куки менялось и отправлялось пользователю в куках.

Устанавливает тестовую куку, чтобы проверить, что браузер пользователя поддерживает куки. Из-за особенностей работы кук вы не сможете проверить тестовую куку, пока пользователь не запросит следующую страницу. Подробности смотрите ниже (`Setting test cookies`_).

Возвращает True или False, в зависимости от того, принял ли бразуер пользователя тестовую куку. Из-за особенностей работы кук вам необходимо вызывать в предыдущем запросе set_test_cookie(). Подробности смотрите ниже (`Setting test cookies`_).

Удаляет тестовую куку. Используйте, чтобы убрать за собой.

set_expiry(value)

Указывает время жизни сессии. Вы можете передать различные значения:

  • Если value целое число, сессия истечет после указанного количества секунд неактивности пользователя. Например, request.session.set_expiry(300) установит время жизни равное 5 минутам.

  • Если value это datetime или timedelta, сессия истечёт в указанное время. Обратите внимание, datetime и timedelta сериализуются только при использовании PickleSerializer.

  • Если value равно 0, сессионная кука удалится при закрытии браузера.

  • Если value равно None, сессия будет использовать глобальное поведение.

Чтение сессии не обновляет время жизни сессии. Время жизни просчитывается с момента последнего изменения.

get_expiry_age()

Возвращает время в секундах до окончания действия сессии. Для сессий без времени окончания (или для тех, что действуют пока работает браузер), значение будет равно SESSION_COOKIE_AGE.

Функция понимает два необязательных именованных аргумента:

  • modification: время последнего изменения сессии в виде объекта datetime. По умолчанию соответствует текущему времени.

  • expiry: время окончания сессии в виде объекта datetime, количества секунд в виде int или None. По умолчанию используется значение, установленное методом set_expiry(), если оно есть, в противном случае будет None.

get_expiry_date()

Возвращает дату окончания действия сессии. Для сессий, у которых дата завершения не указана (или которые завершаются при закрытии браузера), значением будет дата, отстоящая на SESSION_COOKIE_AGE секунд от текущего момента.

Функция принимает те же именованные аргументы, что и get_expiry_age().

get_expire_at_browser_close()

Возвращает True или False, в зависимости от того, будет ли кука пользовательской сессии сброшена когда пользователь закроет свой браузер.

clear_expired()

Удаляет просроченные сессии из хранилища сессий. Этот метод класса используется clearsessions.

cycle_key()

Создает новый ключ сессии при сохранении текущих данных в ней. Функция django.contrib.auth.login() вызывает этот метод, чтобы противодействовать фиксации сессии.

Сериализация сессии

By default, Django serializes session data using JSON. You can use the SESSION_SERIALIZER setting to customize the session serialization format. Even with the caveats described in Реализация своего сериализатора, we highly recommend sticking with JSON serialization especially if you are using the cookie backend.

For example, here’s an attack scenario if you use pickle to serialize session data. If you’re using the signed cookie session backend and SECRET_KEY is known by an attacker (there isn’t an inherent vulnerability in Django that would cause it to leak), the attacker could insert a string into their session which, when unpickled, executes arbitrary code on the server. The technique for doing so is simple and easily available on the internet. 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.

Поставляемые сериализации

class serializers.JSONSerializer

Обёртка над 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.

class serializers.PickleSerializer

Поддерживает произвольные объекты Python, но, как было указано выше, может привести к несанкционированному исполнению чужого кода на сервере в случае, если значение параметра конфигурации SECRET_KEY станет известно злоумышленнику.

Реализация своего сериализатора

Следует отметить, что в отличие от PickleSerializer, JSONSerializer не может обрабатывать произвольные типы данных Python. Как обычно бывает, надо искать компромисс между удобством и безопасностью. Если вам требуется хранить различные типы данных, включая datetime и Decimal, в JSON формате, вам придётся написать собственный сериализатор (или преобразовать такие значения в JSON сериализуемый объект перед их сохранением в request.session). Хотя сериализация этих значений достаточно проста (вам может пригодиться django.core.serializers.json.DateTimeAwareJSONEncoder), создание декодера, который сможет восстанавливать данные – задача более сложная. Например, вы случайно можете вернуть datetime, который в действительности является строкой, записанной в виде совпадают с форматом, выбранным для datetime.

Класс вашего сериализатора должен реализовать два метода: 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().

Установка проверочных кук

Для удобства Django представляет простой способ проверить принимает ли браузер пользователя куки или нет. Просто вызовите метод set_test_cookie() объекта request.session в предоставлении и затем вызовите метод test_cookie_worked() в следующем вызове представления, это важный момент.

Такое неудобное разделение set_test_cookie() и test_cookie_worked() между вызовами предоставления необходимо из-за особенностей работы механизма кук. Когда вы устанавливаете куку, вы не можете гарантировать, что браузер действительно принял куке до тех пор, пока браузер не выполнит следующий запрос.

Хорошим тоном будет использование метода delete_test_cookie() для очистки куки после проверки. Очистку следует выполнять после окончания этапа проверки наличия поддержки кук.

Покажем типичный вариант использования:

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_to_response('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.save()
>>> s.session_key
'2b1189a188b44ad18c35e113ac6ceead'

>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
>>> s['last_login']
1376587691

Для затруднения атак, направленных на фиксацию сессии, ключи сессий генерируются заново в случае их отсутствия:

>>> from django.contrib.sessions.backends.db import SessionStore
>>> s = SessionStore(session_key='no-such-session-here')
>>> s.save()
>>> s.session_key
'ff882814010ccbc3c870523934fee5a2'

При использовании хранилища django.contrib.sessions.backends.db каждая сессия будет представлена в виде обычной модели Django. Модель Session определена в django/contrib/sessions/models.py. Раз это обычная модель, то вы можете работать с ней через стандартный интерфейс:

>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)

Note that you’ll need to call get_decoded() to get the session dictionary. This is necessary because the dictionary is stored in an encoded format:

>>> 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. Это укажет Django на то, что необходимо сохранять сессию в базу данных при каждом запросе.

Следует отметить, что сессионная кука отправляется только при создании и модификации сессии. Если параметр конфигурации SESSION_SAVE_EVERY_REQUEST установлен в True, сессионная кука будет отправляться на каждый запрос.

Аналогично, значение атрибутп expires сессионной куки обновляется при каждой её отправке.

Сессия не сохраняется, если отклик имеет статус 500.

Разница между временными и постоянными куками

Вы можете указать какой должна быть длительность сессии, пока браузер не завершается или до некоторой даты, с помощью параметра конфигурации SESSION_EXPIRE_AT_BROWSER_CLOSE.

По умолчанию параметр конфигурации SESSION_EXPIRE_AT_BROWSER_CLOSE установлен в False, это означает, что сессионные куки будут храниться в браузере пользователя до тех пор, пока не истечёт время, указанное в параметре конфигурации SESSION_COOKIE_AGE. Используйте это если не желаете, чтобы пользователи авторизовались на сайте при каждом открытии браузера.

Если параметр конфигурации SESSION_EXPIRE_AT_BROWSER_CLOSE установлен в True, то Django будет использовать временные куки, такие куки работают до момента закрытия браузера. Используйте это если желаете, чтобы пользователи авторизовались на сайте при каждом открытии браузера.

Данный параметр конфигурации является глобальным, но может быть переопределён на уровне сессии с помощью явного вызова метода 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 удаляет эту запись. В противном случае эта запись не удаляется. Похожее происходит и в случае файлового хранилища.

Django не обеспечивает автоматическую очистку просроченных сессий. Следовательно, эта задача ложится на вас на регулярной основе. Django представляет для этой цели команду очистки: clearsessions. Рекомендуется выполнять эту команду на регулярной основе, например, через cron ежедневно.

Следует отметить, что хранилище на основе кэша не подтверждено этой проблеме так как кэш автоматически удаляет устаревшие данные. Так же нет проблемы и с хранилищем на основе кук, потому что они хранятся в браузерах у пользователей.

Безопасность сессии

Поддомены внутри сайта имеют возможность устанавливать куки на клиенте для всего домена. Это приводит к возможности фиксации сессии, если куки принимаются от поддоменов, которые не находятся под управлением доверенных пользователей.

Например, атакующий может авторизоваться на good.example.com и получить достоверную сессию для своего аккаунта. Если у атакующего есть контроль над bad.example.com, он может использовать его для отправки своего ключа сессии вам, так как поддомену разрешено устанавливать куки для *.example.com. Когда вы посетите good.example.com, вы будете авторизован как атакующий и можете непреднамеренно внести важные персональные данные (например, номер кредитной карты) в аккаунт атакующего.

Другой возможной атакой может быть ситуация, если сайт good.example.com имеет в параметре конфигурации SESSION_COOKIE_DOMAIN значение ".example.com", что может привести отправке сессионной куки на сайт bad.example.com.

Технические детали

  • Словарь сессии принимает любые значения, сериализуемые в json, при использовании JSONSerializer или любые pickle объекты при использовании PickleSerializer. Обратитесь к модулю pickle для дополнительной информации.

  • Данные сессии сохраняются в базе данных в таблице django_session .

  • Django отправляем куки только когда это требуется. Если вы не сохранили никаких данных в сессию, то сессионная кука не будет отправляться.

The SessionStore object

When working with sessions internally, Django uses a session store object from the corresponding session engine. By convention, the session store object class is named SessionStore and is located in the module designated by SESSION_ENGINE.

All SessionStore classes available in Django inherit from SessionBase and implement data manipulation methods, namely:

In order to build a custom session engine or to customize an existing one, you may create a new class inheriting from SessionBase or any other existing SessionStore class.

Extending most of the session engines is quite straightforward, but doing so with database-backed session engines generally requires some extra effort (see the next section for details).

Extending database-backed session engines

Добавлено в Django 1.9.

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.

class base_session.AbstractBaseSession
Добавлено в Django 1.9.

The abstract base session model.

session_key

Primary key. The field itself may contain up to 40 characters. The current implementation generates a 32-character string (a random sequence of digits and lowercase ASCII letters).

session_data

A string containing an encoded and serialized session dictionary.

expire_date

A datetime designating when the session expires.

Expired sessions are not available to a user, however, they may still be stored in the database until the clearsessions management command is run.

classmethod get_session_store_class()

Returns a session store class to be used with this session model.

get_decoded()

Returns decoded session data.

Decoding is performed by the session store class.

You can also customize the model manager by subclassing BaseSessionManager:

class base_session.BaseSessionManager
Добавлено в Django 1.9.
encode(session_dict)

Returns the given session dictionary serialized and encoded as a string.

Encoding is performed by the session store class tied to a model class.

save(session_key, session_dict, expire_date)

Saves session data for a provided session key, or deletes the session in case the data is empty.

Customization of SessionStore classes is achieved by overriding methods and properties described below:

class backends.db.SessionStore

Implements database-backed session store.

classmethod get_model_class()
Добавлено в Django 1.9.

Override this method to return a custom session model if you need one.

create_model_instance(data)
Добавлено в Django 1.9.

Returns a new instance of the session model object, which represents the current session state.

Overriding this method provides the ability to modify session model data before it’s saved to database.

class backends.cached_db.SessionStore

Implements cached database-backed session store.

cache_key_prefix
Добавлено в Django 1.9.

A prefix added to a session key to build a cache key string.

Example

The example below shows a custom database-backed session engine that includes an additional database column to store an account ID (thus providing an option to query the database for all active sessions for an account):

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)

    class Meta:
        app_label = 'mysessions'

    @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(SessionStore, self).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

If you are migrating from the Django’s built-in cached_db session store to a custom one based on cached_db, you should override the cache key prefix in order to prevent a namespace clash:

class SessionStore(CachedDBStore):
    cache_key_prefix = 'mysessions.custom_cached_db_backend'

    # ...

Идентификаторы сессии в URL

Поддержка сессий в Django полностью, и исключительно, основана на кук. Django не помещает идентификатор сессии в URL в качестве крайнего варианта, как это делает PHP. Это - осознанное проектное решение. Мало того, что такое поведение делает URL уродливыми, оно делает ваш сайт уязвимым для воровства идентификатора сессии через заголовок “Referer”.