2021-05-12 14:32:11
Django 中介軟體
Django中介軟體
Django
中介軟體會對所有的資源請求,所有的返回方式,所有的路由到檢視的跳轉、所有檢視層的異常進行處理。
在Django
中,自帶的有7箇中間件,都具有不同的功能。
目前而言瞭解下面這兩個即可。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # 插入session至資料表
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # 防止跨域請求
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
這裡的每一箇中間件其實都是一個模組,利用importlib
模組使之能夠作為字元串進行匯入。
並且,在這些自帶的中介軟體中,都繼承了MiddlewareMixin
類。
在該類中,提供了五個鉤子方法,能夠讓我們對自定義中介軟體進行擴展。
中介軟體鉤子函數 | 描述 |
---|---|
process_request | 所有請求來時都會運行的方法 |
process_view | 所有路由匹配成功之後,跳轉執行檢視函數之前都會運行該方法 |
process_exception | 所有檢視中有異常發生時運行的方法 |
process_response | 所有返回頁面響應時運行的方法 |
process_template_response | 返回的HttpResponse物件具有render屬性時才會觸發該方法 |
執行順序
在Django
中,請求來時中介軟體的執行流程是自上而下,而進行響應時中介軟體的執行流程都是自下而上。
每個自帶中介軟體中的鉤子方法都會依次運行
不管你自定義多少中介軟體,永遠都是這個流程。
不過需要注意的是,如果你自定義了一箇中間件,並且對其中的process_requset
方法進行返回了HttpResponse
,那麼會同級進行返回。不同於flask
,flask
則還是會至下而上進行返回。
自定義中介軟體
自定義中介軟體做下面三步即可:
1.任意目錄下新建一個任意名稱的
.py
資料夾2.在該檔案下書寫任意名稱的類,但是一定要繼承
MiddlewareMixin
,在該類下可以進行上面五種方法的覆寫3.在
settings.py
中介軟體進行新增路徑.類名
from django.utils.deprecation import MiddlewareMixin
如我在項目全局資料夾下新建了一個資料夾。叫CustomMiddleware
,並且在裡面新建了一個py
檔案customMid
。
在該檔案下,新建了一個類Mid
。
那麼我在註冊的時候就直接新增上這個路徑即可:
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',
'p1.CustomMiddleware.customMid.Mid', # 新增的自定義中介軟體
]
process_request
process_request
有一個參數,就是request
,這個request
和檢視函數中的request
是一樣的(在交給Django
後面的路由之前,對這個request
物件可以進行一系列的操作)。
返回值:預設為
None
,如果返回一個HttpResponse
物件,則將直接進行向上返回。如果是
HttpResponse
物件,Django
將不執行檢視函數,而將相應物件返回給瀏覽器。
class Mid(MiddlewareMixin):
def process_request(self, request):
print("process_request")
process_response
該方法有兩個參數。
多箇中間件中的process_response
方法是按照MIDDLEWARE
中的註冊順序倒序執行的,也就是說第一個中介軟體的process_request
方法首先執行,而它的process_response
方法最後執行,最後一箇中間件的process_request
方法最後一個執行,它的process_response
方法是最先執行。
class Mid(MiddlewareMixin):
def process_response(self, request, response):
print("process_response")
process_view
該方法有四個參數,Django
會在呼叫檢視函數之前呼叫process_view
方法。
request
是HttpRequest
物件。
view_func
是Django
即將使用的檢視函數。 (它是實際的函數物件,而不是函數的名稱作為字元串。)
view_args
是將傳遞給檢視的位置參數的列表.
view_kwargs
是將傳遞給檢視的關鍵字參數的字典。view_args
和view_kwargs
都不包含第一個檢視參數(request
)。它應該返回
None
或一個HttpResponse
物件。如果返回
None
,Django
將繼續處理這個請求,執行任何其他中介軟體的process_view
方法,然後在執行相應的檢視。如果它返回一個
HttpResponse
物件,那麼將不會執行Django
的檢視函數,而是直接在中介軟體中掉頭,倒敘執行一個個process_response
方法,最後返回給瀏覽器
class Mid(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
print("process_view")
process_exception
該方法兩個參數,這個方法只有在檢視函數中出現異常了才執行。
一個
HttpRequest
物件一個
exception
是檢視函數異常產生的Exception
物件。它返回的值可以是一個
None
也可以是一個HttpResponse
物件。如果是
HttpResponse
物件,Django
將呼叫模板和中介軟體中的process_response
方法,並返回給瀏覽器,否則將預設處理異常。如果返回一個
None
,則交給下一個中介軟體的process_exception
方法來處理異常。它的執行順序也是按照中介軟體註冊順序的倒序執行。
class Mid(MiddlewareMixin):
def process_exception(self, request, exception):
print("process_exception")
process_template_response
它有兩個參數,由於執行條件很苛刻,所以用的非常少。
一個
HttpRequest
物件,一個response
物件。並且這個
response
是TemplateResponse
物件(由檢視函數或者中介軟體產生)。
process_template_response
是在檢視函數執行完成後立即執行,但是它有一個前提條件,那就是檢視函數返回的物件有一個render()
方法(或者表明該物件是一個TemplateResponse
物件或等價方法)。
def index(request): # 必須有render屬性/方法,該中介軟體鉤子方法才會執行
def render():
return HttpResponse("OK")
rep = HttpResponse("OK")
rep.render = render
return rep
class Mid(MiddlewareMixin):
def process_template_response(self, request, response): # 換而言之,response必須能點出render才行
print("process_template_response")
return response
執行流程
由於process_exception
以及process_template_response
的觸發是有條件限制的,故此不再舉例,記住他們的執行順序是倒序即可。
實際應用
session白名單
由於所有的request
請求都會走這個,所以我們可以對其進行session
控制。
維護一個集合(也可以做一個非關係型資料庫,放快取中),放上不需要session
認證的url
,稱之為白名單。
如果使用者未進行登入就去訪問不在白名單中的路徑,則返回一個頁面提示使用者進行登入。
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class Mid(MiddlewareMixin):
def process_request(self,request):
whitelist = {"/","/admin/","/index/","/login/","/register/"} # 不需要登入就能訪問的頁面
target_url = request.path
for url in whitelist:
if target_url in url:
return
if not request.session.get("login"): # 如果未有session,代表未登入,跳轉到登入頁面
return redirect("/login/?next={0}".format(target_url))
def login(request):
""" 登入頁面 """
target_url = request.GET.get("next",None)
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
if username == "Yunya" and password == "123456":
request.session["login"] = True
request.session.set_expiry(3600)
if not target_url:
return redirect("/index/") # 如果是直接點的登入頁面,登陸完成後跳轉到主頁
else:
return redirect(target_url) # 否則跳轉到從其他頁面過來的
return render(request,"login.html",locals())
訪問頻率限制
某些IP
訪問伺服器的頻率過高,進行攔截,比如限制每分鐘不能超過10次。
如果要配合上面的白名單進行使用,這個應該註冊在白名單上面。
import time
from django.shortcuts import redirect
from django.shortcuts import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class Mid2(MiddlewareMixin):
# 訪問IP池
visit_ip_pool = {}
def process_request(self, request):
# 獲取訪問者IP
ip = request.META.get("REMOTE_ADDR")
# 獲取訪問當前時間
visit_time = time.time()
# 判斷如果訪問IP不在池中,就將訪問的ip時間插入到對應ip的key值列表,如{"127.0.0.1":[時間1]}
if ip not in Mid2.visit_ip_pool:
Mid2.visit_ip_pool[ip] = [visit_time]
return
# 然後在從池中取出時間列表
history_time = Mid2.visit_ip_pool.get(ip)
# 迴圈判斷當前ip的時間列表,有值,並且當前時間減去列表的最後一個時間大於60s,把這種資料pop掉,這樣列表中只有60s以內的訪問時間,
while history_time and visit_time-history_time[-1] > 60:
history_time.pop()
# 如果訪問次數小於10次就將訪問的ip時間插入到對應ip的key值列表的第一位置,如{"127.0.0.1":[時間2,時間1]}
print(history_time)
if len(history_time) < 10:
history_time.insert(0, visit_time)
return None
else:
# 如果大於10次就禁止訪問
return HttpResponse("訪問過於頻繁,還需等待%s秒才能繼續訪問" % int(60-(visit_time-history_time[-1])))
CSRF
跨域偽造請求
跨域偽造請求我舉一個例子:
有一個釣魚網站,和銀行的轉賬頁面一模一樣。
但是唯一不同的地方在於,你在釣魚網站上輸好資訊後點擊提交,它並不會將對方卡號進行提交,而是將騙子卡號進行提交(隱藏的input
框)。這個時候銀行後端收到這一條資訊,你的錢就轉到騙子哪兒去了。
如何解決這個問題?可以使用CSRF
來防止跨域偽造請求。
CSRF中介軟體
在Django
中,有一箇中間件就是幹這個事兒的,派發隨機字元串,驗證隨機字元串。
'django.middleware.csrf.CsrfViewMiddleware',
我們開啟它,並且在頁面中新增上{% csrf_token %}
來獲取到這一隨機字元串,在頁面上就會顯示出來。
<form action="" method="POST">
{% csrf_token %}
<p><input type="text" placeholder="username" name="username"></p>
<p><input type="text" placeholder="password" name="password"></p>
<p><button type="submit">登入</button></p>
</form>
注意!這個標籤會生成一個input
框,一定要將他放在form
表單中。
並且!每次重新整理頁面都會生成不同的字元串。
<input type="hidden" name="csrfmiddlewaretoken" value="yoW7bYRlhbHDcDI2KugGgHpNvjFsvZj47PNKGGXHbth2pCfITEul8NkJzN4xoUXI">
那麼加上這個隨機字元串後,就可以提交POST
請求了。
Ajax請求
Ajax
提交的話,該怎麼做?
以下有三種辦法。
方式一
通過獲取隱藏的<input>
標籤中的csrfmiddlewaretoken
值,放置在data
中傳送。
$.ajax({
url: "http://127.0.0.1:8000",
type: "POST",
data: {
"username": "Yunya",
"password": 123456,
"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用JQuery取出csrfmiddlewaretoken的值,拼接到data中
},
success: function (data) {
console.log(data);
}
})
方式二
請求的鍵永遠都是csrfmiddlewaretoken
,我們只要把value
輸入為正確的隨機字元串即可。
$.ajax({
url: "/http://127.0.0.1:8000/",
type: "POST",
data: {"username": "Q1mi", "password": 123456,"csrfmiddlewaretoken":"{{csrf_token}}"},
success: function (data) {
console.log(data);
}
})
方式三
通過靜態檔案,為所有ajax
傳送請求時自動新增上csrftoken
及其隨機字元串。
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);
}
}
});
前端使用時記得匯入這個靜態檔案:
{% load static %}
<script src={% static 'js/csrf.js' %}>
$.ajax({
url: "/http://127.0.0.1:8000/",
type: "POST",
headers: {"X-CSRFToken": $.cookie('csrftoken')}, // 從Cookie取csrf_token,並設定ajax請求頭
data: {"username": "Q1mi", "password": 123456},
success: function (data) {
console.log(data);
}
})
檢視驗證
我們可以在檢視中,為某個函數單獨設定需要csrf
校驗,或者取消單獨某個函數的csrf
校驗。
需要匯入以下兩個模組。
from django.views.decorators.csrf import csrf_protect # 單獨校驗
from django.views.decorators.csrf import csrf_exempt # 取消校驗
特別注意!如果你是使用CBV
,那麼取消驗證時只能這樣設定csrf_exempt
:
@method_decorat(csrf_exempt,name="dispatch")
class Test(View):
def get(self,request):
pass
def post(self,request):
pass
關於如何為CBV
新增裝飾器,你需要匯入以下兩個模組。
from django.views import View # 使用CBV的模組,必須繼承該類
from django.utils.decorators import method_decorator # 新增裝飾器的模組
相關文章