Django 中間件

Django中間件

   Django中間件會對全部的資源請求,全部的返回方式,全部的路由到視圖的跳轉、全部視圖層的異常進行處理。html

   在Django中,自帶的有7箇中間件,都具備不一樣的功能。前端

   目前而言瞭解下面這兩個便可。ajax

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類。django

   在該類中,提供了五個鉤子方法,可以讓咱們對自定義中間件進行擴展。flask

中間件鉤子函數 描述
process_request 全部請求來時都會運行的方法
process_view 全部路由匹配成功以後,跳轉執行視圖函數以前都會運行該方法
process_exception 全部視圖中有異常發生時運行的方法
process_response 全部返回頁面響應時運行的方法
process_template_response 返回的HttpResponse對象具備render屬性時纔會觸發該方法

執行順序

   在Django中,請求來時中間件的執行流程是自上而下,而進行響應時中間件的執行流程都是自下而上。後端

   每一個自帶中間件中的鉤子方法都會依次運行跨域

   image-20200917215230866

   無論你自定義多少中間件,永遠都是這個流程。瀏覽器

   不過須要注意的是,若是你自定義了一箇中間件,而且對其中的process_requset方法進行返回了HttpResponse,那麼會同級進行返回。不一樣於flaskflask則仍是會至下而上進行返回。緩存

   image-20200917215808465

自定義中間件

   自定義中間件作下面三步便可:

   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

   img

process_response

   img

全總結

   img

   因爲process_exception以及process_template_response的觸發是有條件限制的,故此再也不舉例,記住他們的執行順序是倒序便可。

   img

實際應用

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框)。這個時候銀行後端收到這一條信息,你的錢就轉到騙子哪兒去了。

   image-20200917235214445

   如何解決這個問題?可使用CSRF來防止跨域僞造請求。

   image-20200917235951799

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  # 添加裝飾器的模塊
相關文章
相關標籤/搜索