基於How To Tango With Django 1.9的重新實踐(10)——Cookies and Sessions
在本章中我們將要介紹sessions和cookies,它們兩個在現代wen應用中有著至關重要的作用.在上一章,Django框架使用sessions和cookies來處理使用者登入和登出功能(都在背後執行).這裡我們將要探索cookies的其他用途.
10.1 Cookies,無處不在的Cookies!
每當對網站做出請求時,web伺服器返回所請求的頁面的內容。此外,一個或多個cookie也可以作為請求的一部分被髮送。將cookie視為從伺服器傳送到客戶端的一小段資訊。當要傳送請求時,客戶端檢查以檢視是否在客戶端上存在與伺服器地址匹配的任何Cookie。如果是,請求中包含它們。然後,伺服器可以將Cookie解釋為請求的上下文的一部分,並生成適合的響應。
例如,您可以使用特定使用者名稱和密碼登入網站。當您進行身份驗證後,可能會在瀏覽器中返回包含您的使用者名稱的Cookie,表明您現在已登入到該網站。每次請求時,此資訊都會傳回到您的登入資訊用於呈現相應頁面的伺服器,可能包括您在網頁上特定位置的使用者名稱。你的會話不能永遠持續,但是cookie 必須在某個時間點過期 - 它們不能是無限長度。包含敏感資訊的Web應用程式可能只在幾分鐘不活動後就會過期。具有瑣碎資訊的不同的Web應用程式可能在最後一次互動後半小時或甚至幾周後到期。
術語cookie實際上不是從你吃的食物中衍生出來的,但是從術語magic cookie,程式接收和傳送的資料包不變。
以Cookie形式傳遞資訊可能會在Web應用程式的設計中產生潛在的安全漏洞。這就是為什麼Web應用程式的開發人員在使用Cookie時需要非常小心的原因。當使用cookie時,設計師必須總是問自己:你想要儲存為cookie的資訊真的需要傳送並存儲在客戶機器上嗎?在許多情況下,有更安全的解決方案。例如,在電子商務網站上將使用者的信用卡號作為Cookie傳遞會非常不安全。如果使用者的計算機被盜用了怎麼辦?惡意程式可能會接受cookie。從那裡,黑客會有他或她的信用卡號碼 - 所有因為你的Web應用程式的設計從根本上有缺陷。
11.2 Sessions和無狀態協議
所有的瀏覽器和伺服器之間的互動都會通過HTTP協議.通過第8章簡單的接觸我們知道HTTP是無狀態協議.這就意味著每次客戶端請求(HTTPGET)或者傳送(HTTPPOST)資源給伺服器都必須新建立一個連線(一個TCP連線).
因為客戶端和伺服器沒有持續的連線,兩端的軟體不能僅僅通過單獨的連線保持會話狀態.例如,客戶端每次都需要告訴伺服器誰在這個主機上登入了這個web應用.這就是使用者和伺服器之間的會話,這也是session的基本 - a semi-permanent exchange of information.作為一個無狀態協議,HTTP需要保持會話狀態非常的困難 - 但是很走運我們可以使用幾種技術來繞過這個問題.
最常用的一種保持狀態的方法就是使用session ID,和cookies一樣儲存在客戶端電腦.session ID可以認為是一種令牌(一大串字串),它能夠唯一標識特定web應用裡的會話.和cookies儲存許多不同種類的資料不一樣(像使用者名稱,名字,密碼),它只儲存session ID,並且對映到web伺服器的一個數據結構.在這個資料結構裡,你可以儲存任何你需要的資訊.這對於使用者來說是更安全的儲存方法.用這種方法,這些資料不會被一個不安全的客戶端和連線所監聽.
如果你的瀏覽器支援cookies,當你訪問所有的網站時都會建立一個新的會話.你可以自己檢視下,如圖所示.在Google Chrome的開發者工具中,你可以檢視到web伺服器傳送給你的cookies.在下圖中,你可以觀察到選中的sessionidcookie.這個cookie包含一系列的字母和數字,它可以使Django唯一標識一個會話.到現在,所有的session細節都表述完了 - 但是伺服器端還沒講.
11.3 在Django裡設定Sessions
儘管我們已經設定好並且正確工作,但是如果學會Django的模組提供哪些工作就會更好了.關於sessions方面Django提供了middleware來提供session功能.
為了檢查一切都設定妥當,開啟Django專案裡的settings.py檔案.找到MIDDLEWARE_CLASSES元組.你可以在元組中看到django.contrib.sessions.middleware.SessionMiddleware模組 - 如果沒有看到現在就加進去.正式這個SessionMiddleware中介軟體能夠建立唯一的sessionidcookies.
SessionMiddleware可以靈活的使用不同的方法來儲存session資訊.你可以採取很多方法儲存 - 存在檔案,資料庫甚至在cache中.最直接的方法是使用django.contrib.sessions應用把session資訊儲存到Django模型/資料庫中(詳細的說是django.contrib.sessions.models.Session模型).使用這種方法,你必須首先確保在Django專案的settings.py檔案裡django.contrib.sessions儲存在INSTALLED_APPS元組裡.如果你現在要新增應用,你需要使用遷移命令來更新資料庫.
11.4 基於cookie的session
我們現在測試我們的瀏覽器是否支援cookies.現代瀏覽器都支援cookies,你可以檢視你瀏覽器的cookies資訊.如果你的瀏覽器安全等級設定的非常高,一些特定的cookies會被阻塞.檢視瀏覽器文件獲取更多資訊,設定使用cookies.
11.4.1 測試Cookie功能
為了測試cookies,你可以使用Django的request物件提供的便捷方法.其中set_test_cookie()
,test_cookie_worked()
和delete_test_cookie()
方法對我們比較有用.在一個視圖裡,你需要設定一個cookie.在另一個檢視你需要檢查cookie是否存在.兩個不同的檢視都要請求cookies,是因為你要檢視是否客戶端接收了來自伺服器的cookie.
我們將會使用前面建立的兩個檢視,index()和about()。我們將使用Django開發伺服器的終端輸出來驗證Cookie是否正常工作,而不是在頁面上顯示任何內容。
在Rango的views.py檔案中,找到您的index()檢視。將以下行新增到檢視。為了確保執行行,請確保將其作為檢視的第一行。
request.session.set_test_cookie()
在register()檢視頂部加入下面3行程式碼 - 同樣是為了保證它們能被執行.
if request.session.test_cookie_worked():
print ">>>> TEST COOKIE WORKED!"
request.session.delete_test_cookie()
更改完以後執行Django服務並開啟Rango首頁,http://127.0.0.1:8000/rango/
.頁面載入完後,前往註冊頁面.當註冊頁面載入後,你將會和下圖一樣在終端裡看到>>>> TEST COOKIE WORKED!.
你可以刪除增加的程式碼 - 我們只是拿它用來驗證以下cookies
11.5 客戶端Cookie:站點計數器示例
現在我們已經知道cookies是如何工作的,讓我們實現一個簡單的網站訪問計數.首先我們需要建立兩個cookies:一個用來追蹤使用者訪問Rango網站次數,另一個用來追蹤上一次登入的時間.保持追蹤使用者上一次的訪問時間和日期將允許我們每天只能增長一次訪問次數.
假設使用者訪問的Rango頁面一般為首頁.開啟rango/view.py並修改index()檢視如下.
注意,它在技術上不是一個檢視,因為它不返回一個response物件 - 它只是一個幫助函式。
def index(request):
'''
#此段程式碼用於驗證cookies是否正常工作
request.session.set_test_cookie()
'''
#使用order_by對likes進行降序排列,取前五個儲存到category_list
category_list = Category.objects.order_by('-likes')[:5]
page_list = Page.objects.order_by('-views')[:5]
context_dict = {'categories':category_list,'pages':page_list}
#儘早獲取我們的Response物件,以便我們可以新增cookie資訊。
response = render(request,'rango/index.html',context_dict)
#呼叫幫助函式來處理cookie
visitor_cookie_handle(request,response)
#將響應返回給使用者,更新所有需要更改的Cookie。
return response
#如果你想出去測試此程式碼,而無需等待一天,改days到seconds。這樣,訪問計數器可以每秒更新,而不是每天更新。
def visitor_cookie_handle(request,response):
'''
獲取網站的訪問次數,我們使用COOKIES.get()函式獲取訪問Cookie。
如果cookie存在,則返回的值將轉換為整數。
如果cookie不存在,則使用預設值1。
注意返回的所有的cookie值都是字串;不要認為cookie儲存的是數字就會返回整型.你需要自己把它們轉化成正確的形式.
如果這個cookie不存在,你需要使用response物件的set_cookie()方法來建立cookie.
這個方法包含兩個值,cookie名(字串)和cookie值.不管你輸入的是什麼形式 - 它都會自動轉化成字串.
'''
visits = int(request.COOKIES.get('visits','1'))
#獲取cookie的值
last_visit_cookie = request.COOKIES.get('last_visit',str(datetime.now()))
#把值轉換為Python date/time 物件
last_visit_time = datetime.strptime(last_visit_cookie[:-7],'%Y-%m-%d %H:%M:%S')
#如果自從上次登入超過一天
if (datetime.now()-last_visit_time).days > 0:
visits = visits + 1
#現在更新上次訪問的cookie,我們已經更新了count
response.set_cookie('last_visit',str(datetime.now()))
else:
visits = 1
#設定上次訪問的cookie
response.set_cookie('last_visit',last_visit_cookie)
#更新/設定visits的cookie
response.set_cookie('visit',visits)
讀下來這段程式碼你將會看到大部分的程式碼都是在處理當前日期和時間.所以在這裡你需要在views.py檔案的頭部加入Python的datetime模組.
from datatime import datetime
確保引進了datetime模組裡的datetime物件.
在程式碼中我們檢查last_visit
是否存在.如果存在我們就使用request.COOKIES['cookie_name']
語法來獲取它的值,這裡的request就是request物件的名字,cookie_name
就是你希望跟蹤的cookie名.注意返回的所有的cookie值都是字串;不要認為cookie儲存的是數字就會返回整型.你需要自己把它們轉化成正確的形式.如果這個cookie不存在,你需要使用response物件的set_cookie()
方法來建立cookie.這個方法包含兩個值,cookie名(字串)和cookie值.不管你輸入的是什麼形式 - 它都會自動轉化成字串.
您可能會注意到,visits重新整理網路瀏覽器時Cookie不會遞增。為什麼?我們在上面提供的示例程式碼僅在使用者重新訪問Rango主頁後至少增加一天。這是一個不可接受的時間等待測試
- 所以為什麼不臨時更改延遲到更短的時間段?在更新的index檢視中,找到以下行。if (datetime.now() - last_visit_time).days > 0:
我們可以輕鬆地更改此行以比較 訪問之間的秒數。在下面的示例中,我們檢查使用者是否至少五秒前訪問過。
if (datetime.now() - last_visit_time).seconds > 5:
這意味著您只需等待五秒鐘即可檢視visitsCookie增量,而不是一整天。當您的程式碼執行良好時,您可以將比較恢復為原始的每天時間段。
能夠使用-運算子找到時間之間的差異是Python提供的許多令人敬畏的功能之一。當減去時間時,timedelta返回一個物件,它提供了
我們在上面的程式碼片段中使用的屬性days和seconds屬性。你可以檢視官方的Python文件
,獲取關於這種型別物件的更多資訊,以及它提供的其他屬性。
11.6 Session資料
在前一個例子中我們使用了客戶端的cookies.然而,另一種更安全的方法是把session資訊儲存在伺服器端.我們可以使用儲存在客戶端的session ID cookie作為解鎖資料的鑰匙.
使用基於cookie的session你需要完成以下幾步.
- 確保settings.py檔案的MIDDLEWARE_CLASSES包含django.contrib.sessions.middleware.SessionMiddleware.
- 配置session後臺.確定django.contrib.sessions在你settings.py檔案的INSTALLED_APPS裡.如果沒有則新增並且執行資料庫遷移命令python
manage.py migrate. - 假設使用資料庫作為後臺,但是你也可以設定成其他(比如cache).參見 official Django Documentation on Sessions for other backend configurations.
而不是直接儲存在請求(因此在客戶端的機器上),您可以通過該方法訪問伺服器端資料,request.session.get()並存儲它們request.session[]。請注意,會話ID cookie仍然用於記住客戶端的計算機(因此技術上存在瀏覽器端Cookie)。但是,所有的使用者/會話資料都儲存在伺服器端。Django的會話中介軟體處理客戶端cookie和使用者/會話資料的儲存。
要使用伺服器端資料,我們需要重構我們目前為止編寫的程式碼。首先,我們需要更新visitor_cookie_handler()
函式,以便它訪問伺服器端的Cookie。我們可以通過呼叫來做到這一點request.session.get()
,並將它們儲存在字典中request.session[]
。為了幫助我們,我們做了一個幫助函式呼叫get_server_side_cookie()
,請求一個cookie的請求。如果cookie在會話資料中,則返回其值。否則,將返回預設值。
由於所有的cookie都儲存在伺服器端,我們不會直接更改響應。因此,我們可以從visitor_cookie_handler()
函式定義中刪除response.
11.7 Browser-Length and Persistent Sessions
當你使用cookies時你可以使用Django的session框架來設定browser-lenght sessions或者persistent sessions.這兩個名字的解釋如下:
- browser-length sessions是當瀏覽器關閉時截至.
- persistent sessions可以隨你的選擇而結束.它可以是一個小時甚至是一個月.
預設的browser-length sessions是被關閉的.你可以通過設定Django的settings.py來開啟它(增加SESSION_EXPIRE_AT_BROWSER_CLOSE
並設定為true).
persistent session
預設是開啟的,可以設定SESSION_EXPIRE_AT_BROWSER_CLOSE
為False
進行開啟.persistent sessions
有一個額外的設定SESSION_COOKIE_AGE
,它可以允許你設定cookie的存活時間.這個值是一個整型,代表著cookie能存活的秒數.例如修改為1209600意味著網頁的cookies將會在兩週後過期.
11.8 清除Sessions資料庫
Session cookies是不斷累積的.所以如果你是使用資料庫作為後臺你需要定期的清除儲存的cookies.可以用python manage.py clearsessions
命令來實現.Django文件裡建議每天執行一次.
11.9 基本的注意事項和流程
在Django應用中使用cookies,有一些事情你需要注意:
- 首先,考慮你的web應用需要什麼型別的cookies.你儲存的資訊需要持續儲存還是在會話結束後就截至?
- 對你使用cookies儲存的資訊要格外小心.儲存在cookies裡的資訊同樣會呈現在使用者的電腦上.這樣做的風險非常的大:你不會知道使用者的電腦是多麼的危險.如果儲存一些敏感的資訊還是存在伺服器端吧.
- 另一個致命的是使用者可以設定他的瀏覽器級別非常的高,這將會阻止你的cookies.一旦你的cookies被阻攔,你的網站功能就會異常.你必須想到這一點.你無法控制使用者瀏覽器.
如果你的情況適合客戶端cookies,你可以按照如下步驟進行:
- 首先檢查你希望的cookie是否存在.可以用檢查request引數實現.
request.COOKIES.has_key('<cookie_name>')
函式會返回一個布林值來告訴我們是否有一個叫<cookie_name>
的cookie存在. - 如果這個cookie存在,你可以通過request.COOKIES[]來獲取.COOKIES屬性是一個字典,你可以把你希望獲取的cookie名寫入方括號中.記住所有的cookies都返回為字串.因此你必須把它們轉化成正確的型別.
- 如果cookie不存在或者你希望更新cookie.你可以使用
response.set_cookie('<cookie_name>',value)
函式來實現,這裡的兩個引數分別是cookie的名字和他們的值.
如果你需要更安全的cookies,那麼就需要使用基於cookies的session:
- 確保settings.py中的MIDDLEWARE_CLASSES包含
django.contrib.sessions.middleware.SessionMiddleware.
- 設定你的session後臺SESSION_ENGINE.檢視 official Django Documentation on Sessions 獲取不同後臺的不同設定.
- 通過
requests.sessions.get()
來獲取存在的cookie. - 通過
requests.session['<cookie_name>']
更改或設定cookie.
11.10Exercises
- 檢查您的Cookie是伺服器端。清除瀏覽器的快取和Cookie,然後檢查以確保您在瀏覽器中看不到last_visit和visits變數。請注意,您仍然會看到該sessionidCookie。Django使用這個cookie在資料庫中查詢會話,它儲存了該會話的所有伺服器端cookie。
- 更新About頁面的檢視和模板,告訴訪問者他們已經登陸這個站點的次數。記得呼叫
visitor_cookie_handler()
在你試圖得到訪客的cookie從request.session
字典,否則如果cookie沒有設定,它將顯示一個錯誤。