Django框架之會(huì)話技術(shù)之Cookie與Session
會(huì)話(Session)跟蹤是Web程序中常用的技術(shù),用來跟蹤用戶的整個(gè)會(huì)話。常用的會(huì)話跟蹤技術(shù)是Cookie與Session。Cookie通過在客戶端記錄信息確定用戶身份,Session通過在服務(wù)器端記錄信息確定用戶身份。
會(huì)話跟蹤技術(shù)
什么是會(huì)話跟蹤技術(shù)
我們可以把會(huì)話理解為客戶端與服務(wù)器之間的一次會(huì)晤,在一次會(huì)晤中可能會(huì)包含多次請(qǐng)求和響應(yīng)。
例如,我們給10086打電話,我就是客戶端,而10086服務(wù)人員就是服務(wù)端。從雙方接通電話那一刻起,會(huì)話就開始了,到某一方掛斷電話標(biāo)識(shí)會(huì)話結(jié)束。在通話過程中,我們會(huì)向10086發(fā)出多個(gè)請(qǐng)求,那么多個(gè)請(qǐng)求都在一個(gè)會(huì)話中。
在Web中,客戶使用瀏覽器向某一服務(wù)器發(fā)出第一個(gè)請(qǐng)求開始,會(huì)話就開始了,直到客戶關(guān)閉了瀏覽器會(huì)話結(jié)束。
在一個(gè)會(huì)話的多個(gè)請(qǐng)求中共享數(shù)據(jù),這就是會(huì)話跟蹤技術(shù)。
例如,在一個(gè)會(huì)話中的請(qǐng)求如下:請(qǐng)求銀行主頁。
1)請(qǐng)求登錄(請(qǐng)求參數(shù)是用戶名和密碼);
2)請(qǐng)求轉(zhuǎn)賬(請(qǐng)求參數(shù)與轉(zhuǎn)賬相關(guān)的數(shù)據(jù));
3)請(qǐng)求信用卡還款(請(qǐng)求參數(shù)與還款相關(guān)的數(shù)據(jù))。
在上面會(huì)話中,當(dāng)前用戶信息必須在這個(gè)會(huì)話中共享的,因?yàn)榈卿浀氖菑埲敲丛谵D(zhuǎn)賬和還款時(shí)一定是張三的轉(zhuǎn)賬和還款,這就說明我們必須在一個(gè)會(huì)話過程中共享數(shù)據(jù)的能力。
會(huì)話路徑技術(shù)使用Cookie或session完成
在Web應(yīng)用中,HTTP協(xié)議是無狀態(tài)的,即每次請(qǐng)求都是獨(dú)立的,無法記錄前一次請(qǐng)求的狀態(tài),每次請(qǐng)求都是一次新的請(qǐng)求。
無狀態(tài)原因:瀏覽器與服務(wù)器是使用Socket套接字進(jìn)行通信的,服務(wù)器將請(qǐng)求結(jié)果返回給瀏覽器之后,會(huì)關(guān)閉當(dāng)前的Socket連接,而且服務(wù)器也會(huì)在處理頁面完畢之后銷毀頁面對(duì)象。
但是,有時(shí)候我們需要知道上次請(qǐng)求狀態(tài),比如用戶是否登錄過,瀏覽過哪些商品等。可以使用會(huì)話跟蹤,可以使用Cookie、Session、token(自定義的session)。
Cookie
什么叫做Cookie
Cookie,翻譯成中文小甜點(diǎn),小餅干的意思。在HTTP中它表示服務(wù)器送給客戶端瀏覽器的小甜點(diǎn)。
Cookie是客戶端會(huì)話技術(shù),數(shù)據(jù)都存儲(chǔ)在客戶端,以key-value進(jìn)行存儲(chǔ)。支持過期時(shí)間max_age,默認(rèn)請(qǐng)求會(huì)攜帶本網(wǎng)站的所有cookie,cookie不能跨域名,不能跨瀏覽器,cookie默認(rèn)不支持中文,base64。
Cookie是由服務(wù)器創(chuàng)建,通過服務(wù)端的響應(yīng)發(fā)送給客戶端瀏覽器的一個(gè)鍵值對(duì),然后瀏覽器會(huì)把Cookie保存起來,并標(biāo)注出Cookie的來源(哪個(gè)服務(wù)器的Cookie),當(dāng)客戶端下一次再訪問服務(wù)器時(shí),默認(rèn)請(qǐng)求會(huì)攜帶本這個(gè)服務(wù)器的所有Cookie,這樣服務(wù)器就可以識(shí)別客戶端了。
設(shè)置cookie應(yīng)該是服務(wù)器response,獲取cookie應(yīng)該在瀏覽器request,刪除cookie應(yīng)該在服務(wù)器response。
Cookie規(guī)范
- Cookie大小上限為4KB;
- 一個(gè)服務(wù)器最多在客戶端瀏覽器上保存20個(gè)Cookie;
- 一個(gè)瀏覽器最多保存300個(gè)Cookie;
上面的數(shù)據(jù)只是HTTP的Cookie規(guī)范,但是,在瀏覽器百家爭鳴的當(dāng)下,一些瀏覽器為了爭奪一份份額,展現(xiàn)自己的優(yōu)勢,可能對(duì)Cookie的規(guī)范擴(kuò)展,例如每個(gè)Cookie的大小為8KB,最多可保存500個(gè)Cookie等!但也不會(huì)出現(xiàn)把你硬盤占滿的可能!
注意,不同瀏覽器之間是不共享Cookie的。也就是說在你使用IE訪問服務(wù)器時(shí),服務(wù)器會(huì)把Cookie發(fā)給IE,然后由IE保存起來,當(dāng)你在使用FireFox訪問服務(wù)器時(shí),不可能把IE保存的Cookie發(fā)送給服務(wù)器
Cookie與HTTP頭
Cookie是通過HTTP請(qǐng)求頭和響應(yīng)頭在客戶端和服務(wù)器端傳遞的:
Set-Cookie:響應(yīng)頭,服務(wù)器端發(fā)送給客戶端;
Cookie:請(qǐng)求頭,客戶端發(fā)送給服務(wù)器端;格式:Cookie: a=A; b=B; c=C。即多個(gè)Cookie用分號(hào)隔開;
一個(gè)Cookie對(duì)象一個(gè)Set-Cookie:Set-Cookie: a=A Set-Cookie: b=B Set-Cookie: c=C;
Cookie的覆蓋
如果服務(wù)器端發(fā)送重復(fù)的Cookie那么會(huì)覆蓋原有的Cookie,例如客戶端的第一個(gè)請(qǐng)求服務(wù)器端發(fā)送的Cookie是:Set-Cookie: a=A;第二請(qǐng)求服務(wù)器端發(fā)送的是:Set-Cookie: a=AA,那么客戶端只留下一個(gè)Cookie,即:a=AA。
請(qǐng)求流程
第一次請(qǐng)求:
① 瀏覽器第一次請(qǐng)求服務(wù)器的時(shí)候,不會(huì)攜帶任何cookie信息,,請(qǐng)求頭中沒有任何cookie信息;
② 當(dāng)服務(wù)器接受到這個(gè)請(qǐng)求之后,會(huì)做一些驗(yàn)證,例如進(jìn)行用戶名和密碼的驗(yàn)證,驗(yàn)證沒有問題則可以設(shè)置cookie信息。
③ 服務(wù)器會(huì)為響應(yīng)設(shè)置cookie信息,響應(yīng)頭中有set_cookie信息。
③ 我們的瀏覽器接收到這個(gè)響應(yīng)之后,發(fā)現(xiàn)響應(yīng)中有cookie信息,瀏覽器會(huì)將cookie信息保存起來。
后續(xù)請(qǐng)求:① 第二次或之后的請(qǐng)求都會(huì)攜帶cookie信息,請(qǐng)求頭中有cookie信息;
② 服務(wù)器接受到請(qǐng)求之后,會(huì)發(fā)現(xiàn)請(qǐng)求中攜帶cookie信息,這樣就認(rèn)識(shí)是誰的請(qǐng)求了。
代碼實(shí)現(xiàn)
login.html文件:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>登錄界面</title>
- <style>
- #loginbox{
- width: 350px;
- height: 200px;
- border: 1px solid red;
- margin: 90px auto;
- text-align: center;
- }
- </style>
- </head>
- <body>
- <div id="loginbox">
- <h2>登錄界面</h2>
- <form action="" method="post">
- {% csrf_token %}
- 帳 號(hào):<input type="text" name="username">
- <br/>
- 密 碼:<input type="password" name="password">
- <br/>
- <br/>
- <input type="submit" value="登錄">
- <input type="button" value="注冊(cè)">
- </form>
- </div>
- </body>
home.html文件:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>首頁</title>
- </head>
- <body>
- 歡迎
- {% if username %}
- {{ username }}
- {% else %}
- 游客
- {% endif %}
- 訪問~~
- <br>
- <a href="{% url 'user:logout' %}">退出</a>
- </body>
- </html>
views.py:
- def login(request):
- if request.method == 'GET':
- return render(request, 'login.html')
- elif request.method == 'POST':
- username = request.POST.get('username')
- password = request.POST.get('password')
- if username and password: # 此處為了方便測試,沒有去數(shù)據(jù)庫進(jìn)行校驗(yàn),實(shí)際的需要去數(shù)據(jù)庫校驗(yàn)用戶名和密碼之類的
- '''
- 響應(yīng)體:
- return HttpResponse()
- return render()
- return redirect()
- 都可以
- '''
- response = redirect(reverse('user:home'))
- # 設(shè)置cookie信息
- response.set_cookie('username', username)
- is_login = True
- response.set_cookie('is_login', is_login)
- return response
- return render(request, 'login.html')
- def home(request):
- # 獲取cookie信息
- is_login = request.COOKIES.get('is_login')
- username = request.COOKIES.get('username')
- if is_login:
- return render(request, 'home.html', {'username': username})
- return redirect(reverse('user:login'))
- def logout(request):
- response = redirect(reverse('user:login'))
- # 刪除cookie信息
- response.delete_cookie('is_login')
- response.delete_cookie('username') # 注銷,最好都刪除,避免注冊(cè)之后下次還會(huì)攜帶未刪除的信息
- return response
我們可以看到第一次打開登錄頁面的時(shí)候,不攜帶Cookie信息(csrf暫忽略):
我們?cè)俚卿?,即提交表單之后,服?wù)器設(shè)置了Cookie(可參考上面設(shè)置Cookie信息代碼):
退出,即刪除Cookie信息,這里只刪除is_login的信息
上面是以明文的方式顯示,我們可以進(jìn)行加鹽設(shè)置,以加密形式顯示:
- def login(request):
- if request.method == 'GET':
- return render(request, 'login.html')
- elif request.method == 'POST':
- username = request.POST.get('username')
- response = redirect(reverse('user:home'))
- #response.set_cookie('username', username)
- #set_signed_cookie(key,value,salt='加密鹽',...)
- response.set_signed_cookie('username', username, 'qmpython')
- return response
- def home(request):
- username = request.COOKIES.get('username')
- # 解密
- #username = request.get_signed_cookie('username', 'qmpython')
- response = HttpResponse(f'{username}登錄成功')
- return response
Cookie:具體一個(gè)瀏覽器針對(duì)一個(gè)服務(wù)器存儲(chǔ)key-value。
注意,不同瀏覽器之間是不共享Cookie的。也就是說在你使用IE訪問服務(wù)器時(shí),服務(wù)器會(huì)把Cookie發(fā)給IE,然后由IE保存起來,當(dāng)你在使用Chrome訪問服務(wù)器時(shí),不可能把IE保存的Cookie發(fā)送給服務(wù)器。
同一個(gè)瀏覽器訪問A網(wǎng)站,又去訪問B網(wǎng)站,不可能將A的cookie信息攜帶發(fā)送給B服務(wù)器。
默認(rèn)情況下,Cookie只在瀏覽器的內(nèi)存中存活,也就是說,當(dāng)你關(guān)閉瀏覽器后,Cookie就會(huì)消失!
問題一:如果只是關(guān)閉網(wǎng)站(關(guān)閉標(biāo)簽頁),沒有退出瀏覽器呢?
A:并不會(huì),如果在不關(guān)閉瀏覽器,只關(guān)閉頁面時(shí),清除cookie??梢允褂胘s事件處理:
- <script>
- window.onunload = function(){
- //窗口關(guān)閉
- //在這發(fā)一個(gè)ajax請(qǐng)求,后臺(tái)清除cookie
- }
- </script>
set_cookie參數(shù):
- class HttpResponseBase:
- def set_cookie(self,
- key, # 鍵
- value='', # 值
- max_age=None, # cookie有效時(shí)長,單位為秒,默認(rèn)為None表示關(guān)閉瀏覽器失效,指定 為有效數(shù)值100表示100秒后自動(dòng)失效
- expires=None, # 支持一個(gè)datetime或timedelta,可以指定一個(gè)具體的日期, expires=timedelta(days=10)表示十天后過期。
- # max-age和exepires兩個(gè)指定一個(gè)。
- path='/', # Cookie生效的路徑,瀏覽器只會(huì)把cookie回傳給帶有該路徑的頁面,這樣可以避免將;cookie傳給站點(diǎn)中的其他的應(yīng)用。;/ 表示根路徑,特殊的:根路徑的cookie可以被任何url的頁面訪問
- domain=None, ''' Cookie生效的域名;你可用這個(gè)參數(shù)來構(gòu)造一個(gè)跨站cookie。
- 如, domain=".example.com"
- 所構(gòu)造的cookie對(duì)下面這些站點(diǎn)都是可讀的:
- www.example.com 、 www2.example.com
- 和an.other.sub.domain.example.com 。
- 如果該參數(shù)設(shè)置為 None ,cookie只能由設(shè)置它的站點(diǎn)讀取。
- '''
- secure=False, # 如果設(shè)置為True,瀏覽器將通過HTTPS來回傳cookie
- httponly=False, # 只能http協(xié)議傳輸,無法被js獲取(不是絕對(duì)的,底層抓包可以獲取到也可以被覆蓋)
- samesite=None
- ):pass
練習(xí):使用cookie實(shí)現(xiàn)上次登錄時(shí)間
- def home(request):
- # 獲取cookie信息
- username = request.COOKIES.get('username')
- last_login_time = request.COOKIES.get('last_login_time', '')
- response = render(request, 'home.html', {'username': username, 'last_login_time': last_login_time})
- from datetime import datetime
- now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- response.set_cookie('last_login_time', now)
- return response
Session
前面介紹了Cookie,為什么還需要Session呢?其實(shí)很多情況下,只使用Cookie便能完成大部分需求。但是,只使用Cookie往往是不夠的,考慮用戶登錄信息或一些重要的敏感信息,用Cookie存儲(chǔ)的話會(huì)帶來一些問題,最明顯的是由于Cookie會(huì)把信息保存到本地,因此信息的安全性可能受到威脅。Session的出現(xiàn)很好地解決的這個(gè)問題,Session與Cookie類似。
Session是服務(wù)器端技術(shù),利用這個(gè)技術(shù),服務(wù)器在運(yùn)行時(shí)可以為每一個(gè)用戶的瀏覽器創(chuàng)建一個(gè)其獨(dú)享的session對(duì)象,由于session為用戶瀏覽器獨(dú)享,所以用戶在訪問服務(wù)器的web資源時(shí),可以把各自的數(shù)據(jù)放在各自的session中,當(dāng)用戶再去訪問該服務(wù)器中的其他web資源時(shí),其他WEB資源再從用戶各自session中取出數(shù)據(jù)為用戶服務(wù)。
Session是服務(wù)端會(huì)話技術(shù),依賴于Cookie,如果在瀏覽器中**禁用cookie**的話,那么session就失效了,因?yàn)樗枰獮g覽器的cookie值去session里做對(duì)比。
1. 啟用Session
Django默認(rèn)啟用Session,可以在設(shè)置文件settings.py:
- MIDDLEWARE = [
- 'django.contrib.sessions.middleware.SessionMiddleware',
- ]
如果禁用session,則注釋即可。
2. 存儲(chǔ)方式
在settings.py文件中,可以設(shè)置session數(shù)據(jù)的存儲(chǔ)方式,可以保存在數(shù)據(jù)庫、本地緩存等。
2.1 數(shù)據(jù)庫
默認(rèn)是存儲(chǔ)在數(shù)據(jù)庫中的。
- SESSION_ENGINE='django.contrib.sessions.backends.db'
如果存儲(chǔ)在數(shù)據(jù)庫中,則需要安裝Session應(yīng)用,默認(rèn)已設(shè)置:
- INSTALLED_APPS = [
- 'django.contrib.sessions',
- ]
在進(jìn)行數(shù)據(jù)庫遷移的時(shí)候,Django自動(dòng)幫我們生成了django_session表,有3個(gè)字段,分別為session_key,session_data,expris_date,Django中session的默認(rèn)過期時(shí)間是14天。
遷移數(shù)據(jù)庫后,就會(huì)生成django_session表:
3. 請(qǐng)求流程
第一次請(qǐng)求:
① 我們第一次請(qǐng)求的時(shí)候可能會(huì)攜帶一些信息(例如用戶名/密碼),cookie中沒有任何信息。
② 當(dāng)服務(wù)器接受到這個(gè)請(qǐng)求之后,會(huì)做一些驗(yàn)證,例如進(jìn)行用戶名和密碼的驗(yàn)證,驗(yàn)證沒有問題則可以設(shè)置session信息。
③ 在設(shè)置session信息的同時(shí)(session信息保存在服務(wù)器端),服務(wù)器會(huì)在響應(yīng)頭中設(shè)置一個(gè)sessionid的cookie信息。
④ 客戶端(瀏覽器)在接收到響應(yīng)之后,會(huì)將cookie信息保存起來(保存sessionid的信息)。
后續(xù)請(qǐng)求:
① 第二次或之后的請(qǐng)求都會(huì)攜帶sessionid的cookie信息
② 當(dāng)服務(wù)器接收到這個(gè)請(qǐng)求之后,獲取到sessionid信息,然后進(jìn)行驗(yàn)證,驗(yàn)證成功,則可以獲取session信息(session信息保存在服務(wù)器端)
4. 代碼實(shí)現(xiàn)
- def login(request):
- if request.method == 'GET':
- return render(request, 'login.html')
- elif request.method == 'POST':
- '''
- 響應(yīng)體:
- return HttpResponse()
- return render()
- return redirect()
- 都可以
- '''
- username = request.POST.get('username')
- password = request.POST.get('password')
- if username and password: # 此處為了方便測試,沒有去數(shù)據(jù)庫進(jìn)行校驗(yàn),實(shí)際的需要去數(shù)據(jù)庫校驗(yàn)用戶名和密碼之類的
- # 設(shè)置session信息
- request.session['is_login'] = True
- request.session['username'] = username
- '''
- 以上設(shè)置,實(shí)際執(zhí)行以下操作:
- 1、生成隨機(jī)字符串,例如:123abc!@#
- 2、response.set_cookie("sessionid", 123abc!@#)
- 3、在django_session表中創(chuàng)建一條記錄:
- session_key session_data
- 123abc!@# {"is_login":True, "username": "admin"}
- '''
- return redirect(reverse('user:home'))
- def home(request):
- is_login = request.session['is_login']
- username = request.session['username']
- '''
- 以上執(zhí)行以下操作:
- 1) request.COOKIES.get('sessionid'),獲得session_key
- 2) 通過上面得到的session_key,在django_session表中獲取對(duì)應(yīng)的session_data
- 3) 從session_data獲取is_login值。
- '''
- if is_login:
- return render(request, 'home.html', {'username': username})
- return redirect(reverse('user:login'))
- def logout(request):
- response = redirect(reverse('user:login'))
- # 刪除session數(shù)據(jù),即刪除了session_data字段值中對(duì)應(yīng)鍵和值,但是cookie信息還存在,請(qǐng)求還會(huì)攜帶cookie信息,也就成了臟數(shù)據(jù),通過cookie再找session,已經(jīng)找不到了。
- #del request.session['is_login']
- # 清除所有session,禁用?。?!
- request.session.clear()
- # 清除當(dāng)前的會(huì)話數(shù)據(jù)并刪除會(huì)話的cookie,session,cookie一起干掉,刪除session表中session的整條數(shù)據(jù)。
- request.session.flush()
- '''
- 執(zhí)行以下操作:
- 1) request.COOKIE.get('sessionid'),獲得session_key
- 2) django_session表中刪除session_key對(duì)應(yīng)的記錄
- 3) response.delete_cookie('sessionid')
- '''
- return response
打開登錄頁面,沒有攜帶任何cookie信息(csrf那個(gè)先不管)
發(fā)起登錄請(qǐng)求,設(shè)置session信息,其中會(huì)生成隨機(jī)字符串比如mjx9gsq47ofwmcg2ubxtg28uk6idbq2a,然后以這個(gè)sessionid為key,這個(gè)字符串為value,設(shè)置cookie信息。
并且在django_session插入記錄,其中session_key字段的值就是sessionid的值,session_data字段的值就是上面設(shè)置的session鍵值對(duì)。
然后跳轉(zhuǎn)到home頁,就會(huì)攜帶cookie信息,獲取sessionid的值,在數(shù)據(jù)庫django_session表中查找是否有相關(guān)記錄。
如果退出,則刪除session信息
從DJANGO角度來看:
第一次請(qǐng)求:
① 第一次請(qǐng)求,在請(qǐng)求頭中沒有攜帶任何cookie信息。
② 我們?cè)谠O(shè)置session的時(shí)候,session會(huì)做2件事。
第一件:將數(shù)據(jù)保存在數(shù)據(jù)庫中。
第二件:設(shè)置一個(gè)cookie信息,這個(gè)cookie信息是以sessionid為key,value為xxxx,cookie肯定會(huì)以響應(yīng)的形式在響應(yīng)頭中出現(xiàn)。
第二次以及之后的請(qǐng)求:
③ 都會(huì)攜帶cookie信息,特別是sessionid。
如果換了瀏覽器,還能獲取到session信息嗎?
解:不可以,因?yàn)閟ession依賴于cookie,換了瀏覽器,都沒有cookie信息了。