mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			838 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			838 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| ===================
 | ||
| How to use sessions
 | ||
| ===================
 | ||
| 
 | ||
| .. module:: django.contrib.sessions
 | ||
|    :synopsis: Provides session management for Django projects.
 | ||
| 
 | ||
| Django provides full support for anonymous sessions. The session framework
 | ||
| lets you store and retrieve arbitrary data on a per-site-visitor basis. It
 | ||
| stores data on the server side and abstracts the sending and receiving of
 | ||
| cookies. Cookies contain a session ID -- not the data itself (unless you're
 | ||
| using the :ref:`cookie based backend<cookie-session-backend>`).
 | ||
| 
 | ||
| Enabling sessions
 | ||
| =================
 | ||
| 
 | ||
| Sessions are implemented via a piece of :doc:`middleware </ref/middleware>`.
 | ||
| 
 | ||
| To enable session functionality, do the following:
 | ||
| 
 | ||
| * Edit the :setting:`MIDDLEWARE` setting and make sure it contains
 | ||
|   ``'django.contrib.sessions.middleware.SessionMiddleware'``. The default
 | ||
|   ``settings.py`` created by ``django-admin startproject`` has
 | ||
|   ``SessionMiddleware`` activated.
 | ||
| 
 | ||
| If you don't want to use sessions, you might as well remove the
 | ||
| ``SessionMiddleware`` line from :setting:`MIDDLEWARE` and
 | ||
| ``'django.contrib.sessions'`` from your :setting:`INSTALLED_APPS`.
 | ||
| It'll save you a small bit of overhead.
 | ||
| 
 | ||
| .. _configuring-sessions:
 | ||
| 
 | ||
| Configuring the session engine
 | ||
| ==============================
 | ||
| 
 | ||
| By default, Django stores sessions in your database (using the model
 | ||
| ``django.contrib.sessions.models.Session``). Though this is convenient, in
 | ||
| some setups it's faster to store session data elsewhere, so Django can be
 | ||
| configured to store session data on your filesystem or in your cache.
 | ||
| 
 | ||
| Using database-backed sessions
 | ||
| ------------------------------
 | ||
| 
 | ||
| If you want to use a database-backed session, you need to add
 | ||
| ``'django.contrib.sessions'`` to your :setting:`INSTALLED_APPS` setting.
 | ||
| 
 | ||
| Once you have configured your installation, run ``manage.py migrate``
 | ||
| to install the single database table that stores session data.
 | ||
| 
 | ||
| .. _cached-sessions-backend:
 | ||
| 
 | ||
| Using cached sessions
 | ||
| ---------------------
 | ||
| 
 | ||
| For better performance, you may want to use a cache-based session backend.
 | ||
| 
 | ||
| To store session data using Django's cache system, you'll first need to make
 | ||
| sure you've configured your cache; see the :doc:`cache documentation
 | ||
