django模塊導入/函數/中間件/MVC和MTV/CSRF

一:模塊導入

第一種:繼承

這裏的母版更像是一個架子,子板都是定義的內容(若是多個頁面中 ,存在相同的頁面;這樣咱們能夠抽到母版中實現)html

母版:layer.html前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    {% block mycss %}
    
    {% endblock %}
</head>
<body>

{% block mycontent %}

{% endblock %}

</body>
<script src="/static/js/jquery-1.12.4.js"></script>
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>

{% block myjs %}

{% endblock %}

</html>

子板:son.htmlpython

{% extends "layer.html" %}

{% block mycss %}
    寫上子板的樣式
{% endblock %}


{% block mycontent %}
    寫上子板的內容
{% endblock %}

{% block myjs %}
  寫上子板的js
{% endblock %}

第二種:導入模板

若是咱們A.html文件中寫好了一個組件,想要添加到B.html文件中。所以咱們能夠在B.html文件使用{% include "a.html" %} 語法,將A.html文件引入到B.html文件中jquery

B.htmlajax

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% include "a.html" %}

</body>
</html>

A.html數據庫

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="https://www.baidu.com">其餘</a>
</body>
</html>

二:函數

1. 使用django自帶的函數

views.pydjango

from django.shortcuts import render,HttpResponse

# Create your views here.


def test(request):


    name = "panlifu"
    return  render(request,'test.html',{"name":name})

test.htmlbootstrap

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {{ name|upper }}


</body>
</html>

2. 自定義函數

自定義函數流程
  • 在app中建立templatetags模塊後端

  • 建立任意.py文件,如:xx.py

  • 在xx.py中編寫函數

from django import template 
register =template.Library()

# 編寫一個函數 並在函數前加上裝飾器  @register.filteer   或  @register.simple_tag

@register.simple_tag
def my_upper(arg1,arg2)
  return arg1+arg2


@register.filter
def my_upper(arg1,arg2)
  return arg1+arg2
  • 在要引入此函數的html文件頂部加上{% load xx%} 導入xx模塊
  • 使用函數屬於@register.filter的函數調用{{"value" | fun:參數}},能夠在if else中作判斷條件
  • 使用函數屬於@register.simple_tag的函數調用{%fun '參數1' '參數2' '參數3'%},不能夠在if else中作判斷條件
  • 必定要在註冊使用此函數的app
須要注意的事項:
  • filter函數單獨使用時必須至少有一個傳入模板的對象做爲參數,好比

    {{ st |fun1 }}

  • filter最多隻要兩個參數

    {{ 參數1 |fun1:參數2 }}

    ,且冒號後面不能有空格

  • simple_tag函數的參數如函數名用空格分開,單個空格和多個空格均可以

  • {% if  st|fun1  %}
     <p>happy</p>
     {% endif %} 做爲條件語句不用加兩個花括號

三:中間件

1. 什麼是中間件

官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局範圍內改變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以後就再也不註釋這個中間件了。

2. 自定義中間件

