iT邦幫忙

0

Django學習紀錄 12.Cookies與Sessions

之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
https://ithelp.ithome.com.tw/upload/images/20190724/20118889bj9fH1vhuR.jpg
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼

目錄:django系列文章-Django學習紀錄

12. Cookies與Sessions

12.1 Http協定的不足

單純的http協定無法讓網站保持著某些資訊跟狀態,因為它就只是非常制式地接收要求,回應要求
每一次的要求,每一次的回應都是獨立的,因為http無法記錄任何狀態,它並不能保持著上一次要求所帶來的任何有用資訊
當然,透過GET與POST我們依然可以將資料透過新的request帶到下一個頁面,但是,這樣相當麻煩,而且沒有效率
將Http協定的不足分為以下三點:

12.1.1 根本不知道使用者

伺服器不知道有某用戶
使用資料庫紀錄用戶的資訊便可解決

12.1.2 知道使用者但不會做資料比對

即使伺服器記錄了用戶的資訊,但不做比對,也是認不出用戶的
這時就需要仰賴登入及辨識了

12.1.3 知道使用者,也會比對,但使用者在每一次的請求中都要比對

只要用戶每次都辛苦地提供資訊,伺服器就可以辨識的出用戶
但我們不希望在同一個網站中每次要求一個新頁面時,都要重新登入一次
倒不如讓伺服器給用戶一塊餅乾,只要你拿這塊餅乾出來,我就可以認得你

12.2 Cookies-好餅乾,不吃嗎?

這塊餅乾就是大家耳熟能詳的cookie了
cookie是伺服器儲存在瀏覽器的一小段訊息,它用來記住一些暫時性資訊並且能讓使用者跨頁面使用,每一次使用者透過瀏覽器向伺服器提出要求時,都會雙手奉上伺服器在稍早存在客戶端(瀏覽器)的cookie
透過這些cookies,伺服器便能掌握使用者的狀態,直到cookie失效那天
一個cookie其實就只是一個鍵值對,包含了cookie的名稱和cookie的值
在django中操作cookie就類似於使用字典那樣

12.2.1 設置cookie

def set_c(request):
    response = HttpResponse('Set your lucky_number as 8')
    response.set_cookie('lucky_number',8)
    return response

set_cookie第一個參數指定cookie的鍵,第二個參數指定cookie的值

12.2 讀取cookie

def get_c(request):
    if 'lucky_number' in request.COOKIES:
        return HttpResponse('Your lucky_number is {0}'.format(request.COOKIES['lucky_number']))
    else:
        return HttpResponse('No cookies.') 

12.2.3 餅乾的問題

cookies是儲存在瀏覽器端的,而用戶可以關閉cookies的功能,這會導致許多行為無效化
Http協定是明文協定,cookies在傳輸過程中容易被攔截、竄改、偽造等,並不安全
為了解決這些問題,引進了Session的機制

12.3 Session

session之所以能夠解決這些問題是因為它是把資訊儲存在伺服端
那為何不直接將資料存在資料庫中?因為這只是暫時性的資訊,實在是沒有必要
不過django的session還是有用到資料庫的,所以在使用時要安裝app,也要同步資料庫
session跨頁面生存的方式:

  1. session會透過cookie儲存一段用以辨識的ID,cookie可以跨頁面生存,session自然也可以囉,透過這個ID,指令搞便可以去伺服端將我們需要的資料取出來,這個方式跟純粹使用cookie來儲存資訊的方式不同,因為現在這個cookie只儲存session的ID(而且是加密過的),我們的資料不會直接暴露給瀏覽器端
  2. 我們一樣可以透過url查詢字符的方式,將session ID附上,這對於cookie功能被瀏覽器端關閉時特別有用
    我們先使用預設的第一種

12.3.1 安裝Session App

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions', # <-確認有安裝
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'restaurants',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware', # <-確認有安裝
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

如果第一次使用session記得要同步一下資料庫才能啟用session

python manage.py migrate

12.3.2 使用Session

def use_session(request):
    request.session['lucky_number'] = 8 # 設置lucky_number
    if 'lucky_number' in request.session:
        lucky_number = request.session['lucky_number'] # 讀取lucky_number
        response = HttpResponse('Your lucky_number is ' + str(lucky_number))
    return response

使用session時的規則:
1.使用字串作為session的鍵值
2.不要任意以底線作為session鍵值字串的開頭
3.不要對session及其屬性賦值
如果要刪除session

del request.session['lucky_number']

即可

12.3.3 Session資料庫

打開shell