| </topics/cache>` for details.
 | ||
| 
 | ||
| .. warning::
 | ||
| 
 | ||
|     You should only use cache-based sessions if you're using the Memcached
 | ||
|     cache backend. The local-memory cache backend doesn't retain data long
 | ||
|     enough to be a good choice, and it'll be faster to use file or database
 | ||
|     sessions directly instead of sending everything through the file or
 | ||
|     database cache backends. Additionally, the local-memory cache backend is
 | ||
|     NOT multi-process safe, therefore probably not a good choice for production
 | ||
|     environments.
 | ||
| 
 | ||
| If you have multiple caches defined in :setting:`CACHES`, Django will use the
 | ||
| default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the
 | ||
| name of that cache.
 | ||
| 
 | ||
| Once your cache is configured, you've got two choices for how to store data in
 | ||
| the cache:
 | ||
| 
 | ||
| * Set :setting:`SESSION_ENGINE` to
 | ||
|   ``"django.contrib.sessions.backends.cache"`` for a simple caching session
 | ||
|   store. Session data will be stored directly in your cache. However, session
 | ||
|   data may not be persistent: cached data can be evicted if the cache fills
 | ||
|   up or if the cache server is restarted.
 | ||
| 
 | ||
| * For persistent, cached data, set :setting:`SESSION_ENGINE` to
 | ||
|   ``"django.contrib.sessions.backends.cached_db"``. This uses a
 | ||
|   write-through cache -- every write to the cache will also be written to
 | ||
|   the database. Session reads only use the database if the data is not
 | ||
|   already in the cache.
 | ||
| 
 | ||
| Both session stores are quite fast, but the simple cache is faster because it
 | ||
| disregards persistence. In most cases, the ``cached_db`` backend will be fast
 | ||
| enough, but if you need that last bit of performance, and are willing to let
 | ||
| session data be expunged from time to time, the ``cache`` backend is for you.
 | ||
| 
 | ||
| If you use the ``cached_db`` session backend, you also need to follow the
 | ||
| configuration instructions for the `using database-backed sessions`_.
 | ||
| 
 | ||
| Using file-based sessions
 | ||
| -------------------------
 | ||
| 
 | ||
| To use file-based sessions, set the :setting:`SESSION_ENGINE` setting to
 | ||
| ``"django.contrib.sessions.backends.file"``.
 | ||
| 
 | ||
| You might also want to set the :setting:`SESSION_FILE_PATH` setting (which
 | ||
| defaults to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to
 | ||
| control where Django stores session files. Be sure to check that your Web
 | ||
| server has permissions to read and write to this location.
 | ||
| 
 | ||
| .. _cookie-session-backend:
 | ||
| 
 | ||
| Using cookie-based sessions
 | ||
| ---------------------------
 | ||
| 
 | ||
| To use cookies-based sessions, set the :setting:`SESSION_ENGINE` setting to
 | ||
| ``"django.contrib.sessions.backends.signed_cookies"``. The session data will be
 | ||
| stored using Django's tools for :doc:`cryptographic signing </topics/signing>`
 | ||
| and the :setting:`SECRET_KEY` setting.
 | ||
| 
 | ||
| .. note::
 | ||
| 
 | ||
|     It's recommended to leave the :setting:`SESSION_COOKIE_HTTPONLY` setting
 | ||
|     on ``True`` to prevent access to the stored data from JavaScript.
 | ||
| 
 | ||
| .. warning::
 | ||
| 
 | ||
|     **If the SECRET_KEY is not kept secret and you are using the**
 | ||
|     :class:`~django.contrib.sessions.serializers.PickleSerializer`, **this can
 | ||
|     lead to arbitrary remote code execution.**
 | ||
| 
 | ||
|     An attacker in possession of the :setting:`SECRET_KEY` can not only
 | ||
|     generate falsified session data, which your site will trust, but also
 | ||
|     remotely execute arbitrary code, as the data is serialized using pickle.
 | ||
| 
 | ||
|     If you use cookie-based sessions, pay extra care that your secret key is
 | ||
|     always kept completely secret, for any system which might be remotely
 | ||
|     accessible.
 | ||
| 
 | ||
|     **The session data is signed but not encrypted**
 | ||
| 
 | ||
|     When using the cookies backend the session data can be read by the client.
 | ||
| 
 | ||
|     A MAC (Message Authentication Code) is used to protect the data against
 | ||
|     changes by the client, so that the session data will be invalidated when being
 | ||
|     tampered with. The same invalidation happens if the client storing the
 | ||
|     cookie (e.g. your user's browser) can't store all of the session cookie and
 | ||
|     drops data. Even though Django compresses the data, it's still entirely
 | ||
|     possible to exceed the `common limit of 4096 bytes`_ per cookie.
 | ||
| 
 | ||
|     **No freshness guarantee**
 | ||
| 
 | ||
|     Note also that while the MAC can guarantee the authenticity of the data
 | ||
|     (that it was generated by your site, and not someone else), and the
 | ||
|     integrity of the data (that it is all there and correct), it cannot
 | ||
|     guarantee freshness i.e. that you are being sent back the last thing you
 | ||
|     sent to the client. This means that for some uses of session data, the
 | ||
|     cookie backend might open you up to `replay attacks`_. Unlike other session
 | ||
|     backends which keep a server-side record of each session and invalidate it
 | ||
|     when a user logs out, cookie-based sessions are not invalidated when a user
 | ||
|     logs out. Thus if an attacker steals a user's cookie, they can use that
 | ||
|     cookie to login as that user even if the user logs out. Cookies will only
 | ||
|     be detected as 'stale' if they are older than your
 | ||
|     :setting:`SESSION_COOKIE_AGE`.
 | ||
| 
 | ||
|     **Performance**
 | ||
| 
 | ||
|     Finally, the size of a cookie can have an impact on the `speed of your site`_.
 | ||
| 
 | ||
| .. _`common limit of 4096 bytes`: https://tools.ietf.org/html/rfc2965#section-5.3
 | ||
| .. _`replay attacks`: https://en.wikipedia.org/wiki/Replay_attack
 | ||
| .. _`speed of your site`: http://yuiblog.com/blog/2007/03/01/performance-research-part-3/
 | ||
| 
 | ||
| Using sessions in views
 | ||
| =======================
 | ||
| 
 | ||
| When ``SessionMiddleware`` is activated, each :class:`~django.http.HttpRequest`
 | ||
| object -- the first argument to any Django view function -- will have a
 | ||
| ``session`` attribute, which is a dictionary-like object.
 | ||
| 
 | ||
| You can read it and write to ``request.session`` at any point in your view.
 | ||
| You can edit it multiple times.
 | ||
| 
 | ||
| .. class:: backends.base.SessionBase
 | ||
| 
 | ||
|     This is the base class for all session objects. It has the following
 | ||
|     standard dictionary methods:
 | ||
| 
 | ||
|     .. method:: __getitem__(key)
 | ||
| 
 | ||
|       Example: ``fav_color = request.session['fav_color']``
 | ||
| 
 | ||
|     .. method:: __setitem__(key, value)
 | ||
| 
 | ||
|       Example: ``request.session['fav_color'] = 'blue'``
 | ||
| 
 | ||
|     .. method:: __delitem__(key)
 | ||
| 
 | ||
|       Example: ``del request.session['fav_color']``. This raises ``KeyError``
 | ||
|       if the given ``key`` isn't already in the session.
 | ||
| 
 | ||
|     .. method:: __contains__(key)
 | ||
| 
 | ||
|       Example: ``'fav_color' in request.session``
 | ||
| 
 | ||
|     .. method:: get(key, default=None)
 | ||
| 
 | ||
|       Example: ``fav_color = request.session.get('fav_color', 'red')``
 | ||
| 
 | ||
|     .. method:: pop(key, default=__not_given)
 | ||
| 
 | ||
|       Example: ``fav_color = request.session.pop('fav_color', 'blue')``
 | ||
| 
 | ||
|     .. method:: keys()
 | ||
| 
 | ||
|     .. method:: items()
 | ||
| 
 | ||
|     .. method:: setdefault()
 | ||
| 
 | ||
|     .. method:: clear()
 | ||
| 
 | ||
|     It also has these methods:
 | ||
| 
 | ||
|     .. method:: 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
 | ||
|       :func:`django.contrib.auth.logout()` function calls it).
 | ||
| 
 | ||
|     .. method:: set_test_cookie()
 | ||
| 
 | ||
|       Sets a test cookie to determine whether the user's browser supports
 | ||
|       cookies. Due to the way cookies work, you won't be able to test this
 | ||
|       until the user's next page request. See `Setting test cookies`_ below for
 | ||
|       more information.
 | ||
| 
 | ||
|     .. method:: test_cookie_worked()
 | ||
| 
 | ||
|       Returns either ``True`` or ``False``, depending on whether the user's
 | ||
|       browser accepted the test cookie. Due to the way cookies work, you'll
 | ||
|       have to call ``set_test_cookie()`` on a previous, separate page request.
 | ||
|       See `Setting test cookies`_ below for more information.
 | ||
| 
 | ||
|     .. method:: delete_test_cookie()
 | ||
| 
 | ||
|       Deletes the test cookie. Use this to clean up after yourself.
 | ||
| 
 | ||
|     .. method:: set_expiry(value)
 | ||
| 
 | ||
|       Sets the expiration time for the session. You can pass a number of
 | ||
|       different values:
 | ||
| 
 | ||
|       * If ``value`` is an integer, the session will expire after that
 | ||
|         many seconds of inactivity. For example, calling
 | ||
|         ``request.session.set_expiry(300)`` would make the session expire
 | ||
|         in 5 minutes.
 | ||
| 
 | ||
|       * If ``value`` is a ``datetime`` or ``timedelta`` object, the
 | ||
|         session will expire at that specific date/time. Note that ``datetime``
 | ||
|         and ``timedelta`` values are only serializable if you are using the
 | ||
|         :class:`~django.contrib.sessions.serializers.PickleSerializer`.
 | ||
| 
 | ||
|       * If ``value`` is ``0``, the user's session cookie will expire
 | ||
|         when the user's Web browser is closed.
 | ||
| 
 | ||
|       * If ``value`` is ``None``, the session reverts to using the global
 | ||
|         session expiry policy.
 | ||
| 
 | ||
|       Reading a session is not considered activity for expiration
 | ||
|       purposes. Session expiration is computed from the last time the
 | ||
|       session was *modified*.
 | ||
| 
 | ||
|     .. method:: get_expiry_age()
 | ||
| 
 | ||
|       Returns the number of seconds until this session expires. For sessions
 | ||
|       with no custom expiration (or those set to expire at browser close), this
 | ||
|       will equal :setting:`SESSION_COOKIE_AGE`.
 | ||
| 
 | ||
|       This function accepts two optional keyword arguments:
 | ||
| 
 | ||
|       - ``modification``: last modification of the session, as a
 | ||
|         :class:`~datetime.datetime` object. Defaults to the current time.
 | ||
|       - ``expiry``: expiry information for the session, as a
 | ||
|         :class:`~datetime.datetime` object, an :class:`int` (in seconds), or
 | ||
|         ``None``. Defaults to the value stored in the session by
 | ||
|         :meth:`set_expiry`, if there is one, or ``None``.
 | ||
| 
 | ||
|     .. method:: get_expiry_date()
 | ||
| 
 | ||
|       Returns the date this session will expire. For sessions with no custom
 | ||
|       expiration (or those set to expire at browser close), this will equal the
 | ||
|       date :setting:`SESSION_COOKIE_AGE` seconds from now.
 | ||
| 
 | ||
|       This function accepts the same keyword arguments as :meth:`get_expiry_age`.
 | ||
| 
 | ||
|     .. method:: get_expire_at_browser_close()
 | ||
| 
 | ||
|       Returns either ``True`` or ``False``, depending on whether the user's
 | ||
|       session cookie will expire when the user's Web browser is closed.
 | ||
| 
 | ||
|     .. method:: clear_expired()
 | ||
| 
 | ||
|       Removes expired sessions from the session store. This class method is
 | ||
|       called by :djadmin:`clearsessions`.
 | ||
| 
 | ||
|     .. method:: cycle_key()
 | ||
| 
 | ||
|       Creates a new session key while retaining the current session data.
 | ||
|       :func:`django.contrib.auth.login()` calls this method to mitigate against
 | ||
|       session fixation.
 | ||
| 
 | ||
| .. _session_serialization:
 | ||
| 
 | ||
| Session serialization
 | ||
| ---------------------
 | ||
| 
 | ||
| By default, Django serializes session data using JSON. You can use the
 | ||
| :setting:`SESSION_SERIALIZER` setting to customize the session serialization
 | ||
| format. Even with the caveats described in :ref:`custom-serializers`, 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 :mod:`pickle` to serialize
 | ||
| session data. If you're using the :ref:`signed cookie session backend
 | ||
