文章目錄
- Cookie的介紹
- Cookie的由來
- 什么是Cookie
- Cookie原理
- Cookie覆蓋
- 瀏覽器查看Cookie
- 在Django中操作Cookie
- 設置Cookie
- 查詢瀏覽器攜帶的Cookie
- 刪除Cookie
- Cookie校驗登錄
- session
- Session的由來
- Session設置
- 查看、更新Session值
- 刪除Session值
- Seesion的其他方法
- Session的其他配置
- Seesion校驗登錄
Cookie的介紹
Cookie的由來
首先我們都應該明白HTTP協議是無連接的。
無狀態的意思是每次請求都是獨立的,它的執行情況和結果與前面的請求和之后的請求都無直接關系,它不會受前面的請求響應情況直接影響,也不會直接英系那個后面的請求響應情況。
所以對服務端來說,每一次請求都是全新的。
狀態也就是說在于服務端進行連接的過程中產生的數據,基于HTTP無連接的特性,在下一次與服務端進行連接之后又是一次全新的狀態,之前的狀態不會保存絲毫。
如果說我們要保存狀態的話,便于下一次或更多次于服務端進行連接都擁有之前的狀態,那么Cookie就誕生在這種需求下。
什么是Cookie
其實Cookie是key-value結構,類似于一個python中的字典。隨著服務器端的響應發送給客戶端瀏覽器。然后客戶端瀏覽器會把Cookie保存起來,當下一次再訪問服務器時把Cookie再發送給服務器。Cookie是由服務器創建,然后通過響應發送給客戶端的一個鍵值對。客戶端會保存Cookie,并會標注出Cookie的來源(哪個服務器的Cookie)。當客戶端向服務器發出請求時會把所有這個服務器Cookie包含在請求中發送給服務器,這樣服務器就可以識別客戶端了!
Cookie原理
Cookie的工作原理:在服務器產生后Cookie發送給瀏覽器,然后瀏覽器將其保存在本地;下次瀏覽器訪問服務器時攜帶這個Cookie,那么服務端就能根據這個Cookie內容判斷這個瀏覽器是誰了。
注意:不同瀏覽器之間是不共享Cookie的。也就是說在你使用IE訪問服務器時,服務器會把Cookie發給IE,然后由IE保存起來,當你使用Chrome訪問服務器時,不可能把IE保存的Cookie發送給服務器。
Cookie覆蓋
服務端重復發送Cookie是會覆蓋瀏覽器原有Cookie的,例如:瀏覽器第一次請求服務端返回的Cookie是:name:jack;瀏覽器第二次請求之后服務端又發送了Cookie給瀏覽器:name:tom;那么瀏覽器只會留下新的Cookie,也就是:name:tom;
注意:如果key不一樣的話則不會覆蓋,因為一個Cookie可以由好很多key-value組成
瀏覽器查看Cookie
window可以通過F12打開開發者選項,選擇Application選項下面的Cookies
在已經開啟的頁面使用開發者選項可以看不到內容,我們再刷新一下頁面就可以了。
在Django中操作Cookie
Django就是我們的服務端,瀏覽器向它發送請求我們可以返回一個Cookie。
設置Cookie
我們可以給基于HttpResponse類返回數據到瀏覽器的方法設置Cookie,如:render、redirect、JsonResponse等
request = HttpResponse('...')data = render('...')response.set_cookie(key,value)data.set_cookie(key,value)
set_cookie可以設置的參數:
- key:鍵
- value:值
- max_age=None,超時時間,cookie需要延續的時間(以秒為單位)如果參數是\None’',這個–cookie會延續到瀏覽器關閉為止
- expires=None,超時時間(IE requires expires,so set it if hasn’t been already.)
- path=‘/’,Cookie生效的路徑,/表示根路徑,特殊的:根路徑的cookie可以被任何url的頁面訪問,瀏覽器只會把cookie回傳給帶有該路徑的頁面,這樣可以避免將cookie傳給站點中的其他的應用。
- domain針對哪個域名有效。默認是針對主域名下都有效,如果只要針對某一個子域名才有效,那么可以設置這個屬性
- secure=False,瀏覽器將通過HTTPS來回傳cookie
- httponly=False,只能http協議傳輸,無法被JavaScript獲取(不是絕對,底層抓包可以獲取到可以被覆蓋)
set_signed_cookie:可以對Cookie的value值進行加鹽
def index(request):response = HttpResponse('Hello World!')response.set_cookie('name','jack')response.set_signed_cookie('sex','man',salt='加鹽')return response
signed_cookie 只是加了簽名的cookie,而不是被加密的cookie,在客戶端還是可以看到沒有加密的value的
注解作用:
單純的記錄uid或者用戶名在cookie中很容易被篡改(也是不建議將用戶敏感信息記錄在cookie中的原因),萬一攻擊者把uid=1換成uid=2豈不是可以訪問uid=2用戶的資源了嗎?而如果換成uid=2r5khk:5Qi0PuFkFQxAFkDnM-UsSljHHxM那么服務端不僅檢驗uid,還檢驗uid=2后面的簽名字段,即是調用HttpRequest.get_signed_cookie(key=key,sait=sait),這樣即使用戶把cookie中的value換成uid=2,但是沒有簽名,服務端照樣拒絕訪問資源。
查詢瀏覽器攜帶的Cookie
當我們響應給客戶端Cookie保存以后,下次客戶端訪問我們的服務端就會攜帶這個Cookie來訪問。
我們可以在視圖接收到客戶端的請求里面提取出Cookie的鍵值對,針對正常設置的Cookie和加鹽的Cookie取值方式有所不同
def index(request):print(request.COOKIES['name']) # 方式一:不推薦使用此方式,如果key不存在則會拋出異常:KeyErrorprint(request.COOKIES.get('name')) # 方式二:獲取name鍵對應的value,如果key不存在則返回Noneprint(request.get_signed_cookie('sex',salt='加鹽')) # 獲取加鹽過的cookie鍵值,salt必須要對上不然報錯return HttpResponse('Hello World!')
刪除Cookie
刪除瀏覽器請求里攜帶的某個Cookie
def index(request):response = HttpResponse('Hello World!')response.delete_cookie('sex')return response
Cookie校驗登錄
通過登錄之后在瀏覽器添加Cookie,只有攜帶該Cookie才能訪問home頁面。
視圖層代碼
from app import models
def login(request,*arg,**kwargs):if request.method == 'POST':username = request.POST.get('username')password = request.POST.get('password')'''查詢數據庫中的用戶數據比對'''obj = models.UserInfo.objects.filter(username=username,password=password).first()if obj:response = HttpResponse('login success!')response.set_cookie('name', username, max_age=30)response.set_cookie('msg','True',max_age=30) # 定義過期時間# 它們分別都在30秒后過期,也就是瀏覽器只有30秒使用這個Cookie的時間,過了30秒以后再次訪問將不能攜帶該Cookiereturn responseelse:return HttpResponse('賬號或密碼錯誤!')return render(request,'login.html')def home(request):try:name = request.COOKIES['name']msg = request.COOKIES['msg']if name and msg == 'True': # 判斷瀏覽器攜帶的Cookie是否合格return render(request,'home.html')return redirect('/login/') # 不合格重定向到登錄頁面except: # 沒有獲取到指定Cookie的話,則說明Cookie過期了,重定向到登錄頁面return redirect('/login/')
進入home頁面是在請求里頭攜帶了服務端在瀏覽器登錄時響應的Cookie,再次將該Cookie發送給服務端才能得到home頁面的響應。
待我們30秒后再次刷新home頁面時,就會被重定向到登錄界面,因為Cookie過期了
如果需要有多個視圖函數需要使用時,這種的每次都需要寫重復的代碼,這樣代碼的重復性就太高了,所以我們可以做一個裝飾器用來存儲這些重復的代碼
'''檢驗用戶登錄的狀態的裝飾器'''
def login_auth(xxx):def inner(request, *args, **kwargs):'''獲取到用戶上一次想要訪問的urlpath_info ----只有路由地址get_full_path ----可以訪問到瀏覽器后攜帶的get參數'''if request.COOKIES.get('name'):return xxx(request,*args, **kwargs)else:return redirect('/login/') # 通過后綴的方式告知跳轉那一個return innerfrom app import models
def login(request):if request.method == 'POST':username = request.POST.get('username')password = request.POST.get('password')'''查詢數據庫中的用戶數據比對'''obj = models.UserInfo.objects.filter(username=username,password=password).first()if obj:response = HttpResponse('login success!')response.set_cookie('name', username, max_age=30)response.set_cookie('msg','True',max_age=30) # 定義過期時間# 它們分別都在30秒后過期,也就是瀏覽器只有30秒使用這個Cookie的時間,過了30秒以后再次訪問將不能攜帶該Cookiereturn responseelse:return HttpResponse('賬號或密碼錯誤!')return render(request,'login.html')@login_out
def func(request):return HttpResponse('from func')def home(request):try:# name = request.COOKIES['name']# msg = request.COOKIES['msg']name = request.COOKIES.get('name')msg = request.COOKIES.get('msg')if name and msg == 'True': # 判斷瀏覽器攜帶的Cookie是否合格return render(request, 'home.html')return redirect('/login/') # 不合格重定向到登錄頁面except: # 沒有獲取到指定Cookie的話,則說明Cookie過期了,重定向到登錄頁面return redirect('/login/')
session
Session的由來
Cookie雖然在一定程度上解決了“保持狀態”的需求,但是由于Cookie本身最大支持4096字節,以及Cookie本身保存在客戶端,可能被攔截或竊取,因此就需要有一種新的東西,它能支持更多的字節,并且他保存在服務器,有較高的安全性。這就是Session。
問題來了,基于HTTP協議的無狀態特征,服務器根本就不知道訪問者是“誰”。那么上述的Cookie就起到橋接的作用。
我們可以給每個客戶端的Cookie分配一個唯一的id,這樣用戶在訪問時,通過Cookie,服務器就知道來的人是“誰”。然后我們再根據不同的Cookie的id,在服務器上保存一段時間的私密資料,如“賬號密碼”等等。
總結而言:Cookie彌補了HTTP無狀態的不足,讓服務器知道來的人是“誰”;但是Cookie以文本的形式保存在本地,自身安全性較差;所以我們就通過Cookie識別不同的用戶,對應的在Session里保存私密的信息以及超過4096字節的文本。
另外,上述所說的Cookie和Session其實是共通性的東西,不限于語言和框架。
另外我們在第一次執行Django中的數據庫遷移命令時,會自動生成一些Django默認的表,其中就包含我們等會需要用到的django_session表。
簡略的Seesion請求過程
Session設置
Session的配置方法與Cookie類似,不過Session針對的是請求配置,然后在響應給瀏覽器
# 設置Sessionrequest.session['name'] = 'jack'
設置Seesion會觸發幾個步驟:
- 會生成一個隨機字符串(如果該字符串已經存在,則對該字符串對應的Session進行更新)
- 會把用戶設置的信息保存在django_session表中,數據也做了加密處理
- 把數據封裝到了request.session里去了
- Django后端把隨機字符串保存到了瀏覽器Cookie中
- 隨機字符串保存在瀏覽器中的key=sessionid
def set_session(request):request.session['name'] = 'jack'request.session['age'] = 18return HttpResponse('set_session')
瀏覽器打開頁面
此時我們檢查數據庫內是否新增了一個django_session表,而表的session_key
字段則是與頁面看到的一模一樣,而Session_data可以看成一個加密的字典,里面存有我們給這個session_key
標識設置的值:{'name':'jack','age':18}
當我們再次使用瀏覽器訪問這個服務端,攜帶的請求就是session_key
,也就是第一次訪問服務端時獲取的一個標識。
再次給這個Session設置值的話,就是根據瀏覽器發送過來的標識,如果不是第一次,則對session_data
字段進行更新
注意:想必上面也觀察到了,服務端響應的標識是根據sessionid作為key,生成的隨機字符串作為value保存在Cookie內,而如果關閉瀏覽器之后,這個標識則會失效,下一次再打開瀏覽器則又會生成一個新的隨機字符串,在數據庫內新增。
當設置多個session值的時候,session_key是不變的,變的是session_Data(session參數)
當設置多個session值的時候,django_Session表中只存在一條記錄(一臺電腦的一個瀏覽器)
上述做法的好處是:節省MySQL的空間
查看、更新Session值
根據瀏覽器攜帶的session_key標識,查看數據庫內是否存有,如果又將其session_data字典內的數據取出。
def get_Session(request):print(request.session.get('name')) # 方式一:如果字段不存在則報錯print(request.session['age']) # 方式二:如果字段不存在則返回Nonereturn HttpResponse('get_session')》》》》:jack、18
當我們給一個已經存在的數據內的Session_key設置Session_data的話,則是新增。
def get_Session(request):request.session['age'] = 20 # 這里就更具對應的key直接存入數據庫內print(request.session.get('name')) # jackprint(request.session.get('age')) # 20return HttpResponse('get_session') # 將請求處理過后響應給瀏覽器
我們可以對比上面圖片里的session_data就可以發現,已經發生了變動了。
查看Seesion會觸發幾個步驟:
- 瀏覽器先把sessionid回傳到Django后端
- Django后端獲取到sessionid,然后去數據表中根據session_key查詢
- 如果查到了,說明之前已經登錄過了
- 如果查不到,就返回None
- 查詢出來的數據默認是加密的,Django后端又把數據解密后封裝到request.session中
- 在取session值的時候,就從reqeust.session中取
刪除Session值
我們可以刪除django_session表內的session_data里的某一個session值
再次注意:我們把session_data看成一個加密的Python字典即可,里面存儲的就是session值。
def get_Session(request):print(request.session.get('name')) # 方式一:如果字段不存在則報錯print(request.session['age']) # 方式二:如果字段不存在則返回Nonereturn HttpResponse('get_session')》》》》:jack、18
也可以刪除瀏覽器發送的請求里的鞋帶的標簽對應的session_key的那一整行的記錄。
def del_Session(request):request.session.delete() # 清空session,它只剩服務端的數據,瀏覽器中的還保留著print(request.session.get('name')) # Noneprint(request.session.get('age')) # Nonereturn HttpResponse('del_session')
并且數據庫內對應的一行記錄也被刪除了
但是也存在一個問題,那就是瀏覽器的Cookie里面的Session_id沒有被清除,也就是說下次瀏覽器訪問服務端還是會攜帶一個標識。
那么我們如果即想要刪除保存在數據庫里面的瀏覽器標識,也要刪除瀏覽器Cookie里面的標識避免下一次再次攜帶這個標識發送請求,使用如下方法:
def del_Session(request):request.session.flush() # 清空保存在數據庫的Session標識與瀏覽器的Session標識return HttpResponse('del_session')
其實說保存了與瀏覽器的會話可能會更好一些。要想讓服務器記得瀏覽器,必須它們之間有一個會話標識。session會生成一個隨機字符串作為會話標識,第一次瀏覽器訪問服務端會得到這個標識,然后服務端會將這個標識保存在數據庫內。下次瀏覽器憑借這個標識就可以讓服務端想起它,并讓服務端幫它存儲一些數據。
Seesion的其他方法
# 所有 鍵、值、鍵值對request.session.keys()request.session.values()request.session.items()request.session.iterkeys()request.session.itervalues()request.session.iteritems()# 會話session的key(隨機字符串)request.session.session_key# 將所有Session失效日期小于當前日期的數據刪除request.session.clear_expired()# 檢查會話session的key在數據庫中是否存在request.session.exists("session_key")# 刪除當前會話的所有Session數據(只刪數據庫)request.session.delete()# 刪除當前的會話數據并刪除會話的Cookie(數據庫和cookie都刪)。request.session.flush() 這用于確保前面的會話數據不可以再次被用戶的瀏覽器訪問# 設置會話Session和Cookie的超時時間request.session.set_expiry(value)* 如果value是個整數,session會在些秒數后失效。* 如果value是個datatime或timedelta,session就會在這個時間后失效。* 如果value是0,用戶關閉瀏覽器session就會失效。* 如果value是None,session會依賴全局session失效策略。
Session的其他配置
在settings.py文件中可以配置下面內容
1. 數據庫Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默認)2. 緩存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的緩存別名(默認內存緩存,也可以是memcache),此處別名依賴緩存的設置3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 緩存文件路徑,如果為None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir() 4. 緩存+數據庫
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎其他公用設置項:
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串(默認)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑(默認)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默認)
SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie(默認)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸(默認)
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Session的cookie失效日期(2周)(默認)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過期(默認)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改之后才保存(默認)
Seesion校驗登錄
這里我就基于CBV來實現這一功能,并且使用到登錄認證裝飾器判斷用戶是否存在某個Session值。如果不存在則跳轉到登錄頁面
urls.py路由配置
urlpatterns = [path('login/', views.cbv_login.as_view()),path('home/', views.Home.as_view()),path('order/', views.Order.as_view()),]
views.py視圖
'''基于CBV和裝飾器來實現登錄功能'''
from django.views import View # 使用CBV基于類的視圖
from app import models # 導入模型層
from django.utils.decorators import method_decorator # 推薦使用作為語法糖def auth_login(xxx):def decorate(request,*args,**kwargs):if request.session.get('is_login'): # 判斷請求內容是否有這個session值return xxx(request,*args,**kwargs)else:return redirect('/login/')return decorate# 登錄類的視圖
class cbv_login(View):def get(self,request):return render(request,'login.html')def post(self,request):username = request.POST.get('username')password = request.POST.get('password')user_obj = models.UserInfo.objects.filter(username=username,password=password).first()if user_obj: # 當與數據庫中的數據匹對成功執行request.session['is_login'] = Truereturn HttpResponse('login success!')request.session.set_expiry(30) # 失效時間30秒else:return HttpResponse('用戶名或密碼錯誤!')@method_decorator(auth_login,name='get') # 表示裝飾器只應用于Home類下面的get方法
class Home(View):def get(self, request):return HttpResponse('from Home!')class Order(View):# @auth_login 這種方式:裝飾器需要接受定義形參接收self@method_decorator(auth_login) # 裝飾器不需要定義形參接收self參數,該方法默認傳遞。def get(self, request):return HttpResponse('from Order!')
第一次訪問order頁面:因為沒有檢測到登錄狀態被重定向到了登錄頁面,
然后輸入能登錄匹對上數據庫中的用戶名和密碼,顯示登錄成功
然后訪問Home頁面:在Cookie內檢測到了Session標識,拿到django_session表匹配找到了登錄標識,訪問成功。
由于設置了登錄標識30秒失效,刷新頁面后可以看到又重定向到登錄頁面了
總結:
Session是服務端保存于某個客戶端的會話狀態。比如:現在用的是Chrome訪問的客戶端,然后服務端與我們的Chrome瀏覽器保存了會話。那么此時再用Edge瀏覽器訪問的話,則又是一次全新的會話保存在服務端。
所以:服務端是與訪問它的客戶端建立會話保持。返回的是一個隨機字符串保存在客戶端的Cookie內,下次訪問服務端則會攜帶這個隨機字符串。