DAY85-Django框架(十五) 中介軟體和CSRF跨站偽裝請求
一、中介軟體
1.定義
中介軟體顧名思義,是介於request與response處理之間的一道處理過程,相對比較輕量級,並且在全域性上改變django的輸入與輸出。因為改變的是全域性,所以需要謹慎實用,用不好會影響到效能。
每次請求到檢視之前,或者響應到瀏覽器之前都會經過中介軟體的篩選
2.基本使用
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', ] #這是Django內部已經封裝好的中介軟體
3.自定義中介軟體
#中介軟體的方法
process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)
#第一步:建立一個PY檔案 #第二步:匯入from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin #第三步:新建自定義中介軟體process_request和process_response是必須的 class Mymiddle1(MiddlewareMixin): def process_request(self, request): print('Mymiddle1 request') def process_response(self, request, response): print('Mymiddle1 response') return response#process_response一定要返回HttpResponse物件 class Mymiddle2(MiddlewareMixin): def process_request(self, request): print('Mymiddle2 request') def process_response(self, request, response): print('Mymiddle2 response') return response
#第四步:寫一個檢視函式
from django.shortcuts import render,HttpResponse
def index(requset):
print('index')
return HttpResponse('ok')
#第五步:在setting裡設定 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.mymiddleware.Mymiddle1', 'app01.mymiddleware.Mymiddle2' ] #請求是從上而下一層層的通過中介軟體的,之後才是執行檢視層,而響應是從下而上一層層的返回去。
#結果
Mymiddle1 request
Mymiddle2 request
index
Mymiddle2 response
Mymiddle1 response
結論
如果請求再通過中介軟體時,在process_request遇到了return返回,那麼這個請求就不會再往下走進入檢視了,而是從當前的process_response往上的返回
from django.shortcuts import render,HttpResponse
class Mymiddle1(MiddlewareMixin):
def process_request(self, request):
print('Mymiddle1 request')
print('Mymiddle1 請求中斷')
return HttpResponse('Mymiddle1 請求中斷')
def process_response(self, request, response):
print('Mymiddle1 response')
return response#process_response一定要返回HttpResponse物件
#結果
Mymiddle1 request
Mymiddle1 請求中斷
Mymiddle1 response
4.process_view
#語法
process_view(self,
request, #request是HttpRequest物件。
callback, #view_func是Django即將使用的檢視函式。
callback_args, #view_args是將傳遞給檢視的位置引數的列表.
callback_kwargs #view_kwargs是將傳遞給檢視的關鍵字引數的字典。
)
它應該返回None或一個HttpResponse物件。 如果返回None,Django將繼續處理這個請求,執行任何其他中介軟體的process_view方法,然後在執行相應的檢視。 如果它返回一個HttpResponse物件,Django不會呼叫適當的檢視函式。 它將執行中介軟體的process_response方法並將應用到該HttpResponse並返回結果。
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse
class Mymiddle1(MiddlewareMixin):
def process_request(self, request):
print('Mymiddle1 request')
def process_response(self, request, response):
print('Mymiddle1 response')
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print("Mymiddle1 view")
class Mymiddle2(MiddlewareMixin):
def process_request(self, request):
print('Mymiddle2 request')
def process_response(self, request, response):
print('Mymiddle2 response')
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print("Mymiddle2 view")
Mymiddle1 request
Mymiddle2 request
Mymiddle1 view
Mymiddle2 view
index
Mymiddle2 response
Mymiddle1 response
結論
如果中介軟體1中process_view在return,那麼就會跳過之後的process_view和檢視函式,直接執行process_response
def process_view(self, request, callback, callback_args, callback_kwargs):
print('Mymiddle1 view中斷')
return HttpResponse('Mymiddle1 view中斷')
#結果
Mymiddle1 request
Mymiddle2 request
Mymiddle1 view中斷
Mymiddle2 response
Mymiddle1 response
可以在process_view中呼叫檢視函式,也會跳過之後的process_view和檢視函式,直接執行process_response
def process_view(self, request, callback, callback_args, callback_kwargs):
res = callback(request)
print('Mymiddle1 view中斷')
return res
#結果
Mymiddle1 request
Mymiddle2 request
index
Mymiddle1 view中斷
Mymiddle2 response
Mymiddle1 response
5.process_exception
process_exception(self,
request, #HttpRequest物件
exception #檢視函式異常產生的Exception物件
)
這個方法只有在檢視函式中出現異常了才執行,它返回的值可以是一個None也可以是一個HttpResponse物件。如果是HttpResponse物件,Django將呼叫模板和中介軟體中的process_response方法,並返回給瀏覽器,否則將預設處理異常。如果返回一個None,則交給下一個中介軟體的process_exception方法來處理異常。它的執行順序也是按照中介軟體註冊順序的倒序執行。
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse
class Mymiddle1(MiddlewareMixin):
def process_request(self, request):
print('Mymiddle1 request')
# print('Mymiddle1 請求中斷')
# return HttpResponse('Mymiddle1 請求中斷')
def process_response(self, request, response):
print('Mymiddle1 response')
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print("Mymiddle1 view")
# res = callback(request)
# print('Mymiddle1 view中斷')
# return res
def process_exception(self,request,exception):
print("Mymiddle1 exception")
class Mymiddle2(MiddlewareMixin):
def process_request(self, request):
print('Mymiddle2 request')
def process_response(self, request, response):
print('Mymiddle2 response')
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print("Mymiddle2 view")
def process_exception(self,request,exception):
print("Mymiddle2 exception")
#結果
#當檢視函式沒有出現異常時,觸發process_exception方法
Mymiddle1 request
Mymiddle2 request
Mymiddle1 view
Mymiddle2 view
index
Mymiddle2 response
Mymiddle1 response
結論
process_exception是在檢視函式執行完畢之後出發的,和process_response一樣是從下往上的執行
如果此時檢視函數出現了異常
def index(requset):
print('index')
asd#未定義的變數
return HttpResponse('ok')
1.自定義的process_exception中沒有return,那麼會內建的來丟擲異常
Mymiddle1 request
Mymiddle2 request
Mymiddle1 view
Mymiddle2 view
index
Mymiddle2 exception
Mymiddle1 exception
...
異常資訊
...
Mymiddle2 response
Mymiddle1 response
2.自定義的process_exception中寫了return,那麼就不會在執行之後的process_exception,而是執行process_response
def process_exception(self,request,exception):
print('Mymiddle2 丟擲異常')
return HttpResponse('Mymiddle2 丟擲異常')
#結果
Mymiddle1 request
Mymiddle2 request
Mymiddle1 view
Mymiddle2 view
index
Mymiddle2 丟擲異常
Mymiddle2 response
Mymiddle1 response
如果Mymiddle2的 process_exception沒有返回HttpResponse物件,那麼就會由從下往上的順序,去找有沒有返回HttpResponse物件的process_exception,直到找到。
#結果
Mymiddle1 request
Mymiddle2 request
Mymiddle1 view
Mymiddle2 view
index
Mymiddle2 exception
Mymiddle1 丟擲異常
Mymiddle2 response
Mymiddle1 response
二、CSRF跨站請求偽造
1.CSRF
CSRF(Cross-site request forgery)跨站請求偽造,也被稱為“One Click Attack”或者Session Riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。儘管聽起來像跨站指令碼(XSS),但它與XSS非常不同,XSS利用站點內的信任使用者,而CSRF則通過偽裝來自受信任使用者的請求來利用受信任的網站。與XSS攻擊相比,CSRF攻擊往往不大流行(因此對其進行防範的資源也相當稀少)和難以防範,所以被認為比XSS更具危險性
可以這樣來理解:
*攻擊者盜用了你的身份,以你的名義傳送惡意請求,對伺服器來說這個請求是完全合法的*,但是卻完成了攻擊者所期望的一個操作,比如以你的名義傳送郵件、發訊息,盜取你的賬號,新增系統管理員,甚至於購買商品、虛擬貨幣轉賬等。
2.攻擊原理
要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:
1.登入受信任網站A,並在本地生成Cookie。
2.在不登出A的情況下,訪問危險網站B。
看到這裡,你也許會說:“如果我不滿足以上兩個條件中的一個,我就不會受到CSRF的攻擊”。是的,確實如此,但你不能保證以下情況不會發生:
1.你不能保證你登入了一個網站後,不再開啟一個tab頁面並訪問另外的網站。
2.你不能保證你關閉瀏覽器了後,你本地的Cookie立刻過期,你上次的會話已經結束。(事實上,關閉瀏覽器不能結束一個會話,但大多數人都會錯誤的認為關閉瀏覽器就等於退出登入/結束會話了......)
3.上圖中所謂的攻擊網站,可能是一個存在其他漏洞的可信任的經常被人訪問的網站。
3.防禦攻擊
目前防禦 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 欄位;在請求中新增 token 並驗證;在 HTTP 頭中自定義屬性並驗證
對於CSRF的防禦,Django內部做了一箇中間件來處理CSRF的攻擊
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]
它設定了一個隨機字串,通過響應發給受信任的使用者,當受信任使用者傳送請求時必須攜帶一模一樣的隨機字串,才可以進入伺服器。而且每次響應伺服器給瀏覽器都不一樣的,有效的做到了對CSRF的防禦
方式一:請求中新增 token 並驗證
放在表單中請求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<!--渲染之後,獲得隨機字串
<input type="hidden" name="csrfmiddlewaretoken" value="RaxCabhuZdpvWxQ67whrdmImNPYuFVQ5Ies9X52TVrIbt1AdVfxPNbMUFVKgLWp4">
-->
<input type="text" name="name" placeholder="使用者名稱">
<input type="text" name="pwd" placeholder="密碼">
<input type="submit">
</form>
</body>
</html>
from django.shortcuts import render, HttpResponse
def csrf_test(request):
if request.method == 'GET':
return render(request, 'csrf_test.html')
else:
name = request.POST.get('name')
pwd = request.POST.get('pwd')
token = request.POST.get('csrfmiddlewaretoken')
print(token)
if name == 'xcq' and pwd == '123':
msg = '登陸成功'
else:
msg = '登入失敗'
return HttpResponse(msg)
通過AJAX
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load static %}
<script src="{% static 'jquery-3.3.1.js' %}"></script>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<input type="text" name="name" placeholder="使用者名稱">
<input type="text" name="pwd" placeholder="密碼">
</form>
<button id="btn">登入</button>
</body>
<script>
//post請求
$('#btn').click(function () {
$.ajax({
url:'/csrf_test/',
type:'post',
data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()},
//或者 data:{'csrfmiddlewaretoken':'{{ csrf_token }}'},
success:function (data) {
alert(data)
}
})
})
//get請求
$('#btn').click(function () {
$.ajax({
url:'/csrf_test/?id={{ csrf_token }}' ,
type:'get',
success:function (data) {
alert(data)
}
})
})
</script>
</html>
方式二:在 HTTP 頭中自定義屬性並驗證
//獲取cookie裡的csrftoken
$("#btn").click(function () {
var token=$.cookie('csrftoken')
$.ajax({
url: '/csrf_test/',
headers:{'X-CSRFToken':token},
type: 'post',
data: {
'name': $('[name="name"]').val(),
'password': $("#pwd").val(),
},
success: function (data) {
alert(data)
}
})
})
區域性禁用和區域性使用
FBV模式
from django.views.decorators.csrf import csrf_exempt,csrf_protect
#區域性禁用,全域性得使用
@csrf_exempt
def csrf_disable(request):
print(request.POST)
return HttpResponse('ok')
#區域性使用,全域性得禁用
@csrf_protect
def csrf_disable(request):
print(request.POST)
return HttpResponse('ok')
CBV模式
from django.views import View
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt,csrf_protect
@method_decorator(csrf_protect,name='dispatch')
#CBV的csrf裝飾器,只能載入類上且指定方法為dispatch,或dispatch方法上
class Csrf_disable(View):
# @method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
ret=super().dispatch(request, *args, **kwargs)
return ret
def get(self,request):
return HttpResponse('ok')
def post(self,request):
return HttpResponse('post---ok')