| <cookie-session-backend>` and :setting:`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 :setting:`SECRET_KEY` leak
 | ||
| immediately escalates to a remote code execution vulnerability.
 | ||
| 
 | ||
| Bundled serializers
 | ||
| ~~~~~~~~~~~~~~~~~~~
 | ||
| 
 | ||
| .. class:: serializers.JSONSerializer
 | ||
| 
 | ||
|     A wrapper around the JSON serializer from :mod:`django.core.signing`. Can
 | ||
|     only serialize basic data types.
 | ||
| 
 | ||
|     In addition, as JSON supports only string keys, note that using non-string
 | ||
|     keys in ``request.session`` won't work as expected::
 | ||
| 
 | ||
|         >>> # initial assignment
 | ||
|         >>> request.session[0] = 'bar'
 | ||
|         >>> # subsequent requests following serialization & deserialization
 | ||
|         >>> # of session data
 | ||
|         >>> request.session[0]  # KeyError
 | ||
|         >>> request.session['0']
 | ||
|         'bar'
 | ||
| 
 | ||
|     Similarly, data that can't be encoded in JSON, such as non-UTF8 bytes like
 | ||
|     ``'\xd9'`` (which raises :exc:`UnicodeDecodeError`), can't be stored.
 | ||
| 
 | ||
