中間件
中間件介紹
什麼是中間件?html
官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局範圍內改變Django的輸入和輸出。每一箇中間件組件都負責作一些特定的功能。python
可是因爲其影響的是全局,因此須要謹慎使用,使用不當會影響性能。ajax
咱們一直都在使用中間件,只是沒有注意到而已,打開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配置項是一個列表,列表中是一個個字符串,這些字符串實際上是一個個類,也就是一個個中間件。django
咱們以前已經接觸過一個csrf相關的中間件了?咱們一開始讓你們把他註釋掉,再提交post請求的時候,就不會被forbidden了,後來學會使用csrf_token以後就再也不註釋這個中間件了。瀏覽器

自定義中間件
中間件能夠定義五個方法,分別是:(主要的是process_request和process_response)服務器
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
以上方法的返回值能夠是None或一個HttpResponse對象,若是是None,則繼續按照django定義的規則向後繼續執行,若是是HttpResponse對象,則直接將該對象返回給用戶。cookie
自定義一箇中間件示例(自定義以後要在MIDDLEWARE中註冊纔有用)session
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1裏面的 process_request")
def process_response(self, request, response):
print("MD1裏面的 process_response")
return response
上面中間件的做用分別爲在請求以前和請求以後在控制檯打印一句話app
process_request
process_request有一個參數,就是request,這個request和視圖函數中的request是同樣的。
它的返回值能夠是None也能夠是HttpResponse對象。返回值是None的話,按正常流程繼續走,交給下一個中間件處理,若是是HttpResponse對象,Django將不執行視圖函數,而將相應對象返回給瀏覽器。
咱們來看看多箇中間件時,Django是如何執行其中的process_request方法的。
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1裏面的 process_request")
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2裏面的 process_request")
pass
在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',
'middlewares.MD1', # 自定義中間件MD1
'middlewares.MD2' # 自定義中間件MD2
]
此時,咱們訪問一個視圖,會發現終端中打印以下內容:
MD1裏面的 process_request
MD2裏面的 process_request
app01 中的 index視圖
把MD1和MD2的位置調換一下,再訪問一個視圖,會發現終端中打印的內容以下:
MD2裏面的 process_request
MD1裏面的 process_request
app01 中的 index視圖
看結果咱們知道:視圖函數仍是最後執行的,MD2比MD1先執行本身的process_request方法。
在打印一下兩個自定義中間件中process_request方法中的request參數,會發現它們是同一個對象。
由此總結一下:
- 中間件的process_request方法是在執行視圖函數以前執行的。
- 當配置多箇中間件時,會按照MIDDLEWARE中的註冊順序,也就是列表的索引值,從前到後依次執行的。
- 不一樣中間件之間傳遞的request都是同一個對象
process_response
執行順序:
按照註冊順序的倒序(在settings.py裏面設置中 從下到上的順序)
什麼時候執行:
請求有響應的時候
返回值:
必須返回一個response對象
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1裏面的 process_request")
def process_response(self, request, response):
print("MD1裏面的 process_response")
return response
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2裏面的 process_request")
pass
def process_response(self, request, response):
print("MD2裏面的 process_response")
return response
終端的輸出:
MD2裏面的 process_request
MD1裏面的 process_request
app01 中的 index視圖
MD1裏面的 process_response
MD2裏面的 process_response
process_view
process_view(self, request, view_func, view_args, view_kwargs)
該方法有四個參數:
request是HttpRequest對象。
view_func是Django即將使用的視圖函數。 (它是實際的函數對象,而不是函數的名稱做爲字符串。)
view_args是將傳遞給視圖的位置參數的列表.
view_kwargs是將傳遞給視圖的關鍵字參數的字典。 view_args和view_kwargs都不包含第一個視圖參數(request)。
Django會在調用視圖函數以前調用process_view方法。
它應該返回None或一個HttpResponse對象。 若是返回None,Django將繼續處理這個請求,執行任何其餘中間件的process_view方法,而後在執行相應的視圖。 若是它返回一個HttpResponse對象,Django不會調用適當的視圖函數。 它將執行中間件的process_response方法並將應用到該HttpResponse並返回結果。
給MD1和MD2添加process_view方法:
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1裏面的 process_request")
def process_response(self, request, response):
print("MD1裏面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__)
class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2裏面的 process_request")
pass
def process_response(self, request, response):
print("MD2裏面的 process_response")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__)
訪問index視圖函數,看一下輸出結果:
MD2裏面的 process_request
MD1裏面的 process_request
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x000001DE68317488> index
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x000001DE68317488> index
app01 中的 index視圖
MD1裏面的 process_response
MD2裏面的 process_response
process_view方法是在process_request以後,視圖函數以前執行的,執行順序按照MIDDLEWARE中的註冊順序從前到後順序執行的
process_exception
process_exception(self, request, exception)
該方法兩個參數:
一個HttpRequest對象
一個exception是視圖函數異常產生的Exception對象。
這個方法只有在視圖函數中出現異常了才執行,它返回的值能夠是一個None也能夠是一個HttpResponse對象。若是是HttpResponse對象,Django將調用模板和中間件中的process_response方法,並返回給瀏覽器,不然將默認處理異常。若是返回一個None,則交給下一個中間件的process_exception方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。
執行順序:
按照註冊順序的倒序(在settings.py裏面設置中 從下到上的順序)
什麼時候執行:
視圖函數中拋出異常的時候才執行
返回值:
返回None,繼續執行後續中間件的process_exception
返回response
process_template_response(用的比較少)
process_template_response(self, request, response)
執行順序:
按照註冊順序的倒序(在settings.py裏面設置中 從下到上的順序)
什麼時候執行:
視圖函數執行完,在執行視圖函數返回的響應對象的render方法以前
返回值:
返回None,繼續執行後續中間件的process_exception
返回response
中間件的執行流程
上一部分,咱們瞭解了中間件中的5個方法,它們的參數、返回值以及何時執行,如今總結一下中間件的執行流程。
請求到達中間件以後,先按照正序執行每一個註冊中間件的process_reques方法,process_request方法返回的值是None,就依次執行,若是返回的值是HttpResponse對象,再也不執行後面的process_request方法,而是執行當前對應中間件的process_response方法,將HttpResponse對象返回給瀏覽器。也就是說:若是MIDDLEWARE中註冊了6箇中間件,執行過程當中,第3箇中間件返回了一個HttpResponse對象,那麼第4,5,6中間件的process_request和process_response方法都不執行,順序執行3,2,1中間件的process_response方法。

