前言
會話跟蹤技術
在一個會話的多個請求中共享數據,這就是會話跟蹤技術。例如在一個會話中的請求如下: ? 請求銀行主頁;
- 請求登錄(請求參數是用戶名和密碼);
- 請求轉賬(請求參數與轉賬相關的數據);
- 請求信譽卡還款(請求參數與還款相關的數據)。
在這上會話中當前用戶信息必須在這個會話中共享的,因為登錄的是張三,那么在轉賬和還款時一定是相對張三的轉賬和還款!這就說明我們必須在一個會話過程中有共享數據的能力。
會話路徑技術使用Cookie或session完成?
我們知道HTTP協議是無狀態協議,也就是說每個請求都是獨立的!無法記錄前一次請求的狀態。但HTTP協議中可以使用Cookie來完成會話跟蹤!在Web開發中,使用session來完成會話跟蹤,session底層依賴Cookie技術。
Cookie
什么是cookie
其實Cookie是key-value結構,類似于一個python中的字典。隨著服務器端的響應發送給客戶端瀏覽器。然后客戶端瀏覽器會把Cookie保存起來,當下一次再訪問服務器時把Cookie再發送給服務器。 Cookie是由服務器創建,然后通過響應發送給客戶端的一個鍵值對。客戶端會保存Cookie,并會標注出Cookie的來源(哪個服務器的Cookie)。當客戶端向服務器發出請求時會把所有這個服務器Cookie包含在請求中發送給服務器,這樣服務器就可以識別客戶端了!
cookie的原理?
cookie的工作原理是:由服務器產生內容,瀏覽器收到請求后保存在本地;當瀏覽器再次訪問時,瀏覽器會自動帶上Cookie,這樣服務器就能通過Cookie的內容來判斷這個是“誰”了。
Cookie規范?
- Cookie大小上限為4KB;
- 一個服務器最多在客戶端瀏覽器上保存20個Cookie;
- 一個瀏覽器最多保存300個Cookie;
上面的數據只是HTTP的Cookie規范,但在瀏覽器大戰的今天,一些瀏覽器為了打敗對手,為了展現自己的能力起見,可能對Cookie規范“擴展”了一些,例如每個Cookie的大小為8KB,最多可保存500個Cookie等!但也不會出現把你硬盤占滿的可能!
注意,不同瀏覽器之間是不共享Cookie的。也就是說在你使用IE訪問服務器時,服務器會把Cookie發給IE,然后由IE保存起來,當你在使用FireFox訪問服務器時,不可能把IE保存的Cookie發送給服務器。
在瀏覽器中查看cookie?
瀏覽器中按F12,點network—cookies就能看到
?獲取Cookie
request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
參數:
- default: 默認值
- salt: 加密鹽
- max_age: 后臺控制過期時間
?設置Cookie
rep = HttpResponse(...)
rep = render(request, ...)rep.set_cookie(key,value)
rep.set_signed_cookie(key,value,salt='加密鹽')
參數:
- 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=None, Cookie生效的域名 你可用這個參數來構造一個跨站cookie。如, domain=”.example.com”所構造的cookie對下面這些站點都是可讀的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果該參數設置為 None ,cookie只能由設置它的站點讀取
- secure=False, 瀏覽器將通過HTTPS來回傳cookie
- httponly=False 只能http協議傳輸,無法被JavaScript獲取(不是絕對,底層抓包可以獲取到也可以被覆蓋)
刪除Cookie?
def logout(request):rep = redirect("/login/")rep.delete_cookie("user") # 刪除用戶瀏覽器上之前設置的usercookie值return rep
Cookie版登錄校驗
views
from django.shortcuts import render,HttpResponse,redirect# Create your views here.def login(request):if request.method=='POST':username=request.POST.get('username')password=request.POST.get('password')if username=="aaa" and password=="111":tag_url=request.GET.get('tag_url')if tag_url:obj=redirect(tag_url)else:#登陸成功之后,保存用戶登陸狀態obj=redirect('/home/')#讓瀏覽器記錄cookieobj.set_cookie('sign','1314520')#登陸成功之后,跳轉到登陸成功的頁面return objreturn render(request,'login.html',locals())def auth_check(func):def inner(request,*args,**kwargs):tag_url=request.get_full_path()if request.COOKIES.get('sign')=='1314520':res=func(request,*args,**kwargs)return reselse:#讀取攜帶的cookie,cookie不正確跳轉到登陸頁面# 將用戶的目址url拼接在login后面,跳轉時一并傳給登錄界面return redirect(f'/login/?next={tag_url}')return inner@auth_check def home(request):#讀取攜帶的cookie,cookie正確登陸成功#if request.COOKIES.get("sign")=='1314520':return HttpResponse("登陸成功")#讀取攜帶的cookie,cookie不正確跳轉到登陸頁面#return redirect('/login')def index(request):return HttpResponse("index登陸成功")def func(request):return HttpResponse('func登陸成功')
home.html<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <a>這里是home</a> </body> </html>
login.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <form action="" method="post"><p>username:<input type="text" name="username" class="form-control"></p><p>password:<input type="password" name="password" class="form-control"</p><input type="submit" class="btn btn-success"> </form> </body> </html>
Cookie總結?
操作cookie的三種方式
因為視圖函數有三種返回值的方式:
return HttpResponse()
return render()
return redirect()
三種返回方式都是返回一個對象,而cookie必須在return之前操作,就必須要用到obj對象了,對應的也有三種操作cookie的方式。
方式一:
obj1 = HttpResponse() # 操作cookie return obj1
方式二:
obj2 = render() # 操作cookie return obj2
方式三:
obj3 = redirect() # 操作cookie return obj3
操作cookie分為設置和獲取兩部分:
# 1 設置cookie obj.set_cookie(key,value)# 2 獲取cookie res = request.COOKIE.get(key) # 3 刪除cookie obj.delete_cookie('key') # 例如: obj.set_cookie('username''tom') """ 在設置cookie的時候可以添加一個超時時間max_ageexpires兩者都是設置超時時間的,都是以秒為單位,不同的是,針對IE瀏覽器需要用expires才行超時主動刪除cookie,可用于超時注銷登錄""" obj.set_cookie('username','tom',expires=3,max_age=3) res = request.COOKIE.get('username')
Session?
Session的由來
Cookie雖然在一定程度上解決了“保持狀態”的需求,但是由于Cookie本身最大支持4096字節,以及Cookie本身保存在客戶端,可能被攔截或竊取,因此就需要有一種新的東西,它能支持更多的字節,并且他保存在服務器,有較高的安全性。這就是Session。
問題來了,基于HTTP協議的無狀態特征,服務器根本就不知道訪問者是“誰”。那么上述的Cookie就起到橋接的作用。
我們可以給每個客戶端的Cookie分配一個唯一的id,這樣用戶在訪問時,通過Cookie,服務器就知道來的人是“誰”。然后我們再根據不同的Cookie的id,在服務器上保存一段時間的私密資料,如“賬號密碼”等等。
總結而言:Cookie彌補了HTTP無狀態的不足,讓服務器知道來的人是“誰”;但是Cookie以文本的形式保存在本地,自身安全性較差;所以我們就通過Cookie識別不同的用戶,對應的在Session里保存私密的信息以及超過4096字節的文本。
另外,上述所說的Cookie和Session其實是共通性的東西,不限于語言和框架。
Django中Session相關方法?
# 獲取、設置、刪除Session中數據
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在則不設置
del request.session['k1']
# 所有 鍵、值、鍵值對
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()?
? ? 這用于確保前面的會話數據不可以再次被用戶的瀏覽器訪問
? ? 例如,django.contrib.auth.logout() 函數中就會調用它。# 設置會話Session和Cookie的超時時間
request.session.set_expiry(value)
? ? * 如果value是個整數,session會在些秒數后失效。
? ? * 如果value是個datatime或timedelta,session就會在這個時間后失效。
? ? * 如果value是0,用戶關閉瀏覽器session就會失效。
? ? * 如果value是None,session會依賴全局session失效策略。
Django中使用session時,做的事:
# 生成隨機字符串
# 寫瀏覽器cookie -> session_id: 隨機字符串
# 寫到服務端session:
? ? # {
? ? # ? ? "隨機字符串": {'user':'alex'}
? ? # }?
Django中的Session配置?
session數據是保存在服務端的,給客服端返回的是一個隨機字符串,服務端可以選擇sesstion的保存位置,如:
- MySQL
- 文件
- redis
- memcache
在默認情況下操作session的時候首先需要有django默認的一張django_session表,之前我們在執行數據庫遷移命名的時候django會幫我們創建很多表,而django_session就是其中的一張,用于存放session數據。
?
session_key字段對應的就是django返回的服務端的隨機字符串,session_data字段就是用戶信息,expire_data 則是過期時間,注意django默認的session過期時間是14天,當然也可以修改。django_session數據的條數:
同一個計算機上(IP地址)同一個瀏覽器只會有一條數據生效,當session過期的時候可能會出現多條數據對應一個瀏覽器,但是該現象不會持續多久,內部會自動識別過期的數據,進行刪除,也可以寫代碼進行清除,主要是為了節省服務端數據庫資源。
操作session的方式很簡單
設置session:
request.session['key'] =value
設置過期時間,過期時間必須分開來設置
request.session.set_expiry()
括號內可以放四種類型的參數:
- 整數————多少秒失效
- 日期對象————到指定日期自動失效
- 0————一旦當前瀏覽器窗口關閉立即失效
- 不寫————失效時間取決于django內部全局的session默認的失效時間
設置session django內部做了哪些事
request.session['hobby'] = 'read'
- django內部會自動產生一個隨機的字符串
- django內部自動將隨機字符串和對應的數據存儲到django_session表中
- 先在內存中產生操作數據的緩存
- 在響應結果django中間件的時候才真正的操作數據庫
- 將產生的隨機字符串返回給瀏覽器保存
獲取session:
request.session.get('hobby')
從上面的設置和獲取session可以看成,操作session相對于在操作一個字典。
獲取session django內部做了哪些事
- 自動從瀏覽器請求中獲取sessionid對應的隨機字符串
- 拿著該數據字符串去django_session表中查找對應的數據
- 獲取字符串對應的數據
- 如果對比上了則將對應的數據取出并以字典的形式封裝到
request.sessoin
中,通過key就能取值- 如果對比不上,則
request.session.get()
返回的是None。清除session
request.session.delete()
————只刪服務端的,客戶端的不刪request.session.flush()
————瀏覽器和服務端都清空(推薦使用)
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 = 1209600 ? ? ? ? ? ? ? ? ? ? ? ? ? ? # Session的cookie失效日期(2周)(默認)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False ? ? ? ? ? ? ? ? ?# 是否關閉瀏覽器使得Session過期(默認)
SESSION_SAVE_EVERY_REQUEST = False ? ? ? ? ? ? ? ? ? ? ? # 是否每次請求都保存Session,默認修改之后才保存(默認)
?Session版登錄校驗
views
from functools import wrapsdef check_login(func):#@wraps函數實際上是一個裝飾器,它需要被用來裝飾裝飾器函數。@wraps(func)def inner(request,*args,**kwargs):#get_full_path():返回網址中的地址path,包括查詢字符串,不包括域名(IP地址)和端口next_url=request.get_full_path()if request.session.get('username'):#激活進行下一步indexreturn func(request,*args,**kwargs)else:#?next = / index在登錄完成后可以設計返回原來的頁面#返回原來的登陸頁面return redirect('/login1/?next={}'.format(next_url))return innerdef login1(request):if request.method=='POST':username=request.POST.get('username')password=request.POST.get('password')if username=='alex' and password=='111':#設置sessionrequest.session['username']=username#next參數用于標記,從哪兒來,回哪兒去。從用戶地址頁來就回到用戶地址頁去,以此類推# 獲取跳到登陸頁面之前的URL#GET格式請求體next_url=request.GET.get('next')# 如果有,就跳轉回登陸之前的URLif next_url:return redirect(next_url)else:# 否則默認跳轉到index頁面return redirect('/index')return render(request,'login1.html')@check_login def logout(request):obj=redirect('login1.html')#退出瀏覽器即刪除#刪除所有當前請求相關的session#session.flush()清除會話數據,在存儲中刪除會話的整條數據。request.session.flush()return obj@check_login def index(request):#根據鍵讀取值#request.session.get('鍵',默認值)current_user=request.session.get('username',None)return render(request,'index.html',{'username':current_user})
login1.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <form action="" method="post"><p>username:<input type="text" name="username" class="form-control"></p><p>password:<input type="password" name="password" class="form-control"</p><input type="submit" class="btn btn-success"> </form> </body> </html>
CBV如何實現添加裝飾器?
有三種添加方式,在方法上方添加、在類上方添加、在類中自定義dispatch方法,在該方法上方定義裝飾器(裝飾器在類里的所有方法都會生效)。
方式一:
from django.views import View form django.utils.decorators import method_docoratorclass MyLogin(View):@method_decorator(login_auth)def get(self,request):return HttpResponse("get請求")@method_decorator(login_auth)def post(self,request):return HttpResponse('post請求')
CBV中django不建議你直給類的方法加裝飾器,無論該裝飾器都能正常的給到方法,但是還是不建議直接加, 所以這樣添加裝飾器的方式不推薦使用。
方式二:
@method_decorator(login_auth,name = 'get') @method_decorator(login_auth,name = 'post') class MyLogin(View):...
第一個參數是裝飾器的名字,name指定給哪個方法添加該裝飾器,這種方式能夠實現針對不同的方法加不同的裝飾器,推薦使用。
方式三:
class MyLogin(View):@method_decorator(login_auth)def dispatch(self,*args,**kwargs):return super().dispatch(request,*args,**kwargs)
這種方式添加的裝飾器,它會直接作用于當前類里面的所有的方法。
?dispatch() 函數:視圖類的分發方法
作用
????????Django的dispatch( )函數是視圖類中用于路由請求的方法,負責根據請求方法(GET、POST等)調用不同的處理方法,并返回響應。
使用方法
????????Django的dispatch( )函數通常配合HTTP請求方法裝飾器使用,這些裝飾器映射了對應的請求方法,如@require_GET、@require_POST等。
????????當視圖函數中被dispatch( )調用的HTTP請求方法不是視圖函數類中定義的方法時,該函數將拋出405(方法不允許)或501(未實現)狀態碼的HttpResponseNotAllowed或HttpResponseNotImplemented響應。
下面是dispatch( )函數的一個示例,用于處理GET請求。
class MyView(View):def get(self, request, *args, **kwargs):# 處理GET請求的代碼return HttpResponse('Hello, World!')def dispatch(self, request, *args, **kwargs):# 處理請求前的前置操作,如權限檢查等return super(MyView, self).dispatch(request, *args, **kwargs)
????????上面的類實現了一個基礎的視圖處理GET請求。當訪問該類的視圖時,dispatch()函數將首先執行權限檢查等前置操作,然后再調用get( )方法處理請求,最后返回響應。
如果需要處理POST請求,只需添加一個post( )方法即可。
class MyView(View):def get(self, request, *args, **kwargs):# 處理GET請求的代碼return HttpResponse('Hello, World!')def post(self, request, *args, **kwargs):# 處理POST請求的代碼return HttpResponse('Hello, POST!')def dispatch(self, request, *args, **kwargs):# 處理請求前的前置操作,如權限檢查等return super(MyView, self).dispatch(request, *args, **kwargs)
dispatch( )函數的執行順序是:先執行dispatch( )的公共部分,最后執行動態方法(即根據HTTP請求方法調用的方法)。如果在處理請求前需要執行公共部分的前置操作,應當將其寫在dispatch( )中。
另外,使用dispatch( )函數的類視圖還可以實現PUT、DELETE請求等其他HTTP請求方法。
示例
下面給出兩個使用dispatch()函數的類視圖示例。
示例一:登錄視圖
from django.views.generic import View from django.shortcuts import render from django.contrib.auth import authenticate, login from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exemptclass LoginView(View):@method_decorator(csrf_exempt)def dispatch(self, request, *args, **kwargs):return super(LoginView, self).dispatch(request, *args, **kwargs)def get(self, request):return render(request, 'login.html')def post(self, request):username = request.POST.get('username')password = request.POST.get('password')user = authenticate(request, username=username, password=password)if user is not None:login(request, user)return HttpResponse('登錄成功')else:return HttpResponse('用戶名或密碼錯誤!')
以上是一個處理登錄請求的視圖類,dispatch()函數使用了csrf_exempt裝飾器避免表單提交時的csrf驗證錯誤。
示例二:支付視圖
from django.views.generic import View from django.shortcuts import render, HttpResponse from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt import stripeclass PaymentView(View):stripe.api_key = "sk_test_xxxx"@method_decorator(csrf_exempt)def dispatch(self, request, *args, **kwargs):return super(PaymentView, self).dispatch(request, *args, **kwargs)def get(self, request):return render(request, 'payment.html')def post(self, request):token = request.POST.get('stripeToken')amount = request.POST.get('amount')try:charge = stripe.Charge.create(amount=amount,currency='usd',source=token,description='Stripe Payment')return HttpResponse('支付成功,支付金額為:$%s' % (amount/100))except stripe.error.CardError as e:return HttpResponse('支付失敗:%s' % str(e))
以上是一個使用Stripe進行支付的視圖類,使用了dispatch()函數和csrf_exempt裝飾器處理請求,并使用stripe庫進行支付。
END?