Web框架之Django_09 重要組件(Django中間件、csrf跨站請求僞造)
摘要
-
Django中間件
-
csrf跨站請求僞造
1、Django中間件:
什麼是中間件?
官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局範圍內改變Django的輸入和輸出。每一箇中間件組件都負責作一些特定的功能。html
可是因爲其影響的是全局,因此須要謹慎使用,使用不當會影響性能。前端
說的直白一點中間件是幫助咱們在視圖函數執行以前和執行以後均可以作一些額外的操做,它本質上就是一個自定義類,類中定義了幾個方法,Django框架會在請求的特定的時間去執行這些方法。web
咱們一直都在使用中間件,只是沒有注意到而已,打開Django項目的Settings.py文件,看到下圖的MIDDLEWARE配置項。ajax
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以後就再也不註釋這個中間件了。django
那接下來就學習中間件中的方法以及這些方法何時被執行。瀏覽器
Django默認有七個中間件,可是django暴露給用戶能夠自定義中間件而且裏面能夠寫五種方法安全
中間件能夠定義五個方法,分別是:(主要的是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對象,則直接將該對象返回給用戶。
自定義中間件:
自定義中間件須要在Django配置文件中告訴Django去哪找這個自定義的中間件,默承認以放在項目的對應app中,新建一個文件夾middleware,在此文件夾中新建文件my_middleware.py,而後在此文件中自定義五個方法中的任意一個或多個:
tip:中間件的本質就是含有五個能夠修改的內置方法的類,因此自定義的時候須要作的就是先繼承一個Django提供的中間件混合的父類。session
- process_request方法:
process_request有一個參數,就是request,這個request和視圖函數中的request是同樣的(在交給Django後面的路由以前,對這個request對象能夠進行一系列修改操做)。
因爲request對象是同樣的,因此咱們能夠對request對象進行操做,包括給其增長屬性,修改屬性,這樣就能夠在後續的視圖函數中經過這個request獲取到咱們在中間件添加的值。
它的返回值能夠是None,按正常流程繼續走,交給下一個中間件處理,若是是HttpResponse對象,Django將不執行視圖函數,,而將響應對象返回給瀏覽器。
# 導入MiddlewareMixin模塊 from django.utils.deprecation import MiddlewareMixin #定義中間件的類,它繼承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')
在settings.py的MIDDLEWARE配置項中註冊上述兩個自定義中間件:app
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', 'app01.middleware.my_middleware.Md1', 'app01.middleware.my_middleware.Md2', ]
此時訪問一個視圖,查看終端打印內容:
根據結果能夠知道:執行順序Md一、Md二、視圖函數內容
接着打印每一個自定義中間件Md1和Md2中的request,發現是一個request對象
總結:
## 中間件的process_request方法是在執行視圖函數以前執行的
## 當配置多箇中間件時,執行順序按照Django配置文件中的MIDDLEWARE列表中的順序依次由上至下執行
## 不一樣的中間件之間傳遞的request其實都是同一個對象
ps:當前端發來請求,Django會執行每一箇中間件中的process_request,執行順序按照配置文件由上至下執行。 -
process_response方法:
當Django處理完請求,發出返回相應的返回結果時候,process_response方法就會執行,同時多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說當請求走的時候,process_response方法會由下至上依次執行每一箇中間件的該方法
定義process_response方法時,必須給方法傳入兩個形參,request和response。request就是上述例子中同樣的對象,response是視圖函數返回的HttpResponse對象(也就是說這是Django後臺處理完以後給出一個的一個具體的視圖)。該方法的返回值(必需要有返回值)也必須是HttpResponse對象。若是不返回response而返回其餘對象,則瀏覽器不會拿到Django後臺給他的視圖,而是個人中間件中返回的對象
在Md1和Md2中定義process_response方法,而後執行一個視圖函數# 導入MiddlewareMixin模塊 from django.utils.deprecation import MiddlewareMixin #定義中間件的類,它繼承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') def process_response(self, request, response): print('Md2中的process_response') return response
總結:
## process_response方法是在視圖函數以後執行的,而且順序是Md2比Md1先執行,也就是執行順序從下到上 -
process_view 方法:路由匹配成功執行視圖函數以前自動觸發(從上到下依次執行每一箇中間件中的該方法)
詳情:
中間件的process_request方法最後一個執行,它的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方法,最後返回給瀏覽器 給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方法是在Django路由系統以後,視圖系統以前執行的,執行順序按照MIDDLEWARE中的註冊順序從前到後順序執行的
- process_exception 方法:當視執行視圖函數報錯時,自動觸發該方法(從下往上依次執行)
詳情:
process_exception process_exception(self, request, exception) 該方法兩個參數: 一個HttpRequest對象 一個exception是視圖函數異常產生的Exception對象。 這個方法只有在視圖函數中出現異常了才執行,它返回的值能夠是一個None也能夠是一個HttpResponse對象。若是是HttpResponse對象,Django將調用模板和中間件中的process_response方法,並返回給瀏覽器,不然將默認處理異常。若是返回一個None,則交給下一個中間件的process_exception方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。 給MD1和MD2添加上這個方法: 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__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") 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__) def process_exception(self, request, exception): print(exception) print("MD2 中的process_exception") 若是視圖函數中無異常,process_exception方法不執行。 想辦法,在視圖函數中拋出一個異常: def index(request): print("app01 中的 index視圖") raise ValueError("呵呵") return HttpResponse("O98K") 在MD1的process_exception中返回一個響應對象: 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__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") return HttpResponse(str(exception)) # 返回一個響應對象 看輸出結果: MD2裏面的 process_request MD1裏面的 process_request -------------------------------------------------------------------------------- MD2 中的process_view <function index at 0x0000022C09727488> index -------------------------------------------------------------------------------- MD1 中的process_view <function index at 0x0000022C09727488> index app01 中的 index視圖 呵呵 MD1 中的process_exception MD1裏面的 process_response MD2裏面的 process_response 注意,這裏並無執行MD2的process_exception方法,由於MD1中的process_exception方法直接返回了一個響應對象。
- process_template_response 方法 :視圖函數返回的對象有一個render()方法(或者代表該對象是一個TemplateResponse對象或等價方法)(從下往上依次執行)
詳情:
process_template_response(用的比較少) process_template_response(self, request, response) 它的參數,一個HttpRequest對象,response是TemplateResponse對象(由視圖函數或者中間件產生)。 process_template_response是在視圖函數執行完成後當即執行,可是它有一個前提條件,那就是視圖函數返回的對象有一個render()方法(或者代表該對象是一個TemplateResponse對象或等價方法)。 --------------------------------------------------------------------- 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__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") return HttpResponse(str(exception)) def process_template_response(self, request, response): print("MD1 中的process_template_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 def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD2 中的process_exception") def process_template_response(self, request, response): print("MD2 中的process_template_response") return response ------------------------------------------------------------------------ views.py中: def index(request): print("app01 中的 index視圖") def render(): print("in index/render") return HttpResponse("O98K") rep = HttpResponse("OK") rep.render = render return rep 訪問index視圖,終端輸出的結果: MD2裏面的 process_request MD1裏面的 process_request -------------------------------------------------------------------------------- MD2 中的process_view <function index at 0x000001C111B97488> index -------------------------------------------------------------------------------- MD1 中的process_view <function index at 0x000001C111B97488> index app01 中的 index視圖 MD1 中的process_template_response MD2 中的process_template_response in index/render MD1裏面的 process_response MD2裏面的 process_response 從結果看出: 視圖函數執行完以後,當即執行了中間件的process_template_response方法,順序是倒序,先執行MD1的,在執行MD2的,接着執行了視圖函數返回的HttpResponse對象的render方法,返回了一個新的HttpResponse對象,接着執行中間件的process_response方法。
中間件的執行流程:
上一部分,咱們瞭解了中間件中的5個方法,它們的參數、返回值以及何時執行,如今總結一下中間件的執行流程。
請求到達中間件以後,先按照正序執行每一個註冊中間件的process_request方法,process_request方法返回的值是None,就依次執行,若是返回的值是HttpResponse對象,再也不執行後面的process_request方法,而是執行當前對應中間件的process_response方法(注意不是掉頭執行全部的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 app02 import views as v2 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/',v2.login), url(r'^home/',v2.home), url(r'^index/',v2.index) ] ---------------------------------------------------------------------------- views.py from django.shortcuts import render,redirect,HttpResponse from app02 import models # Create your views here. def login(request): error_msg='' if request.method=='POST': username=request.POST.get('username') password=request.POST.get('password') user_obj=models.User.objects.filter(username=username,password=password) if user_obj: #設置session request.session['login']='ok' #獲取用戶想直接訪問的URL url=request.GET.get('next') #若是有,就跳轉到客戶初始想訪問的URL if not url: #沒有則默認跳轉到home頁面 url='/home/' return redirect(url) else: error_msg='username or password error!' return render(request,'login.html',{'error_msg':error_msg}) def home(request): return HttpResponse('<h1>這是home頁面 只有登陸了才能看到</h1>') def index(request): return HttpResponse('<h1>這是index頁面 也只有登陸了才能看到<h1>') ---------------------------------------------------------------------------- login.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄頁面</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <form action="" method="post"> {% csrf_token %} <label for="">username:<input type="text" name="username"></label> <label for="">password:<input type="password" name="password"></label> <input type="submit" value="submit"> </form> <h1 style="color: red">{{ error_msg }}</h1> </body> </html> ---------------------------------------------------------------------------- middlewares.py from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect class Check_Login(MiddlewareMixin): def process_request(self,request): next_url=request.path_info if not next_url.startswith('/login/'): is_login=request.session.get('login','') if not is_login: 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', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'middleware.my_middleware.Check_Login', ]
附:Django請求流程圖
總結:django中間件可以幫我實現 網站全局的身份驗證,黑名單,白名單,訪問頻率限制,反爬相關
2、csrf跨站請求僞造
啥叫跨站請求僞造:
首先:創造2個Django項目,模擬2個web服務框架:一個假設是正規轉帳服務框架,一個是釣魚網站服務框架
正規轉帳服務框架(Django項目1,端口默認8000):
# 交易頁面trade.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>官方網站</title> </head> <body> <h1>官方網站交易頁面</h1> <form action="http://127.0.0.1:8000/trade/" method="post"> <p>用戶名:<input type="text" name="username"></p> <p>轉帳金額:<input type="text" name="money"></p> <p>轉帳目標帳戶名:<input type="text" name="target_account"></p> <input type="submit"> </form> </body> </html> # 交易視圖函數: def trade(request): if request.method == 'POST': user_name = request.POST.get('username') money = request.POST.get('money') target_account = request.POST.get('target_account') # 模擬轉帳成功後的提示 res = '%s 給 %s 轉帳了 %s,成功!' % (user_name, target_account, money) return HttpResponse(res) return render(request, 'trade.html') (記得配路由,註釋掉setting文件中的crsf中間件)
釣魚網站服務框架(Django項目2,端口改成8001):
# trade.html頁面 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>釣魚網站</title> </head> <body> <h1>釣魚網站僞造的交易頁面</h1> <form action="http://127.0.0.1:8000/trade/" method="post"> <p>用戶名:<input type="text" name="username"></p> <p>轉帳金額:<input type="text" name="money"></p> <p>轉帳目標帳戶名:<input type="text"></p> <input type="text" name="target_account" value="Sgt" style="display:none"> <input type="submit"> </form> </body> </html> (記得配路由)
以上過程就是跨站請求僞造的過程,主要就是仿照和銀行如出一轍的的網站,裏面的用戶名和轉帳的接口都是指向銀行的,只是在網站上form表單處作了手腳,讓用戶選擇的轉帳用戶覺得是轉給他了,可是其實是轉給了隱藏input標籤預設好的帳戶,達到一種不正當的目的。
固然此處僅僅是展示一下釣魚的基本簡單過程,也就是它實現的基礎。
當咱們知道存在這種安全隱患的時候,就能夠認識這個csrf中間件的做用了,它的做用就是給每個post請求創造一個按照它的規則和用戶製做出來的特殊字符串,每當用戶在提交post請求時候都會拿這個字符串與Django中的進行比對,若是不同則拒絕請求,若是同樣則容許該請求進行路由對應的視圖函數操做。
接下來就能夠將setting配置文件中的中間件部分的csrf部分取消註釋,讓他生效,只要每次在前端頁面提交post請求出加上一行{% csrf_token %}就能夠了。這樣就完美解決了用戶post請求的安全性。
(它的實現過程其實是在post請求處,好比form表單里加上標籤:<input type="hidden" name="csrfmiddlewaretoken" value="2vzoo1lmSWgLTdI2rBQ4PTptJQKRQTRwnqeWRGcpdAQGagRp8yKg8RX2PdkF4aqh">,value是動態生成的,每一次刷新都不同)
當使用ajax發送post請求的時候csrf校驗該如何進行:
<h1>正經的網站</h1> <form action="/index3/" method="post"> {% csrf_token %} <p>username:<input type="text" name="username"></p> <p>money:<input type="text" name="money"></p> <p>others:<input type="text" name="others"></p> <input type="submit"> </form> <button>ajax</button> <script> $('button').click(function () { $.ajax({ url:'', type:'post', data:{'name':'jason','csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()}, success:function (data) { console.log(data) } }) }) </script>
也能夠自定義那個視圖函數須要csrf校驗和不須要csrf校驗:
from django.views.decorators.csrf import csrf_exempt, csrf_protect # csrf_protect設置校驗crsf # csrf_exempt設置不校驗crsf from django.utils.decorators import method_decorator from django.views import View @csrf_exempt # 不校驗csrf def index1(request): return HttpResponse('ok') @csrf_protect # 校驗csrf def index2(request): return HttpResponse('ok') ''' csrf裝飾CBV須要注意(** ** ** ) csrf_protect 跟正常的CBV裝飾器同樣 三種 csrf_exempt 只能有下面兩種方式 ''' @method_decorator(csrf_exempt, name='dispatch') # 第一種 class Index3(View): # @method_decorator(csrf_exempt) # 第二種 def dispatch(self, request, *args, **kwargs): super().dispatch(request, *args, **kwargs) # 其實都是給dispatch加