process_request方法都執行完後,匹配路由,找到要執行的視圖函數,先不執行視圖函數,先執行中間件中的process_view方法,process_view方法返回None,繼續按順序執行,全部process_view方法執行完後執行視圖函數。加入中間件3 的process_view方法返回了HttpResponse對象,則4,5,6的process_view以及視圖函數都不執行,直接從最後一箇中間件,也就是中間件6的process_response方法開始倒序執行。

process_template_response和process_exception兩個方法的觸發是有條件的,執行順序也是倒序。總結全部的執行流程以下:


中間件版登陸驗證
中間件版的登陸驗證須要依靠session,因此數據庫中要有django_session表。
urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/$', views.index),
url(r'^login/$', views.login, name='login'),
]
views.py
from django.shortcuts import render, HttpResponse, redirect
def index(request):
return HttpResponse('this is index')
def home(request):
return HttpResponse('this is home')
def login(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
if user == "Q1mi" and pwd == "123456":
# 設置session
request.session["user"] = user
# 獲取跳到登錄頁面以前的URL
next_url = request.GET.get("next")
# 若是有,就跳轉回登錄以前的URL
if next_url:
return redirect(next_url)
# 不然默認跳轉到index頁面
else:
return redirect("/index/")
return render(request, "login.html")
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登陸頁面</title>
</head>
<body>
<form action="{% url 'login' %}">
<p>
<label for="user">用戶名:</label>
<input type="text" name="user" id="user">
</p>
<p>
<label for="pwd">密 碼:</label>
<input type="text" name="pwd" id="pwd">
</p>
<input type="submit" value="登陸">
</form>
</body>
</html>
middlewares.py
class AuthMD(MiddlewareMixin):
white_list = ['/login/', ] # 白名單
balck_list = ['/black/', ] # 黑名單
def process_request(self, request):
from django.shortcuts import redirect, HttpResponse
next_url = request.path_info
print(request.path_info, request.get_full_path())
if next_url in self.white_list or request.session.get("user"):
return
elif next_url in self.balck_list:
return HttpResponse('This is an illegal URL')
else:
return redirect("/login/?next={}".format(next_url))
在settings.py中註冊
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',
'middlewares.AuthMD',
]
AuthMD中間件註冊後,全部的請求都要走AuthMD的process_request方法。
訪問的URL在白名單內或者session中有user用戶名,則不作阻攔走正常流程;
若是URL在黑名單中,則返回This is an illegal URL的字符串;
正常的URL可是須要登陸後訪問,讓瀏覽器跳轉到登陸頁面。
注:AuthMD中間件中須要session,因此AuthMD註冊的位置要在session中間的下方。
Django請求流程圖

