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
中,請求來時中間件的執行流程是自上而下,而進行響應時中間件的執行流程都是自下而上。後端
每一個自帶中間件中的鉤子方法都會依次運行跨域
無論你自定義多少中間件,永遠都是這個流程。瀏覽器
不過須要注意的是,若是你自定義了一箇中間件,而且對其中的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
有一個參數,就是request
,這個request
和視圖函數中的request
是同樣的(在交給Django
後面的路由以前,對這個request
對象能夠進行一系列的操做)。
返回值:默認爲
None
,若是返回一個HttpResponse
對象,則將直接進行向上返回。若是是
HttpResponse
對象,Django
將不執行視圖函數,而將相應對象返回給瀏覽器。
class Mid(MiddlewareMixin): def process_request(self, request): print("process_request")
該方法有兩個參數。
多箇中間件中的process_response
方法是按照MIDDLEWARE
中的註冊順序倒序執行的,也就是說第一個中間件的process_request
方法首先執行,而它的process_response
方法最後執行,最後一箇中間件的process_request
方法最後一個執行,它的process_response
方法是最早執行。
class Mid(MiddlewareMixin): def process_response(self, request, response): print("process_response")
該方法有四個參數,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")
該方法兩個參數,這個方法只有在視圖函數中出現異常了才執行。
一個
HttpRequest
對象一個
exception
是視圖函數異常產生的Exception
對象。它返回的值能夠是一個
None
也能夠是一個HttpResponse
對象。若是是
HttpResponse
對象,Django
將調用模板和中間件中的process_response
方法,並返回給瀏覽器,不然將默認處理異常。若是返回一個
None
,則交給下一個中間件的process_exception
方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。
class Mid(MiddlewareMixin): def process_exception(self, request, exception): print("process_exception")
它有兩個參數,因爲執行條件很苛刻,因此用的很是少。
一個
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
的觸發是有條件限制的,故此再也不舉例,記住他們的執行順序是倒序便可。
因爲全部的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])))
跨域僞造請求我舉一個例子:
有一個釣魚網站,和銀行的轉帳頁面如出一轍。
可是惟一不一樣的地方在於,你在釣魚網站上輸好信息後點擊提交,它並不會將對方卡號進行提交,而是將騙子卡號進行提交(隱藏的input
框)。這個時候銀行後端收到這一條信息,你的錢就轉到騙子哪兒去了。
如何解決這個問題?可使用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
提交的話,該怎麼作?
如下有三種辦法。
方式一
經過獲取隱藏的<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 # 添加裝飾器的模塊