|     See the :ref:`custom-serializers` section for more details on limitations
 | ||
|     of JSON serialization.
 | ||
| 
 | ||
| .. class:: serializers.PickleSerializer
 | ||
| 
 | ||
|     Supports arbitrary Python objects, but, as described above, can lead to a
 | ||
|     remote code execution vulnerability if :setting:`SECRET_KEY` becomes known
 | ||
|     by an attacker.
 | ||
| 
 | ||
| .. _custom-serializers:
 | ||
| 
 | ||
| Write your own serializer
 | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~
 | ||
| 
 | ||
| Note that unlike :class:`~django.contrib.sessions.serializers.PickleSerializer`,
 | ||
| the :class:`~django.contrib.sessions.serializers.JSONSerializer` cannot handle
 | ||
| arbitrary Python data types. As is often the case, there is a trade-off between
 | ||
| convenience and security. 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
 | ||
| (:class:`~django.core.serializers.json.DjangoJSONEncoder` may be helpful),
 | ||
| writing a decoder that can reliably get back the same thing that you put in is
 | ||
| more fragile. For example, you run the risk of returning a ``datetime`` that
 | ||
| was actually a string that just happened to be in the same format chosen for
 | ||
| ``datetime``\s).
 | ||
| 
 | ||
| Your serializer class must implement two methods,
 | ||
