Django学习笔记10
中間件
中間件介紹
什么是中間件?
官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用于在全局范圍內改變Django的輸入和輸出。每個中間件組件都負責做一些特定的功能。
但是由于其影響的是全局,所以需要謹慎使用,使用不當會影響性能。
我們一直都在使用中間件,只是沒有注意到而已,打開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配置項是一個列表,列表中是一個個字符串,這些字符串其實是一個個類,也就是一個個中間件。
我們之前已經接觸過一個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對象,則直接將該對象返回給用戶。
自定義一個中間件示例(自定義之后要在MIDDLEWARE中注冊才有用)
from django.utils.deprecation import MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return response上面中間件的作用分別為在請求之前和請求之后在控制臺打印一句話
process_request
process_request有一個參數,就是request,這個request和視圖函數中的request是一樣的。
它的返回值可以是None也可以是HttpResponse對象。返回值是None的話,按正常流程繼續走,交給下一個中間件處理,如果是HttpResponse對象,Django將不執行視圖函數,而將相應對象返回給瀏覽器。
我們來看看多個中間件時,Django是如何執行其中的process_request方法的。
from django.utils.deprecation import MiddlewareMixinclass 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_response
執行順序:
按照注冊順序的倒序(在settings.py里面設置中 從下到上的順序)
何時執行:
請求有響應的時候
返回值:
必須返回一個response對象
終端的輸出:
MD2里面的 process_request MD1里面的 process_request app01 中的 index視圖 MD1里面的 process_response MD2里面的 process_responseprocess_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 MiddlewareMixinclass MD1(MiddlewareMixin):def process_request(self, request):print("MD1里面的 process_request")def process_response(self, request, response):print("MD1里面的 process_response")return responsedef 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")passdef process_response(self, request, response):print("MD2里面的 process_response")return responsedef 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_responseprocess_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 viewsurlpatterns = [url(r'^index/$', views.index),url(r'^login/$', views.login, name='login'), ]views.py
from django.shortcuts import render, HttpResponse, redirectdef 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":# 設置sessionrequest.session["user"] = user# 獲取跳到登陸頁面之前的URLnext_url = request.GET.get("next")# 如果有,就跳轉回登陸之前的URLif 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, HttpResponsenext_url = request.path_infoprint(request.path_info, request.get_full_path())if next_url in self.white_list or request.session.get("user"):returnelif 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(理論上),也不知道服務器上生成的字段值,所以表單中的數據也就構造失敗了
Django 里如何使用 CSRF 防護
- 首先,最基本的原則是:GET 請求不要用有副作用。也就是說任何處理 GET 請求的代碼對資源的訪問都一定要是“只讀“的。
- 要啟用 django.middleware.csrf.CsrfViewMiddleware 這個中間件
- 再次,在所有的 POST 表單元素時,需要加上一個 {% csrf_token %} tag
- 在渲染模塊時,使用 RequestContext。RequestContext 會處理 csrf_token 這個 tag, ?從而自動為表單添加一個名為 csrfmiddlewaretoken 的 input
轉載于:https://www.cnblogs.com/wlx97e6/p/9911127.html
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的Django学习笔记10的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【转】【centos】启动网卡报错(Fa
- 下一篇: 1-8周成绩总结