django 1.8 官方文檔翻譯: 13-9-1 如何使用會話

如何使用會話

Django 提供對匿名會話的徹底支持。其會話框架讓你根據各個站點的訪問者存儲和訪問任意數據。它在服務器端存儲數據並抽象Cookie 的發送和接收。Cookie 包含會話的ID —— 不是數據自己(除非你使用基於Cookie 的後端)。html

啓用會話

會話是經過一箇中間件實現的。python

爲了啓用會話功能,須要這樣作:數據庫

編輯MIDDLEWARE_CLASSES 設置並確保它包含'django.contrib.sessions.middleware.SessionMiddleware'。django-admin startproject建立的默認的settings.py已經啓用SessionMiddleware
若是你不想使用會話,你也能夠從MIDDLEWARE_CLASSES中刪除SessionMiddleware行,並從INSTALLED_APPS中刪除'django.contrib.sessions'。它將節省一些性能消耗。django

配置會話引擎

默認狀況下,Django 存儲會話到你的數據庫中(使用django.contrib.sessions.models.Session模型)。雖然這很方便,可是在某些架構中存儲會話在其它地方會更快,因此能夠配置Django 來存儲會話到你的文件系統上或緩存中。json

使用數據庫支持的會話

若是你想使用數據庫支持的會話,你須要添加'django.contrib.sessions' 到你的INSTALLED_APPS設置中。後端

在配置完成以後,請運行manage.py migrate來安裝保存會話數據的一張數據庫表。瀏覽器

使用基於緩存的會話

爲了更好的性能,你可能想使用一個基於緩存的會話後端。緩存

爲了使用Django 的緩存系統來存儲會話數據,你首先須要確保你已經配置好你的緩存;詳細信息參見緩存的文檔。安全

警告服務器

你應該只在使用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 會話後端,你還須要遵循使用數據庫支持的會話中的配置說明。

Changed in Django 1.7:

在1.7 版以前,`cached_db` 永遠使用`default`緩存而不是`SESSION_CACHE_ALIAS`。

使用基於文件的緩存

要使用基於文件的緩存,請設置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 設置。

建議保留SESSIONCOOKIEHTTPONLY 設置爲True 以防止從JavaScript 中訪問存儲的數據。

警告

若是SECRET_KEY 沒有保密而且你正在使用 PickleSerializer,這可能致使遠端執行任意的代碼。

擁有SECRET_KEY 的攻擊者不只能夠生成篡改的會話數據而你的站點將會信任這些數據,並且能夠遠程執行任何代碼,就像數據是經過pickle 序列化過的同樣。

若是你使用基於Cookie 的會話,請格外注意你的安全祕鑰對於任何能夠遠程訪問的系統都是永遠徹底保密的。

會話數據通過簽名但沒有加密。

若是使用基於Cookie的會話,則會話數據能夠被客戶端讀取。

MAC(消息認證碼)被用來保護數據不被客戶端修改,因此被篡改的會話數據將是變成不合法的。若是保存Cookie的客戶端(例如你的瀏覽器)不能保存全部的會話Cookie或丟失數據,會話一樣會變得不合法。儘管Django 對數據進行壓縮,仍然徹底有可能超過每一個Cookie 常見的4096 個字節的限制

沒有更新保證

還要注意,雖然MAC能夠保證數據的權威性(由你的站點生成,而不是任何其餘人)和完整性(包含所有的數據而且是正確的),它不能保證是最新的,例如返回給你發送給客戶端的最新的數據。這意味着對於某些會話數據的使用,基於Cookie 可能讓你受到重放攻擊。其它方式的會話後端在服務器端保存每一個會話並在用戶登出時使它無效,基於Cookie 的會話在用戶登出時不會失效。所以,若是一個攻擊者盜取用戶的Cookie,它們可使用這個Cookie 來以這個用戶登陸即便用戶已登出。Cookies 只能被當作是「過時的」,若是它們比你的SESSIONCOOKIEAGE要舊。

性能

