Django中介軟體、csrf跨站請求、csrf裝飾器、基於django中介軟體學習程式設計思想
django中介軟體
中介軟體介紹
什麼是中介軟體?
官方的說法:中介軟體是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的外掛系統,用於在全域性範圍內改變Django的輸入和輸出。每個中介軟體元件都負責做一些特定的功能。
但是由於其影響的是全域性,所以需要謹慎使用,使用不當會影響效能。
說的直白一點中介軟體是幫助我們在檢視函式執行之前和執行之後都可以做一些額外的操作,它本質上就是一個自定義類,類中定義了幾個方法,Django框架會在請求的特定的時間去執行這些方法。
我們一直都在使用中介軟體,只是沒有注意到而已,開啟Django專案的Settings.py檔案,看到下圖的MIDDLEWARE配置項。
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
MIDDLEWARE配置項是一個列表(列表是有序的,記住這一點,後面你就知道為什麼要強調有序二字),列表中是一個個字串,這些字串其實是一個個類,也就是一個個中介軟體。
我們之前已經接觸過一個csrf相關的中介軟體了?我們一開始讓大家把他註釋掉,再提交post請求的時候,就不會被forbidden了,後來學會使用csrf_token之後就不再註釋這個中介軟體了。
那接下來就學習中介軟體中的方法以及這些方法什麼時候被執行。
自定義中介軟體
中介軟體可以定義五個方法,分別是:(主要的是process_request和process_response)
- process_request(self,request)
- process_response(self, request, response)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
以上方法的返回值可以是None或一個HttpResponse物件,如果是None,則繼續按照django定義的規則向後繼續執行,如果是HttpResponse物件,則直接將該物件返回給使用者。
準備工作
- 在專案名或者應用名下建立一個任意名稱的資料夾(應用名下更加解耦合)
- 在該資料夾內建立一個任意名稱的py檔案
- 在該py檔案內需要書寫類(這個類必須繼承MiddlewareMixin)
- 上面繼承的類需要匯入模組from django.utils.deprecation import MiddlewareMixin
- 然後在這個類裡就可以自定義5個方法了(用幾個寫幾個)
- 需要將類的路徑以字串的形式註冊到配置檔案中才能生效
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'你自己寫的中介軟體的路徑1',
'你自己寫的中介軟體的路徑2',
'你自己寫的中介軟體的路徑3',
]
process_request(掌握)
from django.utils.deprecation import MiddlewareMixin
class MyMiddle1(MiddlewareMixin):
def process_request(self, request):
print('我是第一個自定義中介軟體裡面的process_request方法')
class MyMiddle2(MiddlewareMixin):
def process_request(self, request):
print('我是第二個自定義中介軟體裡面的process_request方法')
在settings.py的MIDDLEWARE配置項中註冊上述兩個自定義中介軟體:
MIDDLEWARE = [
...
# 註冊自己的中介軟體(在應用下建立路徑有提示,但是在專案下建立就沒有提示了)
'app01.middleware.mymiddleware.MyMiddle1',
'app01.middleware.mymiddleware.MyMiddle2',
]
在view.py裡定義如下函式
def index(request):
print('我是檢視函式index')
return HttpResponse('index')
訪問檢視函式index,看一下終端的輸出:
把settings.py裡註冊的MyMiddle1和MyMiddle1的位置調換一下,再訪問index檢視,會發現終端中列印的內容如下:
如果在中介軟體裡返回HttpResponse物件,那麼:
class MyMiddle1(MiddlewareMixin):
def process_request(self, request):
print('我是第一個自定義中介軟體裡面的process_request方法')
return HttpResponse('嘿嘿嘿') # 在這返回HttpResponse物件
class MyMiddle2(MiddlewareMixin):
def process_request(self, request):
print('我是第二個自定義中介軟體裡面的process_request方法')
總結
1.請求來的時候需要經過每個中介軟體裡面的process_request方法
結果的順序是按照配置檔案中註冊的中介軟體從上往下的順序依次執行
2.如果中介軟體裡沒有定義該方法,那麼直接跳過執行下一個中介軟體
3.如果該方法返回了HttpResponse物件,那麼請求將不再繼續往後執行
而是直接原路返回,並把HttpResponse物件返回到瀏覽器
process_request方法就是用來做全域性相關的所有限制功能
process_response(掌握)
class MyMiddle1(MiddlewareMixin):
def process_request(self, request):
print('我是第一個自定義中介軟體裡面的process_request方法')
def process_response(self, request, response):
print('我是第一個自定義中介軟體裡面的process_response方法')
return response # 必須返回 不然報錯
class MyMiddle2(MiddlewareMixin):
def process_request(self, request):
print('我是第二個自定義中介軟體裡面的process_request方法')
def process_response(self, request, response):
print('我是第二個自定義中介軟體裡面的process_response方法')
return response # 必須返回 不然報錯
訪問檢視函式index,看一下終端的輸出:
如果在process_response方法裡返回HttpResponse物件
class MyMiddle1(MiddlewareMixin):
def process_request(self, request):
print('我是第一個自定義中介軟體裡面的process_request方法')
def process_response(self, request, response):
print('我是第一個自定義中介軟體裡面的process_response方法')
return HttpResponse('哈哈') # 在這裡改為返回HttpResponse物件
class MyMiddle2(MiddlewareMixin):
def process_request(self, request):
print('我是第二個自定義中介軟體裡面的process_request方法')
def process_response(self, request, response):
print('我是第二個自定義中介軟體裡面的process_response方法')
return response
總結
1.響應走的時候需要經過每一箇中間件裡面的process_response方法
該方法有兩個額外的引數request,response
2.該方法必須返回一個HttpResponse物件,預設返回的就是形參response,也可以返回你定義的
3.順序是按照配置檔案中註冊的中介軟體從下往上依次經過,如果你沒定義的話,就直接跳過執行下一個
如果在第一個process_request方法就已經返回了HttpResponse物件,那麼響應走的時候是經過所有的中介軟體裡面的process_response還是有其他情況
class MyMiddle1(MiddlewareMixin):
def process_request(self, request):
print('我是第一個自定義中介軟體裡面的process_request方法')
return HttpResponse('baby') # 在這返回HttpResponse物件
def process_response(self, request, response):
print('我是第一個自定義中介軟體裡面的process_response方法')
return response
class MyMiddle2(MiddlewareMixin):
def process_request(self, request):
print('我是第二個自定義中介軟體裡面的process_request方法')
def process_response(self, request, response):
print('我是第二個自定義中介軟體裡面的process_response方法')
return response
總結:
只走返回了HttpResponse物件該方法的同級的process_response方法
然後就直接原路返回
瞭解
flask框架也有一箇中間件但是它的規律
只要返回資料了就必須經過所有中介軟體裡面的類似於process_reponse方法
process_view(瞭解)
路由匹配成功之後 執行檢視函式之前,會自動執行中介軟體裡面的該方法
順序是按照配置檔案中註冊的中介軟體從上往下的順序依次執行
process_exception
class MyMiddle1(MiddlewareMixin):
def process_request(self, request):
print('我是第一個自定義中介軟體裡面的process_request方法')
def process_response(self, request, response):
print('我是第一個自定義中介軟體裡面的process_response方法')
return response
def process_exception(self, request, exception):
print('我是第一個自定義中介軟體裡面的process_exception方法')
print(exception)
class MyMiddle2(MiddlewareMixin):
def process_request(self, request):
print('我是第二個自定義中介軟體裡面的process_request方法')
def process_response(self, request, response):
print('我是第二個自定義中介軟體裡面的process_response方法')
return response
def process_exception(self, request, exception):
print('我是第二個自定義中介軟體裡面的process_exception方法')
print(exception)
在view.py中
def index(request):
print('我是檢視函式index')
asdasd # 故意寫個語法錯誤
return HttpResponse('index')
process_template_response(極少)
返回的HttpResponse物件有render屬性的時候才會觸發
順序是按照配置檔案中註冊了的中介軟體從下往上依次經過
csrf跨站請求偽造
"""
釣魚網站
我搭建一個跟正規網站一模一樣的介面(中國銀行)
使用者不小心進入到了我們的網站,使用者給某個人打錢
打錢的操作確確實實是提交給了中國銀行的系統,使用者的錢也確確實實減少了
但是唯一不同的時候打錢的賬戶不適用戶想要打的賬戶變成了一個莫名其妙的賬戶
大學英語四六級
考之前需要學生自己網站登陸繳費
內部本質
我們在釣魚網站的頁面 針對對方賬戶 只給使用者提供一個沒有name屬性的普通input框
然後我們在內部隱藏一個已經寫好name和value的input框
如何規避上述問題
csrf跨站請求偽造校驗
網站在給使用者返回一個具有提交資料功能頁面的時候會給這個頁面加一個唯一標識
當這個頁面朝後端傳送post請求的時候 我的後端會先校驗唯一標識,如果唯一標識不對直接拒絕(403 forbbiden)如果成功則正常執行
"""
csrf校驗
針對form表單
在form表單內部的任意位置書寫{% csrf_token %}
eg:
<form action="" method="post">
{% csrf_token %} # 在這書寫這個就可以通過csrf校驗,此時你就不用註釋掉csrf那行中介軟體了
<p>username:<input type="text" name="username"></p>
<p>target_user:<input type="text" name="target_user"></p>
<p>money:<input type="text" name="money"></p>
<input type="submit">
</form>
即使沒有註釋掉 django.middleware.csrf.CsrfViewMiddleware 中介軟體,也可以成功提交form表單的資料
針對Ajax請求
方式一
$('#d1').click(function (){
$.ajax({
url:'',
type:'post',
// 第一種 利用標籤查詢獲取頁面上的隨機字串
data:{'username':'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
success:function (){
}
})
})
方式二
$('#d1').click(function (){
$.ajax({
url:'',
type:'post',
// 第二種 利用模版語法提供的快捷書寫
data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},
success:function (){
}
})
})
方式三
新建一個JS檔案,把下面程式碼CV進去,再把它引入你的html檔案中
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
csrf相關裝飾器
在FBV上的效果
csrf_exempt(忽視校驗)
# 匯入模組
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt
def transfer(request):
return render(request, 'transfer.html')
結論: 可以看出中介軟體不註釋掉用這個@csrf_exempt裝飾器也可以忽視校驗
csrf_protect(強制校驗)
# 註釋掉 django.middleware.csrf.CsrfViewMiddleware 中介軟體
# 匯入模組
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_csrf_protect
def transfer(request):
return render(request, 'transfer.html')
結論: 可以看出中介軟體註釋掉用這個@csrf_protect裝飾器也可以強制校驗
在CBV上的效果
csrf_protect(強制校驗)
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
@method_decorator(csrf_protect,name='post') # 第二種可以
class Csrf(View):
@method_decorator(csrf_protect) # 第三種可以
def dispatch(self, request, *args, **kwargs):
return super(Csrf, self).dispatch(request, *args, **kwargs)
@method_decorator(csrf_protect) # 第一種可以
def get(self, request):
return HttpResponse('get')
def post(self, request):
return HttpResponse('post')
結論: 這三種方式加csrf_protect裝飾器都可以正常使用
csrf_exempt(忽視校驗)
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
@method_decorator(csrf_exempt,name='post') # 第二種不可以
class Csrf(View):
@method_decorator(csrf_exempt) # 第三種可以
def dispatch(self, request, *args, **kwargs):
return super(Csrf, self).dispatch(request, *args, **kwargs)
@method_decorator(csrf_exempt) # 第一種不可以
def get(self, request):
return HttpResponse('get')
def post(self, request):
return HttpResponse('post')
結論: 這三種方式只有再把csrf_exempt裝飾器加在dispatch方法上才可以正常使用
importlib模組
# 模組:importlib
import importlib
res = 'myfile.b'
ret = importlib.import_module(res) # 等價於from myfile import b
# 該方法最小單位只能到py檔名
基於django中介軟體學習程式設計思想
1.建立一個notify資料夾(名字隨便) , 在裡面建立4個py檔案
2.在email、qq、wechat裡書寫以下程式碼3
# qq.py檔案內
class Qq(object):
def __init__(self):
pass # 這裡寫該函式其他的準備工作
def send(self, content):
print('qq通知:%s' % content)
# wechat.py檔案內
class Wechat(object):
def __init__(self):
pass # 這裡寫該函式其他的準備工作
def send(self, content):
print('微信通知:%s' % content)
# email.py檔案內
class Email(object):
def __init__(self):
pass # 這裡寫該函式其他的準備工作
def send(self, content):
print('郵箱通知:%s' % content)
3.建立settings.py
NOTIFY_LIST = [
'notify.email.Email',
'notify.qq.Qq',
'notify.wechat.Wechat',
]
(核心思想, 很重要)4.在notify資料夾裡的_ _ init _ _裡書寫以下程式碼
import settings
import importlib
def send_all(content):
for path_str in settings.NOTIFY_LIST: # 'notify.email.Email'
module_path, class_name = path_str.rsplit('.', maxsplit=1) # 'notify.email' 'Email'
# 利用字串匯入模組
module = importlib.import_module(module_path) # 等價於from notify import email
# 利用反射獲得類名(記憶體地址)
cls = getattr(module, class_name) # cls就是之前定義的類的名字
# 例項化生成物件
obj = cls()
# 利用鴨子型別直接呼叫send方法
obj.send(content)
最後建立一個start.py檔案看效果
import notify
content = '撲街仔'
notify.send_all(content)
這樣你就實現了和django中介軟體中類似的功能,如果不想用哪個功能直接去settings.py裡把對應的字串註釋掉就行了
要新增功能只需要新建一個py檔案然後書寫功能最後註冊進settings.py檔案裡的列表內就行了
eg: