简单介绍

django自带七个中间件,每个中间件都有各自对应的功能,并且django还支持程序员自定义中间件

在用django开发项目的项目的时候,只要是涉及到全局相关的功能都可以使用中间件方便的完成

# django中间件是django的门户
1.请求来的时候需要先经过中间件才能到达真正的django后端
2.响应走的时候最后也需要经过中间件才能发送出去

django自带七个中间件

Django中间件:默认自带七个如下

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',
]

解释,这其中的字符串元素是路径名,利用字符串导模块(利用importlib模块)

from django.middleware.security import SecurityMiddleware

查看几个中间件源码

class SessionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
        request.session = self.SessionStore(session_key)
    def process_response(self, request, response):
        return response

class CsrfViewMiddleware(MiddlewareMixin):
      def process_request(self, request):
        csrf_token = self._get_token(request)
        if csrf_token is not None:
            # Use same token next time.
            request.META['CSRF_COOKIE'] = csrf_token
    def process_view(self, request, callback, callback_args, callback_kwargs):
        return self._accept(request)

    def process_response(self, request, response):
        return response

class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        request.user = SimpleLazyObject(lambda: get_user(request))

自定义中间件

django支持程序员自定义中间件并且暴露给程序员五个可以自定义的方法

# 1.必须掌握
    process_request(self,request)

    process_response(self,request,response)
# 2.了解即可
    process_view(self,view_name,*args,**kwargs)

    process_template_response(self,request,response)

    process_exception(self,request,exception)
# 1.在项目名或者应用名下创建一个任意名称的文件夹

# 2.在该文件夹内创建一个任意名称的py文件

# 3.在该py文件内需要书写类(这个类必须继承MiddlewareMixin)
from django.utils.deprecation import MiddlewareMixin
然后在这个类里面就可以自定义五个方法了
(这五个方法并不是全部都需要书写,用几个写几个)

# 4.需要将类的路径以字符串的形式注册到配置文件中才能生效
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',
'你自己写的中间件的路径1',
'你自己写的中间件的路径2',
'你自己写的中间件的路径3',
]

案例

1、在应用名下创建一个名为mymiddleware的文件夹

2、在该文件夹内创建一个mydd.py文件

3.在该py文件内需要书写类(这个类必须继承MiddlewareMixin)

mydd.py

from django.utils.deprecation import MiddlewareMixin

class MyMiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print("我是第一个自定义中间件的process_request方法")

    def process_response(self,request,response):
        """
        :param request:
        :param response: 就是django后端返回给浏览器的内容
        :return:必须返回response
        """
        print("我是第一个自定义中间件的process_response方法")
        return response

class MyMiddleware2(MiddlewareMixin):
    def process_request(self,request):
        print("我是第二个自定义中间件的request方法")

    def process_response(self,request,response):
        """
        :param request:
        :param response: 就是django后端返回给浏览器的内容
        :return:response
        """
        print("我是第二个自定义中间件的process_response方法")
        return response

4、需要将类的路径以字符串的形式注册到配置文件中才能生效

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.mydd.MyMiddleware1',
    'app01.mymiddleware.mydd.MyMiddleware2',
]

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.
def index(request):
    print("我是视图函数index")
    return HttpResponse("index")

浏览器访问

查看后端输出

总结

