首頁 > 軟體

Django 中介軟體

2020-09-22 14:00:38

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,那麼會同級進行返回。不同於flaskflask則還是會至下而上進行返回。

  

自定義中介軟體

   自定義中介軟體做下面三步即可:

   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方法。

   requestHttpRequest物件。

   view_funcDjango即將使用的檢視函數。 (它是實際的函數物件,而不是函數的名稱作為字元串。)

   view_args是將傳遞給檢視的位置參數的列表.

   view_kwargs是將傳遞給檢視的關鍵字參數的字典。 view_argsview_kwargs都不包含第一個檢視參數(request)。

   它應該返回None或一個HttpResponse物件。

   如果返回NoneDjango將繼續處理這個請求,執行任何其他中介軟體的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物件。

   並且這個responseTemplateResponse物件(由檢視函數或者中介軟體產生)。

   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_request

  

process_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  # 新增裝飾器的模組

IT145.com E-mail:sddin#qq.com