中間件能夠定義五個方法,分別是:(主要的是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對象,則直接將該對象返回給用戶。

自定義一箇中間件示例
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
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參數,會發現它們是同一個對象。

由此總結一下:

  1. 中間件的process_request方法是在執行視圖函數以前執行的。
  2. 當配置多箇中間件時,會按照MIDDLEWARE中的註冊順序,也就是列表的索引值,從前到後依次執行的。
  3. 不一樣中間件之間傳遞的request都是同一個對象

多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的process_request方法最後一個執行,它的process_response方法是最早執行。

process_response

它有兩個參數,一個是request,一個是response,request就是上述例子中同樣的對象,response是視圖函數返回的HttpResponse對象。該方法的返回值也必須是HttpResponse對象。

給上述的M1和M2加上process_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_response方法是在視圖函數以後執行的,而且順序是MD1比MD2先執行。(此時settings.py中 MD2比MD1先註冊)

多箇中間件中的process_response方法是按照MIDDLEWARE中的註冊順序倒序執行的,也就是說第一個中間件的process_request方法首先執行,而它的process_response方法最後執行,最後一箇中間件的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方法並將應用到該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方法來處理異常。它的執行順序也是按照中間件註冊順序的倒序執行。

給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(用的比較少)

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_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方法開始倒序執行。

ZOZWDJ.png

中間件版登陸驗證

中間件版的登陸驗證須要依靠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請求流程圖

四:MVC和MTV

MVC模型

MVC模式(Model-View-Controller)是軟件工程中的一種軟件架構模式。
MVC把軟件系統分爲三個基本部分:模型(Model)、視圖(View)和控制器(Controller)。

MVC的目的是實現一種動態的程序設計,便於後續對程序的修改和擴展簡化,而且使程序某一部分的重複利用成爲可能。

除此以外,此模式經過對複雜度的簡化,使程序結構更加直觀。

  • 模型Model – 處理全部的數據庫邏輯。模型提供了鏈接和操做數據庫的抽象層。
  • 控制器Controller - 管理全部的業務邏輯處理,包括負責響應用戶請求、準備數據,以及決定如何展現數據等。
  • 視圖View – 負責渲染數據,經過HTML方式呈現給用戶。

一個典型的Web MVC流程:

  • Controller截獲用戶發出的請求;
  • Controller調用Model完成狀態的讀寫操做;
  • Controller把數據傳遞給View;
  • View渲染最終結果並呈獻給用戶。

MTV模型:

Django框架的設計模式借鑑了MVC框架的思想,也是分紅三部分,來下降各個部分之間的耦合性。

MTV框架是Django的框架,三部分爲:Model、Template(模板)和View

  • M 表明模型(Model),數據存取層,該層處理與數據相關的全部事物:如何存取、包含哪些行爲以及數據之間的關係等
  • T 表明模板(Template),即表現層。該層處理與表現相關的決定:如何在頁面和其餘類型的文檔中進行顯示
  • V 表明視圖(View),即業務邏輯層。該層包含存取模型及調取恰當模型的相關邏輯,可看做模板與模型之間的橋樑

此外,Django還有一個urls分發器,

它的做用是將一個個URL的頁面請求分發給不一樣的view處理,view再調用相應的Model和Template
Django的MTV和廣義上的MVC的對應關係:模型(Model)----models.py視圖(View)----templates控制器(Controller)-----View.py和urls.py

五:csrf

1. CSRF是什麼

跨站請求僞造(CSRF)與跨站請求腳本正好相反。跨站請求腳本的問題在於,客戶端信任服務器端發送的數據。跨站請求僞造的問題在於,服務器信任來自客戶端的數據。

2. 無CSRF時存在的隱患

跨站請求僞造是指攻擊者經過HTTP請求江數據傳送到服務器,從而盜取回話的cookie。盜取回話cookie以後,攻擊者不只能夠獲取用戶的信息,還能夠修改該cookie關聯的帳戶信息。

3. Form提交(CSRF)

那麼在Django中CSRF驗證大致是一個什麼樣的原理呢?下面經過一個小例子來簡單說明一下:
咱們把Django中CSRF中間件開啓(在settings.py中)

'django.middleware.csrf.CsrfViewMiddleware'

在app01的views.py中寫一個簡單的後臺

from django.shortcuts import render,redirect,HttpResponse
 
# Create your views here.
def login(request):
    if request.method == "GET":
        return render(request,'login.html')
    elif request.method == "POST":
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        if user == 'root' and pwd == "123123":
            #生成隨機字符串
            #寫到用戶瀏覽器cookie
            #保存在服務端session中
            #在隨機字符串對應的字典中設置相關內容
            request.session['username'] = user
            request.session['islogin'] = True
            if request.POST.get('rmb',None) == '1':
                #認爲設置超時時間
                request.session.set_expiry(10)
            return redirect('/index/')
        else:
            return render(request,'login.html')
 
def index(request):
    #獲取當前隨機字符串
    #根據隨機字符串獲取對應的信息
    if request.session.get('islogin', None):
        return render(request,'index.html',{'username':request.session['username']})
    else:
        return HttpResponse('please login ')
 
def logout(request):
    del request.session['username']
    request.session.clear()
    return redirect('/login/')

在templates中寫一個簡單的登錄建立兩個文件(login.html,index.html)登錄成功跳轉index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登陸
        <input type="submit" value="提交" />
    </form>
</body>
</html>

這是瀏覽器中的樣式 :

那麼若是這個時候,咱們點擊登錄提交,django會由於沒法經過csrf驗證返回一個403:

而csrf驗證實際上是對http請求中一段隨機字符串的驗證,那麼這段隨機字符串從何而來呢?這個時候咱們嘗試把login.html作一個修改添加一句 {% csrf_token %}:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登陸
        <input type="submit" value="提交" />
    </form>
</body>
</html>

這個時候咱們再經過瀏覽器元素審查,就會發現一段新的代碼:

Django在html中建立一個基於input框value值的隨機字符串 ,這個時候咱們再次輸入後臺設置的帳號密碼,進行提交,會發現提交成功,進入了咱們的index.html界面當中:

這就是csrf的基本原理,若是沒有這樣一段隨機字符串作驗證,咱們只要在另外一個站點,寫一個表單,提交到這個地址下,是同樣能夠發送數據的,這樣就形成了極大的安全隱患。而咱們新添加的csrf_token就是在咱們本身的站點中,設置的隱藏參數,用來進行csrf驗證。

4. Ajax提交 (CSRF)

上面是一個基於form表單提交的小例子,csrf中間件要驗證的隨機字符串存放在了form表單當中,發送到了後臺,那麼若是咱們使用的是ajax異步請求,是否是也要傳送這樣一個相似的字符串呢?答案是確定的,但這個字符串又該來自哪裏?其實它來自cookie,在上面的那個小例子當中,咱們打開F12元素審查,會發現,在cookie中也存放這樣一個csrftoken:

因此下面呢咱們就再聊一下ajax請求中如何進行。

首先咱們引入兩個js文件放在工程項目的static當中,這兩個文件是jquery的庫文件,方便咱們進行請求操做:

而後咱們把以前的login.html作一個修改:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登陸
        <input type="submit" value="提交" />
        <input id="btn" type="button" value="按鈕">
    </form>
 
    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        $(function () {
            $('#btn').click(function () {
                $.ajax({
                    url:'/login/',
                    type:"POST",
                    data:{'username':'root','pwd':'123123'},
                    success:function (arg) {
                        
                    }
                })
            })
        })
    </script>
</body>
</html>

這個時候咱們打開界面,點擊按鈕,會發現http請求發送403錯誤,很明顯咱們直接發送請求是不合適的,並無帶隨機字符串過去:

因此,咱們應該先從cookie中獲取到這個隨機字符串,這個隨機字符串的名字,咱們能夠經過以前的驗證得出是「csrftoken」:

var csrftoken = $.cookie('csrftoken');

這樣變量csrftoken取到的就是咱們的隨機字符串;可是若是後臺想要去接收這個隨機字符串,也應該須要一個key,那這個key是什麼?咱們能夠經過查找配置文件,經過控制檯輸出的方式驗證這個key:

from django.conf import settings
print(settings.CSRF_HEADER_NAME)

最後輸出的是:

HTTP_X_CSRFTOKEN

但這裏須要注意的是,HTTP_X_CSRFTOKEN並非請求頭中發送給django真正拿到的字段名,前端發過去真正的字段名是:

X-CSRFtoken

那爲何從Django的控制檯輸出會獲得HTTP_X_CSRFTOKEN呢?其實咱們前端的請求頭X-CSRFtoken發送到後臺以後,django會作一個名字處理,在原來的字段名前家一個HTTP_,而且將原來的小寫字符變成大寫的,「-」會處理成下劃線「_」,因此會有這兩個字段的不同。但本質上他們指向的都是同一個字符串。知道這一點以後,那麼咱們前端就能夠真正地發起含有CSRF請求頭的數據請求了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登陸
        <input type="submit" value="提交" />
        <input id="btn" type="button" value="按鈕">
    </form>
 
    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        var csrftoken = $.cookie('csrftoken');
        $(function () {
            $('#btn').click(function () {
                $.ajax({
                    url:'/login/',
                    type:"POST",
                    data:{'username':'root','pwd':'123123'},
                    header:{'X-CSRFtoken':csrftoken},
                    success:function (arg) {
                        
                    }
                })
            })
        })
    </script>
</body>
</html>

在頁面中點擊按鈕以後,會發現請求成功!

那麼這個時候有人會問,難道全部的ajax請求,都須要這樣獲取一次寫進去嗎,會不會很麻煩。針對這一點,jquery的ajax請求中爲咱們封裝了一個方法:ajaxSetup,它能夠爲咱們全部的ajax請求作一個集體配置,因此咱們能夠進行以下改造,這樣無論你的ajax請求有多少,均可以很方便地進行csrf驗證了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="POST">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登陸
        <input type="submit" value="提交" />
        <input id="btn" type="button" value="按鈕">
    </form>
 
    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        var csrftoken = $.cookie('csrftoken');
        
        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });
 
        $(function () {
            $('#btn').click(function () {
                $.ajax({
                    url:'/login/',
                    type:"POST",
                    data:{'username':'root','pwd':'123123'},
                    success:function (arg) {
                        
                    }
                })
            })
        });
    </script>
</body>
</html>

5. 裝飾器配置

在講完基本的csrf驗證操做以後,咱們還有一個可說的地方。在平時的產品需求當中,並不必定全部的接口驗證都須要進行csrf驗證,而咱們以前採用的是在settings.py中間件配置進行全局配置,那麼若是遇到了不須要開啓csrf的時候該怎麼辦呢?

from django.views.decorators.csrf import csrf_exempt,csrf_protect
 
@csrf_protect
def index(request):
    .....
 
@csrf_exempt
def login(request):

@csrf_protect 是 開啓csrf驗證的裝飾器,@csrf_exempt是關閉csrf驗證的裝飾器。

6. 先後端分離項目手動將csrftoken寫入cookie的方法

手動設置,在view 中添加(常常失效,建議採用2,3,4種方法,親測有效)

request.META["CSRF_COOKIE_USED"] = True

手動調用 csrf 中的 get_token(request) 或 rotate_token(request) 方法。

from django.middleware.csrf import get_token ,rotate_token
 
def server(request):
 
    # get_token(request)       // 二者選一
    # rotate_token(request)   // 此方法每次設置新的cookies
 
    return render(request, ‘server.html‘)

在HTML模板中添加 {% csrf_token %}

在須要設置cookie的視圖上加裝飾器 ensure_csrf_cookie()

from django.views.decorators.csrf import ensure_csrf_cookie
 
@ensure_csrf_cookie
def server(request):
 
    return render(request,'server.html')
相關文章
相關標籤/搜索