Django 的 CSRF 保護機制
- 每次初始化一個項目時都能看到 django.middleware.csrf.CsrfViewMiddleware 這個中間件
- csrf是基於中間件實現的,那麼是放在哪一個中間件裏的呢?---放在_view中間件裏
- 爲何放在_view裏?緣由:由於CBV的csrf有個裝飾器,啓用或棄用csrf,可是_request只是得到視圖函數,_view纔是執行視圖函數,這裏才能判斷出哪一個啓用csrf,因此是放在_view裏的
- 每次在模板裏寫 form 時都知道要加一個 {% csrf_token %} tag
- 每次發 ajax POST 請求,都須要加一個 X_CSRFTOKEN 的 header
什麼是 CSRF
CSRF, Cross Site Request Forgery, 跨站點僞造請求。
舉例來說,當你看小姐姐照片的同時,登錄了銀行網站,而後你銀行帳戶尚未退出,這時,你又回去看小姐姐,點擊了小姐姐的圖片,可是,小姐姐的圖片上有個連接,正好是一個post請求發給你登錄的銀行網頁,請求的內容就是給小姐姐轉帳,平時狀況下,你沒登錄,轉帳不可能成功(後天驗證不經過),可是,你帳戶是你本身登錄的(有cookie了),連接是你本身點的,就這樣,你的錢就到小姐姐那裏去了。
正經一點的話就是:
別的某個惡意的網站上有一個指向你的網站的連接,若是某個用戶已經登陸到你的網站上了,那麼當這個用戶點擊這個惡意網站上的那個連接時,就會向你的網站發來一個請求,你的網站會覺得這個請求是用戶本身發來的,其實呢,這個請求是那個惡意網站僞造的。
要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:
1.登陸受信任網站A,並在本地生成Cookie。
2.在不登出A的狀況下,訪問危險網站B。
Django 提供的 CSRF 防禦機制
django 第一次響應來自某個客戶端的請求時,會在服務器端隨機生成一個 token,把這個 token 放在 cookie 裏。而後每次 POST 請求都會帶上這個 token,這樣就能避免被 CSRF 攻擊。
簡單的說就是在form表單里加個csrftoken 字段,由於攻擊者不能得到第三方的Cookie(理論上),也不知道服務器上生成的字段值,因此表單中的數據也就構造失敗了
- 在返回的 HTTP 響應的 cookie 裏,django 會爲你添加一個 csrftoken 字段,其值爲一個自動生成的 token
- 在全部的 POST 表單時,必須包含一個 csrfmiddlewaretoken 字段 (只須要在模板里加一個 tag, django 就會自動幫你生成,見下面)
- 在處理 POST 請求以前,django 會驗證這個請求的 cookie 裏的 csrftoken 字段的值和提交的表單裏的 csrfmiddlewaretoken 字段的值是否同樣。若是同樣,則代表這是一個合法的請求,不然,這個請求多是來自於別人的 csrf 攻擊,返回 403 Forbidden.
- 在全部 ajax POST 請求裏,添加一個 X-CSRFTOKEN header,其值爲 cookie 裏的 csrftoken 的值
Django 裏如何使用 CSRF 防禦
- 首先,最基本的原則是:GET 請求不要用有反作用。也就是說任何處理 GET 請求的代碼對資源的訪問都必定要是「只讀「的。
- 要啓用 django.middleware.csrf.CsrfViewMiddleware 這個中間件
- 再次,在全部的 POST 表單元素時,須要加上一個 {% csrf_token %} tag
- 在渲染模塊時,使用 RequestContext。RequestContext 會處理 csrf_token 這個 tag, 從而自動爲表單添加一個名爲 csrfmiddlewaretoken 的 input