最後,Cookie 的大小對你的網站的速度 有影響。

在視圖中使用會話

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']。若是給出的key 在會話中不存在,將拋出 KeyError

__contains__(key)

例如:'fav_color' in request.session

get(key, default=None)

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

pop(key)

例如:fav_color = request.session.pop('fav_color')

keys()

items()

setdefault()

clear()

它還具備這些方法:

flush()

刪除當前的會話數據並刪除會話的Cookie。這用於確保前面的會話數據不能夠再次被用戶的瀏覽器訪問(例如,django.contrib.auth.logout() 函數中就會調用它)。

Changed in Django 1.8:

刪除會話Cookie 是Django 1.8 中的新行爲。之前,該行爲用於從新生成會話中的值,這個值會在Cookie 中發回給用戶。

set_test_cookie()

設置一個測試的Cookie 來驗證用戶的瀏覽器是否支持Cookie。由於Cookie 的工做方式,只有到用戶的下一個頁面才能驗證。更多信息參見下文的設置測試的Cookie。

test_cookie_worked()

返回TrueFalse,取決於用戶的瀏覽器時候接受測試的Cookie。由於Cookie的工做方式,你必須在前面一個單獨的頁面請求中調用set_test_cookie()。更多信息參見下文的設置測試的Cookie。

delete_test_cookie()

刪除測試的Cookie。使用這個函數來本身清理。

set_expiry(value)

設置會話的超時時間。你能夠傳遞一系列不一樣的值:

  • 若是value 是一個整數,會話將在這麼多秒沒有活動後過時。例如,調用request.session.set_expiry(300) 將使得會話在5分鐘後過時。

  • 若果value 是一個 datetimetimedelta 對象,會話將在這個指定的日期/時間過時。注意datetimetimedelta 值只有在你使用PickleSerializer 時纔可序列化。

  • 若是value 爲0,那麼用戶會話的Cookie將在用戶的瀏覽器關閉時過時。

  • 若是valueNone,那麼會話轉向使用全局的會話過時策略。

過時的計算不考慮讀取會話的操做。會話的過時從會話上次修改的時間開始計算。

get_expiry_age()

返回會話離過時的秒數。對於沒有自定義過時的會話(或者設置爲瀏覽器關閉時過時的會話),它將等於SESSION_COOKIE_AGE

該函數接收兩個可選的關鍵字參數:

  • modification:會話的最後一次修改時間,類型爲一個datetime 對象。默認爲當前的時間。

  • expiry:會話的過時信息,類型爲一個datetime 對象、一個整數(以秒爲單位)或None。默認爲經過set_expiry()保存在會話中的值,若是沒有則爲None

get_expiry_date()

返回過時的日期。對於沒有自定義過時的會話(或者設置爲瀏覽器關閉時過時的會話),它將等於從如今開始SESSION_COOKIE_AGE秒後的日期。

這個函數接受與get_expiry_age()同樣的關鍵字參數。

get_expire_at_browser_close()

返回TrueFalse,取決於用戶的會話Cookie在用戶瀏覽器關閉時會不會過時。

clear_expired()

從會話的存儲中清除過時的會話。這個類方法被clearsessions調用。

cycle_key()

建立一個新的會話,同時保留當前的會話數據。django.contrib.auth.login() 調用這個方法來減緩會話的固定。

會話的序列化

在1.6 版之前,在保存會話數據到後端以前Django 默認使用pickle 來序列化它們。若是你使用的是簽名的Cookie 會話後端 而且SECRET_KEY 被攻擊者知道(Django 自己沒有漏洞會致使它被泄漏),攻擊者就能夠在會話中插入一個字符串,在unpickle 以後能夠在服務器上執行任何代碼。在因特網上這個攻擊技術很簡單並很容易查到。儘管Cookie 會話的存儲對Cookie 保存的數據進行了簽名以防止篡改,SECRET_KEY 的泄漏會當即使得能夠執行遠端的代碼。