"""
1.必须掌握
        process_request 
            1.请求来的时候需要经过每一个中间件里面的process_request方法
            结果的顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
            2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
            3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
            而是直接原路返回(就相当于校验失败不允许访问...)
            process_request方法就是用来做全局相关的所有限制功能

        process_response
            1.响应走的时候需要结果每一个中间件里面的process_response方法
            该方法有两个额外的参数request,response
            2.该方法必须返回一个HttpResponse对象
                1.默认返回的就是形参response
                  return response
                2.你也可以自己返回自己的(狸猫换太子)
                  return HttpResponse("Hello world!")
            3.顺序是按照配置文件中注册了的中间件从下往上依次经过
                如果你没有定义的话 直接跳过执行下一个

        研究如果在第一个自定义中间件process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况
        是其他情况
            结论:如果第一个自定义中间件process_request方法就已经返回了HttpResponse对象,那么响应走的时候会直接走同级别的的第一个自定义中间件中的process_reponse返回

        flask框架也有一个中间件但是它的规律
            只要返回数据了就必须经过所有中间件里面的类似于process_reponse方法


2.了解即可
        process_view
            路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该放法
            顺序是按照配置文件中注册的中间件从上往下的顺序依次执行

        process_template_response
            返回的HttpResponse对象有render属性的时候才会触发
            顺序是按照配置文件中注册了的中间件从下往上依次经过
            # mydd.py
            def process_template_response(self,request,response):
                print('我是第一个自定义中间件里面的process_template_response')
                return response
            # views.py
                def index(request):
                    print('我是视图函数index')
                    obj = HttpResponse('index')

                    def render():
                        print('内部的render')
                        return HttpResponse("O98K")
                    obj.render = render
                    return obj
        process_exception
            当视图函数中出现异常的情况下触发
            顺序是按照配置文件中注册了的中间件从下往上依次经过
"""

完整示例:

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class MyMiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个自定义中间件里面的process_request方法')
        # return HttpResponse('baby!')

    def process_response(self,request,response):
        """
        :param request:
        :param response: 就是django后端返回给浏览器的内容
        :return:
        """
        print('我是第一个自定义中间件里面的process_response方法')
        return response

    def process_view(self,request,view_name,*args,**kwargs):
        print(view_name,args,kwargs)
        print('我是第一个自定义中间件里面的process_view')

    def process_template_response(self,request,response):
        print('我是第一个自定义中间件里面的process_template_response')
        return response

    def process_exception(self,request,exception):
        print('我是第一个中间件里面的process_exception')
        print(exception)

csrf跨站请求伪造

"""
钓鱼网站
    我搭建一个跟正规网站一模一样的界面(中国银行)
    用户不小心进入到了我们的网站,用户给某个人打钱
    打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了
    但是唯一不同的时候打钱的账户不适用户想要打的账户变成了一个莫名其妙的账户

大学英语四六级
    考之前需要学生自己网站登陆缴费

内部本质
    我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框
    然后我们在内部隐藏一个已经写好name和value的input框

如何规避上述问题
    csrf跨站请求伪造校验
        网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识
        当这个页面朝后端发送post请求的时候 我的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden),如果成功则正常执行    
"""

钓鱼网站案例:

正规网站:

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 转账
    url(r'^transfer/', views.transfer),

]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.
def transfer(request):
    if request.method == "POST":
        username = request.POST.get("username")
        target_user = request.POST.get("target_user")
        money = request.POST.get("money")
        print("%s 给 %s 转了%s元" %(username,target_user,money))
    return render(request,"transfer.html")

transfer.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<h1>我是正儿八经的网站</h1>
<form action="" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>target_user:<input type="text" name="target_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>

</body>
</html>

钓鱼网站:

urls.py

from django.conf.urls import url
from django.contrib import admin
from app01 import  views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^transfer/', views.transfer),
]

views.py

from django.shortcuts import render

# Create your views here.
def transfer(request):

    return render(request,"transfer.html")

transfer.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<h1>我是钓鱼网站</h1>
<form action="http://127.0.0.1:8000/transfer/" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>target_user:<input type="text"></p>
    <input type="hidden" name="target_user" value="jason">
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>

</body>
</html>

如何符合csrf校验

form表单如何符合校验,在form表单中任意位置添加{% csrf_token %}
<form action="" method="post">
    {% csrf_token %}
    <p>username:<input type="text" name="username"></p>
    <p>target_user:<input type="text" name="target_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>

此时,上面钓鱼网站例子中钓鱼网站提交到正规的网站时,就会403

ajax如何符合校验

  • 方式一:利用标签查找获取页面上的随机字符串,data中添加一个键csrfmiddlewaretoken(名字必须叫这个),值为拿到的随机字符串
在form表单中任意位置添加{% csrf_token %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- Bootstrap3 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- Bootstrap3 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- font-awesome.min.css图标库4.7版本 -->
    <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<h1>我是正儿八经的网站</h1>
<form action="" method="post">
    {% csrf_token %}
    <p>username:<input type="text" name="username"></p>
    <p>target_user:<input type="text" name="target_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>
<button id="d1">ajax请求</button>

<script>
    $("#d1").click(function () {
        $.ajax({
            url:"",
            type:"post",
            // 方式一:利用标签查找获取页面上的随机字符串
            data:{"username":'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
            success:function () {

            }
        })
    })
</script>
</body>
</html>
  • 方式二:利用模版语法提供的快捷书写
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<h1>我是正儿八经的网站</h1>
<form action="" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>target_user:<input type="text" name="target_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>

<button id="d1">ajax请求</button>

<script>
    $("#d1").click(function () {
        $.ajax({
            url:"",
            type:"post",
            // 方式二:利用模版语法提供的快捷书写
            data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},
            success:function () {

            }
        })
    })
</script>
</body>
</html>
  • 方式三:通用方式直接拷贝js代码并应用到自己的html页面上即可

配置静态文件

mysteup.js

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('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);
    }
  }
});

方式三:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<h1>我是正儿八经的网站</h1>
<form action="" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>target_user:<input type="text" name="target_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>
<button id="d1">ajax请求</button>

<!--方式三 动态引入静态文件-->
{% load static %}
<script src="{% static "js/mysteup.js" %}"></script>
<script>
    $("#d1").click(function () {
        $.ajax({
            url:"",
            type:"post",
            data:{"username":'jason'},
            success:function () {
            }
        })
    })
</script>
</body>
</html>

csrf相关装饰器

"""
1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验
"""

两个装饰器导入:

from django.views.decorators.csrf import csrf_protect,csrf_exempt

FBV上使用:

from django.views.decorators.csrf import csrf_protect,csrf_exempt


# @csrf_exempt
# @csrf_protect
def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_user = request.POST.get('target_user')
        money = request.POST.get('money')
        print('%s给%s转了%s元'%(username,target_user,money))
    return render(request,'transfer.html')

CBV上使用

from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
"""
csrf_protect  需要校验
    针对csrf_protect符合我们之前所学的装饰器的三种玩法
csrf_exempt   忽视校验
    针对csrf_exempt只能给dispatch方法加才有效
"""

# @method_decorator(csrf_protect,name='post')  # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):
    # @method_decorator(csrf_protect)  # 针对csrf_protect 第三种方式可以
    # @method_decorator(csrf_exempt)  # 针对csrf_exempt 第三种方式可以
    def dispatch(self, request, *args, **kwargs):
        return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)

    def get(self,request):
        return HttpResponse('get')

    # @method_decorator(csrf_protect)  # 针对csrf_protect 第一种方式可以
    # @method_decorator(csrf_exempt)  # 针对csrf_exempt 第一种方式不可以
    def post(self,request):
        return HttpResponse('post')

csrf_exempt还可以通过在urls.py给CBV类装饰

from django.conf.urls import url
from django.views.decorators.csrf import csrf_exempt
import views

urlpatterns = [
  url(r'^myview/$', csrf_exempt(views.MyView.as_view()), name='myview'),
]

1.网站整体都不校验csrf,就单单几个视图函数需要校验

# 'django.middleware.csrf.CsrfViewMiddleware',     # 配置文件禁用scrf
  • 第一种csrf_protect
from django.shortcuts import render, HttpResponse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views import View


class MyCsrfToken(View):
    def get(self, request):
        return HttpResponse('get')

    @method_decorator(csrf_protect)  # 针对csrf_protect 第一种方式
    def post(self, request):
        return HttpResponse('post')
  • 第二种