| ``dumps(self, obj)`` and ``loads(self, data)``, to serialize and deserialize
 | ||
| the dictionary of session data, respectively.
 | ||
| 
 | ||
| Session object guidelines
 | ||
| -------------------------
 | ||
| 
 | ||
| * Use normal Python strings as dictionary keys on ``request.session``. This
 | ||
|   is more of a convention than a hard-and-fast rule.
 | ||
| 
 | ||
| * Session dictionary keys that begin with an underscore are reserved for
 | ||
|   internal use by Django.
 | ||
| 
 | ||
| * Don't override ``request.session`` with a new object, and don't access or
 | ||
|   set its attributes. Use it like a Python dictionary.
 | ||
| 
 | ||
| Examples
 | ||
| --------
 | ||
| 
 | ||
| This simplistic view sets a ``has_commented`` variable to ``True`` after a user
 | ||
| posts a comment. It doesn't let a user post a comment more than once::
 | ||
| 
 | ||
|     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!')
 | ||
| 
 | ||
| This simplistic view logs in a "member" of the site::
 | ||
| 
 | ||
|     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.")
 | ||
| 
 | ||
| ...And this one logs a member out, according to ``login()`` above::
 | ||
| 
 | ||
|     def logout(request):
 | ||
|         try:
 | ||
|             del request.session['member_id']
 | ||
|         except KeyError:
 | ||
|             pass
 | ||
|         return HttpResponse("You're logged out.")
 | ||
| 
 | ||
| The standard :meth:`django.contrib.auth.logout` function actually does a bit
 | ||
| more than this to prevent inadvertent data leakage. It calls the
 | ||
| :meth:`~backends.base.SessionBase.flush` method of ``request.session``.
 | ||
| We are using this example as a demonstration of how to work with session
 | ||
| objects, not as a full ``logout()`` implementation.
 | ||
| 
 | ||
| Setting test cookies
 | ||
| ====================
 | ||
| 
 | ||
| As a convenience, Django provides an easy way to test whether the user's
 | ||
| browser accepts cookies. Just call the
 | ||
| :meth:`~backends.base.SessionBase.set_test_cookie` method of
 | ||