這種攻擊能夠經過JSON而不是pickle序列化會話數據來減緩。爲了幫助這個功能,Django 1.5.3 引入一個新的設置,SESSION_SERIALIZER,來自定義會話序列化的格式。爲了向後兼容,這個設置在Django 1.5.x 中默認爲django.contrib.sessions.serializers.PickleSerializer,可是爲了加強安全性,在Django 1.6 中默認爲django.contrib.sessions.serializers.JSONSerializer。即便在編寫你本身的序列化方法講述的說明中,咱們也強烈建議依然使用JSON 序列化,特別是在你使用的是Cookie 後端時。

綁定的序列化方法

class serializers.JSONSerializer

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 序列化的限制。

class serializers.PickleSerializer

支持任意Python 對象,可是正如上面描述的,可能致使遠端執行代碼的漏洞,若是攻擊者知道了SECRET_KEY

編寫你本身的序列化器

注意,與PickleSerializer不一樣,JSONSerializer 不能夠處理任意的Python 數據類型。這是常見的狀況,須要在便利性和安全性之間權衡。若是你但願在JSON 格式的會話中存儲更高級的數據類型好比datetimeDecimal,你須要編寫一個自定義的序列化器(或者在保存它們到request.session中以前轉換這些值到一個可JSON 序列化的對象)。雖然序列化這些值至關簡單直接 (django.core.serializers.json.DateTimeAwareJSONEncoder 可能幫得上忙),編寫一個解碼器來可靠地取出相同的內容卻能困難。例如,返回一個datetime 時,它可能其實是與datetime 格式碰巧相同的一個字符串)。

你的序列化類必須實現兩個方法,dumps(self, obj)loads(self, data) 來分別序列化和去序列化會話數據的字典。

會話對象指南

在request.session 上使用普通的Python 字符串做爲字典的鍵。這主要是爲了方便而不是一條必須遵照的規則。
以一個下劃線開始的會話字典的鍵被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() 函數實際上所作的內容比這個要多一點以防止意外的數據泄露。它調用的request.sessionflush()方法。咱們使用這個例子來演示如何利用會話對象來工做,而不是一個完整的logout()實現。

設置測試的Cookie

爲了方便,Django 提供一個簡單的方法來測試用戶的瀏覽器時候接受Cookie。只需在一個視圖中調用request.sessionset_test_cookie()方法,並在接下來的視圖中調用test_cookie_worked() —— 不是在同一個視圖中調用。

因爲Cookie的工做方式,在set_test_cookie()test_cookie_worked() 之間這種笨拙的分離是必要的。當你設置一個Cookie,直到瀏覽器的下一個請求你不可能真實知道一個瀏覽器是否接受了它。

使用delete_test_cookie() 來本身清除測試的Cookie是一個很好的實踐。請在你已經驗證測試的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')

在視圖外使用會話

這一節中的示例直接從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.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中。由於它是一個普通的模型,你可使用普通的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'

上面例子的最後一種狀況,咱們能夠經過設置會話對象的modified屬性顯式地告訴會話對象它已經被修改過:

request.session.modified = True

若要修改這個默認的行爲,能夠設置 SESSION_SAVE_EVERY_REQUESTTrue。當設置爲True時,Django 將對每一個請求保存會話到數據庫中。

注意會話的Cookie 只有在一個會話被建立或修改後纔會發送。若是SESSION_SAVE_EVERY_REQUESTTrue,會話的Cookie 將在每一個請求中發送。

相似地,會話Cookie 的expires 部分在每次發送會話Cookie 時更新。

若是響應的狀態碼時500,則會話不會被保存。

瀏覽器時長的會話 VS. 持久的會話

你能夠經過SESSION_EXPIRE_AT_BROWSER_CLOSE設置來控制會話框架使用瀏覽器時長的會話,仍是持久的會話。

默認狀況下,SESSION_EXPIRE_AT_BROWSER_CLOSE設置爲False,表示會話的Cookie 保存在用戶的瀏覽器中的時間爲SESSION_COOKIE_AGE。若是你不想讓你們每次打開瀏覽器時都須要登陸時能夠這樣使用。