from django.shortcuts import render, HttpResponse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views import View


@method_decorator(csrf_protect, name='post')  # 针对csrf_protect 第二种方式
class MyCsrfToken(View):

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')
  • 第三种
from django.shortcuts import render, HttpResponse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views import View


class MyCsrfToken(View):
    @method_decorator(csrf_protect)     # 针对csrf_protect 第三种方式
    def dispatch(self, request, *args, **kwargs):
        return super(MyCsrfToken, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')

2.网站整体都校验csrf,就单单几个视图函数不校验

'django.middleware.csrf.CsrfViewMiddleware',        # 配置文件开启scrf
  • 第一种(不行)
from django.shortcuts import render, HttpResponse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views import View


class MyCsrfToken(View):
    def get(self, request):
        return HttpResponse('get')

    @method_decorator(csrf_exempt)      # 针对csrf_exempt 第一种方式不可以
    def post(self, request):
        return HttpResponse('post')
  • 第二种(不行)
from django.shortcuts import render, HttpResponse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views import View


@method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第二种方式不可以
class MyCsrfToken(View):
    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')
  • 第三种(可以)
from django.shortcuts import render, HttpResponse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views import View


class MyCsrfToken(View):
    @method_decorator(csrf_exempt)  # 针对csrf_exempt 第三种方式可以
    def dispatch(self, request, *args, **kwargs):
        return super(MyCsrfToken, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')

补充知识点

importlib模块

目录:

b.py

name = "jason"

a.py

import importlib

res = "myfile.b"
ret = importlib.import_module(res)  # 等价于 from myfile import b
# 该方法最小只能到py文件名
print(ret) # <module 'myfile.b' from '/Users/v_jiangchun/PycharmProjects/pythonProject/myfiletest/myfile/b.py'>

根据中间件的settings推导出重要思想

在settings.py文件中配置功能模块文件夹字符串,想用的时候打开,不想用直接注释掉(全局就不可以用这个功能)

案例发送通知(通过QQ、微信。。。)

正常思路:比较麻烦,如果像去掉一个功能很费劲

notify.py

def wechat(content):
    print("微信通知:%s" %content)

def qq(content):
    print("QQ通知:%s" %content)

def email(content):
    print("邮箱通知:%s" %content)

start.py

from notify import *

def send_all(content):
    wechat(content)
    qq(content)
    email(content)

if __name__ == '__main__':
    send_all("这是个重要思想")

使用中间件中的通过字符串导入模块方法的思想

目录结构:

test
    │
    ├─notify
    │  ├─__init__.py
    │  │
    │  ├─email.py
    │  │
    │  ├─qq.py
    │  │
    │  └─wechat.py
    │
    ├─settings.py
    │
    └─start.py
  • email
class Email(object):
    def __init__(self):
        pass
    def send(self,content):
        print("邮箱通知:%s" %content)
  • qq
class QQ(object):
    def __init__(self):
        pass
    def send(self,content):
        print("QQ通知:%s" %content)
  • wechat
class Wechat(object):
    def __init__(self):
        pass
    def send(self,content):
        print("微信通知:%s" %content)
  • __init__.py
import settings
import importlib

def send_all(content):
    for path_str in settings.NOTIFY_LIST:  # path_str   'notify.email.Email'
        module_path,class_name = path_str.rsplit(".",1)
        # module_path = 'notify.email'
        # 利用字符串导入模块
        module = importlib.import_module(module_path)     # from notify import email
        # 利用反射获取类名
        cls = getattr(module,class_name)   # 拿到真正类方法的内存地址
        # 生成类的对象
        obj = cls()
        # 利用鸭子类型直接调用send方法
        obj.send(content)
  • settings.py
NOTIFY_LIST = [
    'notify.email.Email',
    'notify.wechat.Wechat',
    'notify.qq.QQ',
]
  • start.py
import notify

notify.send_all('这是个非非常重要的思想')
文档更新时间: 2022-03-14 16:04   作者:李延召