| ``request.session`` in a view, and call
 | ||
| :meth:`~backends.base.SessionBase.test_cookie_worked` in a subsequent view --
 | ||
| not in the same view call.
 | ||
| 
 | ||
| This awkward split between ``set_test_cookie()`` and ``test_cookie_worked()``
 | ||
| is necessary due to the way cookies work. When you set a cookie, you can't
 | ||
| actually tell whether a browser accepted it until the browser's next request.
 | ||
| 
 | ||
| It's good practice to use
 | ||
| :meth:`~backends.base.SessionBase.delete_test_cookie()` to clean up after
 | ||
| yourself. Do this after you've verified that the test cookie worked.
 | ||
| 
 | ||
| Here's a typical usage example::
 | ||
| 
 | ||
|     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')
 | ||
| 
 | ||
| Using sessions out of views
 | ||
| ===========================
 | ||
| 
 | ||
| .. note::
 | ||
| 
 | ||
|     The examples in this section import the ``SessionStore`` object directly
 | ||
|     from the ``django.contrib.sessions.backends.db`` backend. In your own code,
 | ||
|     you should consider importing ``SessionStore`` from the session engine
 | ||
|     designated by :setting:`SESSION_ENGINE`, as below:
 | ||
| 
 | ||
|       >>> from importlib import import_module
 | ||
|       >>> from django.conf import settings
 | ||
|       >>> SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
 | ||
| 
 | ||
| An API is available to manipulate session data outside of a view::
 | ||
| 
 | ||
|     >>> 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).
 | ||
| Calling ``save()`` on a new session may also work but has a small chance of
 | ||
| generating a ``session_key`` that collides with an existing one. ``create()``
 | ||
| calls ``save()`` and loops until an unused ``session_key`` is generated.
 | ||
| 
 | ||
| If you're using the ``django.contrib.sessions.backends.db`` backend, each
 | ||
| session is just a normal Django model. The ``Session`` model is defined in
 | ||
| ``django/contrib/sessions/models.py``. Because it's a normal model, you can
 | ||
| access sessions using the normal Django database 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)
 | ||
| 
 | ||
| Note that you'll need to call
 | ||
| :meth:`~base_session.AbstractBaseSession.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}
 | ||
| 
 | ||
| When sessions are saved
 | ||
| =======================
 | ||
| 
 | ||
| By default, Django only saves to the session database when the session has been
 | ||
| modified -- that is if any of its dictionary values have been assigned or
 | ||
| deleted::
 | ||
| 
 | ||
|     # 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'
 | ||
| 
 | ||
| In the last case of the above example, we can tell the session object
 | ||
| explicitly that it has been modified by setting the ``modified`` attribute on
 | ||
| the session object::
 | ||
| 
 | ||
|     request.session.modified = True
 | ||
| 
 | ||
| To change this default behavior, set the :setting:`SESSION_SAVE_EVERY_REQUEST`
 | ||
| setting to ``True``. When set to ``True``, Django will save the session to the
 | ||
| database on every single request.
 | ||
| 
 | ||
| Note that the session cookie is only sent when a session has been created or
 | ||
| modified. If :setting:`SESSION_SAVE_EVERY_REQUEST` is ``True``, the session
 | ||
| cookie will be sent on every request.
 | ||
| 
 | ||
| Similarly, the ``expires`` part of a session cookie is updated each time the
 | ||
| session cookie is sent.
 | ||
| 
 | ||
| The session is not saved if the response's status code is 500.
 | ||
| 
 | ||
| .. _browser-length-vs-persistent-sessions:
 | ||
| 
 | ||
| Browser-length sessions vs. persistent sessions
 | ||
| ===============================================
 | ||
| 
 | ||
| You can control whether the session framework uses browser-length sessions vs.
 | ||
| persistent sessions with the :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE`
 | ||
| setting.
 | ||
| 
 | ||
| By default, :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` is set to ``False``,
 | ||
| which means session cookies will be stored in users' browsers for as long as
 | ||
| :setting:`SESSION_COOKIE_AGE`. Use this if you don't want people to have to
 | ||
| log in every time they open a browser.
 | ||
| 
 | ||
| If :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` is set to ``True``, Django will
 | ||