若是SESSION_EXPIRE_AT_BROWSER_CLOSE 設置爲True,Django 將使用瀏覽器時長的Cookie —— 用戶關閉他們的瀏覽器時當即過時。若是你想讓你們在每次打開瀏覽器時都須要登陸時能夠這樣使用。

這個設置是一個全局的默認值,能夠經過顯式地調request.sessionset_expiry() 方法來覆蓋,在上面的在視圖中使用會話中有描述。

某些瀏覽器(例如Chrome)提供一種設置,容許用戶在關閉並從新打開瀏覽器後繼續使用會話。在某些狀況下,這可能干擾SESSION_EXPIRE_AT_BROWSER_CLOSE 設置並致使會話在瀏覽器關閉後不會過時。在測試啓用SESSION_EXPIRE_AT_BROWSER_CLOSE設置的Django 應用時請注意這點。

清除存儲的會話

隨着用戶在你的網站上建立新的會話,會話數據可能會在你的會話存儲倉庫中積累。若是你正在使用數據庫做爲後端,django_session 數據庫表將持續增加。若是你正在使用文件做爲後端,你的臨時目錄包含的文件數量將持續增加。

要理解這個問題,考慮一下數據庫後端發生的狀況。當一個用戶登入時,Django 添加一行到django_session 數據庫表中。每次會話數據更新時,Django 將更新這行。若是用戶手工登出,Django 將刪除這行。可是若是該用戶不登出,該行將永遠不會刪除。以文件爲後端的過程相似。

Django 不提供自動清除過時會話的功能。所以,按期地清除會話是你的任務。Django 提供一個清除用的管理命令來知足這個目的:clearsessions。建議定義調用這個命令,例如做爲一個天天運行的Cron 任務。

注意,以緩存爲後端不存在這個問題,由於緩存會自動刪除過時的數據。以cookie 爲後端也不存在這個問題,由於會話數據經過用戶的瀏覽器保存。

設置

一些Django 設置 讓你能夠控制會話的行爲:

會話的安全

一個站點下的子域名可以在客戶端爲整個域名設置Cookie。若是子域名不收信任的用戶控制且容許來自子域名的Cookie,那麼可能發生會話固定。

例如,一個攻擊者能夠登陸good.example.com併爲他的帳號獲取一個合法的會話。若是該攻擊者具備bad.example.com的控制權,那麼他可使用這個域名來發送他的會話ID給你,由於子域名容許在*.example.com上設置Cookie。當你訪問good.example.com時,你將被登陸成攻擊者而沒有注意到並輸入你的敏感的我的信息(例如,信用卡信息)到攻擊者的帳號中。

另一個可能的攻擊是,若是good.example.com設置它的 SESSION_COOKIE_DOMAIN 爲".example.com" ,這將致使來自該站點的會話Cookie 被髮送到bad.example.com

技術細節

  • 當使用JSONSerializer時,會話字典接收任何可json 序列化的值,當使用PickleSerializer時接收任何pickleable 的Python對象。更多信息參見pickle 模塊。

  • 會話數據存儲在數據中名爲django_session 的表中。

  • Django 只發送它須要的Cookie。若是你沒有設置任何會話數據,它將不會發送會話Cookie。

URL 中的會話ID

Django 會話框架徹底地、惟一地基於Cookie。它不像PHP同樣,實在沒辦法就把會話的ID放在URL 中。這是一個故意的設計。這個行爲不只使得URL變得醜陋,還使得你的網站易於受到經過"Referer" 頭部竊取會話ID的攻擊。

譯者:Django 文檔協做翻譯小組,原文:Sessions

本文以 CC BY-NC-SA 3.0 協議發佈,轉載請保留做者署名和文章出處。

Django 文檔協做翻譯小組人手緊缺,有興趣的朋友能夠加入咱們,徹底公益性質。交流羣:467338606。

相關文章
相關標籤/搜索