python manage.py shell
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.all()[0]
>>> s.expire_date
datetime.datetime(2019, 7, 31, 10, 20, 57, 569595, tzinfo=<UTC>)

expire_date是該session的有效期限,當超過這個時間時,session即失效

>>> s.session_data
'ODdhMzQxMWY0MzU0Nzg5YjgwYmRkZTZlZjJmYTBmZjBiMmQ4MzJkNzp7Il9hdXRoX3VzZXJfaWQiOiIxIiwiX2F1dGhfdXNlcl9iYWNrZW5kIjoiZGphbmdvLmNvbnRyaWIuYXV0aC5iYWNrZW5kcy5Nb2RlbEJhY2tlbmQiLCJfYXV0aF91c2VyX2hhc2giOiJmMTQwNzM4OThkNzRlZjVjOWE1NzFiZmZhZDJhYjI1ZjYxNDgzNGQyIn0='
>>> s.get_decoded()
{'_auth_user_id': '1', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': 'f14073898d74ef5c9a571bffad2ab25f614834d2'}

session_data是經過編碼的,必須利用get_decoded()方法來取得編碼後的資料,而這會是一個字典

12.3.4 Session cookie

session ID 是透過cookie來保存的,這個cookie就叫session cookie
我們可以透過sessionid這個cookie名稱來取得cookie,而他的值就是編碼後的session ID
撰寫視圖函式

from django.contrib.sessions.models import Session

def session_test(request):
    sid = request.COOKIES['sessionid']
    s = Session.objects.get(pk=sid)
    s_info = 'Session ID:' + sid + '<br>Expire_date:' + str(s.expire_date) + 
             '<br>Data:' + str(s.get_decoded())
    return HttpResponse(s_info)

利用request.COOKIES來取得sessionid這個cookie的值,也就是session ID,接著我們利用模型的get方法去找到在資料庫中符合該ID的sessions(Session資料表的主鍵pk即為session id),最後我們把expire_date和session的內容都印出來
在頁面上按右鍵->檢查,就可以查看cookie及session資訊
https://ithelp.ithome.com.tw/upload/images/20190721/20118889JqK3omSRUj.png
另一個方法

def session_test(request):
    sid = request.COOKIES['sessionid']
    sid2 = request.session.session_key
    s = Session.objects.get(pk=sid)
    s_info = 'Session ID:' + sid + '<br>SessionID2:' + sid2 + '<br>Expire_date:' + str(s.expire_date) + '<br>Data:' + str(s.get_decoded())
    return HttpResponse(s_info)

https://ithelp.ithome.com.tw/upload/images/20190721/2011888997zmMJLlt1.png
結果兩個方式拿到的session ID也是一樣的
這種預設以cookie記錄session ID來達成跨頁面的方式雖然方便,但讀者們還是要確認使用者端的cookie功能是否被開啟,透過以下步驟來測試:
1.利用HttpRequest.session.set_test_cookie()設置測試cookie
2.利用HttpRequest.session.test_cookie_worked()來檢查cookie是否被允許使用
3.利用HttpRequest.session.delete_test_cookie()來刪除測試cookie
如果cookie未被開啟,那我們只得利用URL查詢的方式,去傳遞session ID,不過這會相當麻煩

12.3.5 Session的持續性

可以在settings.py中設定參數

參數 意義 預設值
SESSION_EXPIRE_AT_BROWSER_CLOSE 決定session是否在瀏覽器關閉時結束 False
SESSION_COOKIE_AGE session(cookie)的有效時間 1209,600秒,兩週

12.3.6 用Session儲存模型物件

views.py

...
def list_restaurants(request):
    restaurants = Restaurant.objects.all()
    request.session['restaurants'] = restaurants  # 試著利用session保存模型物件
    return render_to_response('restaurants_list.html', locals())
...

結果出現錯誤
https://ithelp.ithome.com.tw/upload/images/20190721/20118889IHViaimb4X.png
在settings.py中加入

SESSION_SERIALIZER = 'django.contrib.sessions.serializers' + '.PickleSerializer'

就能解決了
pickle是python儲存物件的一種特殊機制,可以將物件訊息化變成一連串的編碼,方便儲存或紀錄,而從這些編碼要回復為物件時也是需要經由pickle才能回復,這個特殊的機制有個名詞:持久化,這裡我們要保存的是模型物件,無法用json格式轉化儲存,所以改為PickleSerializer

上一篇:Django學習紀錄 11.表單的驗證與模型化

下一篇:Django學習紀錄 13.用戶的登入與登出


尚未有邦友留言

立即登入留言