| use browser-length cookies -- cookies that expire as soon as the user closes
 | ||
| their browser. Use this if you want people to have to log in every time they
 | ||
| open a browser.
 | ||
| 
 | ||
| This setting is a global default and can be overwritten at a per-session level
 | ||
| by explicitly calling the :meth:`~backends.base.SessionBase.set_expiry` method
 | ||
| of ``request.session`` as described above in `using sessions in views`_.
 | ||
| 
 | ||
| .. note::
 | ||
| 
 | ||
|     Some browsers (Chrome, for example) provide settings that allow users to
 | ||
|     continue browsing sessions after closing and re-opening the browser. In
 | ||
|     some cases, this can interfere with the
 | ||
|     :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` setting and prevent sessions
 | ||
|     from expiring on browser close. Please be aware of this while testing
 | ||
|     Django applications which have the
 | ||
|     :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` setting enabled.
 | ||
| 
 | ||
| Clearing the session store
 | ||
| ==========================
 | ||
| 
 | ||
| As users create new sessions on your website, session data can accumulate in
 | ||
| your session store. If you're using the database backend, the
 | ||
| ``django_session`` database table will grow. If you're using the file backend,
 | ||
| your temporary directory will contain an increasing number of files.
 | ||
| 
 | ||
| To understand this problem, consider what happens with the database backend.
 | ||
| When a user logs in, Django adds a row to the ``django_session`` database
 | ||
| table. Django updates this row each time the session data changes. If the user
 | ||
| logs out manually, Django deletes the row. But if the user does *not* log out,
 | ||
| the row never gets deleted. A similar process happens with the file backend.
 | ||
| 
 | ||
| Django does *not* provide automatic purging of expired sessions. Therefore,
 | ||
| it's your job to purge expired sessions on a regular basis. Django provides a
 | ||
| clean-up management command for this purpose: :djadmin:`clearsessions`. It's
 | ||
| recommended to call this command on a regular basis, for example as a daily
 | ||
| cron job.
 | ||
| 
 | ||
| Note that the cache backend isn't vulnerable to this problem, because caches
 | ||
| automatically delete stale data. Neither is the cookie backend, because the
 | ||
| session data is stored by the users' browsers.
 | ||
| 
 | ||
| Settings
 | ||
| ========
 | ||
| 
 | ||
| A few :ref:`Django settings <settings-sessions>` give you control over session
 | ||
| behavior:
 | ||
| 
 | ||
| * :setting:`SESSION_CACHE_ALIAS`
 | ||
| * :setting:`SESSION_COOKIE_AGE`
 | ||
| * :setting:`SESSION_COOKIE_DOMAIN`
 | ||
| * :setting:`SESSION_COOKIE_HTTPONLY`
 | ||
| * :setting:`SESSION_COOKIE_NAME`
 | ||
| * :setting:`SESSION_COOKIE_PATH`
 | ||
| * :setting:`SESSION_COOKIE_SECURE`
 | ||
| * :setting:`SESSION_ENGINE`
 | ||
