1. 程式人生 > >DAY85-Django框架(十五) 中介軟體和CSRF跨站偽裝請求

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')