| * :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE`
 | ||
| * :setting:`SESSION_FILE_PATH`
 | ||
| * :setting:`SESSION_SAVE_EVERY_REQUEST`
 | ||
| * :setting:`SESSION_SERIALIZER`
 | ||
| 
 | ||
| .. _topics-session-security:
 | ||
| 
 | ||
| Session security
 | ||
| ================
 | ||
| 
 | ||
| Subdomains within a site are able to set cookies on the client for the whole
 | ||
| domain. This makes session fixation possible if cookies are permitted from
 | ||
| subdomains not controlled by trusted users.
 | ||
| 
 | ||
| For example, an attacker could log into ``good.example.com`` and get a valid
 | ||
| session for their account. If the attacker has control over ``bad.example.com``,
 | ||
| they can use it to send their session key to you since a subdomain is permitted
 | ||
| to set cookies on ``*.example.com``. When you visit ``good.example.com``,
 | ||
| you'll be logged in as the attacker and might inadvertently enter your
 | ||
| sensitive personal data (e.g. credit card info) into the attackers account.
 | ||
| 
 | ||
| Another possible attack would be if ``good.example.com`` sets its
 | ||
| :setting:`SESSION_COOKIE_DOMAIN` to ``".example.com"`` which would cause
 | ||
| session cookies from that site to be sent to ``bad.example.com``.
 | ||
| 
 | ||
| Technical details
 | ||
| =================
 | ||
| 
 | ||
| * The session dictionary accepts any :mod:`json` serializable value when using
 | ||
|   :class:`~django.contrib.sessions.serializers.JSONSerializer` or any
 | ||
|   picklable Python object when using
 | ||
|   :class:`~django.contrib.sessions.serializers.PickleSerializer`. See the
 | ||
|   :mod:`pickle` module for more information.
 | ||
| 
 | ||
| * Session data is stored in a database table named ``django_session`` .
 | ||
| 
 | ||
| * Django only sends a cookie if it needs to. If you don't set any session
 | ||
|   data, it won't send a session cookie.
 | ||
| 
 | ||
| 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
 | ||
| :setting:`SESSION_ENGINE`.
 | ||
| 
 | ||
| All ``SessionStore`` classes available in Django inherit from
 | ||
| :class:`~backends.base.SessionBase` and implement data manipulation methods,
 | ||
| namely:
 | ||
| 
 | ||
| * ``exists()``
 | ||
| * ``create()``
 | ||
| * ``save()``
 | ||
| * ``delete()``
 | ||
| * ``load()``
 | ||
| * :meth:`~backends.base.SessionBase.clear_expired`
 | ||
| 
 | ||
| In order to build a custom session engine or to customize an existing one, you
 | ||
| may create a new class inheriting from :class:`~backends.base.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:
 | ||
| 
 | ||
| Extending database-backed session engines
 | ||
| =========================================
 | ||
| 
 | ||
| Creating a custom database-backed session engine built upon those included in
 | ||
| Django (namely ``db`` and ``cached_db``) may be done by inheriting
 | ||
| :class:`~base_session.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 :setting:`INSTALLED_APPS`.
 | ||
| 
 | ||
| .. class:: base_session.AbstractBaseSession
 | ||
| 
 | ||
| 
 | ||
|     The abstract base session model.
 | ||
| 
 | ||
|     .. attribute:: 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).
 | ||
| 
 | ||
|     .. attribute:: session_data
 | ||
| 
 | ||
|         A string containing an encoded and serialized session dictionary.
 | ||
| 
 | ||
|     .. attribute:: 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 :djadmin:`clearsessions` management
 | ||
|         command is run.
 | ||
| 
 | ||
|     .. classmethod:: get_session_store_class()
 | ||
| 
 | ||
|         Returns a session store class to be used with this session model.
 | ||
| 
 | ||
|     .. method:: get_decoded()
 | ||
| 
 | ||
|         Returns decoded session data.
 | ||
| 
 | ||
|         Decoding is performed by the session store class.
 | ||
| 
 | ||
| You can also customize the model manager by subclassing
 | ||
| :class:`~django.contrib.sessions.base_session.BaseSessionManager`:
 | ||
| 
 | ||
| .. class:: base_session.BaseSessionManager
 | ||
| 
 | ||
|     .. method:: 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.
 | ||
| 
 | ||
|     .. method:: 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()
 | ||
| 
 | ||
|         Override this method to return a custom session model if you need one.
 | ||
| 
 | ||
|     .. method:: create_model_instance(data)
 | ||
| 
 | ||
|         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.
 | ||
| 
 | ||
|     .. attribute:: cache_key_prefix
 | ||
| 
 | ||
|         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)
 | ||
| 
 | ||
|         @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'
 | ||
| 
 | ||
|         # ...
 | ||
| 
 | ||
| Session IDs in URLs
 | ||
| ===================
 | ||
| 
 | ||
| The Django sessions framework is entirely, and solely, cookie-based. It does
 | ||
| not fall back to putting session IDs in URLs as a last resort, as PHP does.
 | ||
| This is an intentional design decision. Not only does that behavior make URLs
 | ||
| ugly, it makes your site vulnerable to session-ID theft via the "Referer"
 | ||
| header.
 |