zoukankan      html  css  js  c++  java
  • 【2020Python修炼记】web框架之Django 中间件

    【目录】

    一、Django 中间件

      1、中间件的介绍

      2、自定义中间件

    二、csrf跨站请求伪造

      1、为何要使用-csrf跨站请求伪造

      2、如何符合校验

      3、scrf 相关装饰器

    三、补充知识-模块 importlib

    四、基于Django 中间件的一个重要编程思想

    一、Django 中间件

      1、中间件的介绍

    #【什么是 Django 中间件】

    Django 中间件是Django的门户——请求 与 响应 的传输,都需要经过 中间件。

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

    # 【组成】

    Django 自带七个 中间件( SecurityMiddleware / SessionMiddleware / CommonMiddleware /  CsrfViewMiddleware

    AuthenticationMiddleware /  MessageMiddleware / XFrameOptionsMiddleware ),

    每个中间件 都有各自的功能;且支持程序员 自定义中间件。

    在 Django 框架的settings文件里,可以看到中间件的组成:

    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)配置文件里的七个中间件,都是 字符串格式的模块路径(由 模块路径+模块名 组成)

    例如 
    'django.middleware.security.SecurityMiddleware' ——
    
    from django.middleware.security import SecurityMiddleware

    (2)源码

    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
            
            process_response
        2.了解即可
            process_view
                
            process_template_response
            
            process_exception
    """

     

    # 【怎么使用】

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

    • 全局用户身份校验

    • 全局用户权限校验(补充)

    • 全局访问频率校验

    • ...

      2、自定义中间件

     【自定义中间件的步骤】

    (1)【创建文件夹】在项目名或者应用名下创建一个任意名称的文件夹

    (2)【创建python文件】在该文件夹内创建一个任意名称的py文件

    (3)【书写类方法】在该py文件内需要书写 类,

    (这个类必须继承MiddlewareMixin — 因此 需要通过中间件源码找到 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',
    ]


    # 在应用下 自定义中间件,写路径时会有提示(如果在项目目录下直接自定义中间件,则没有提示)
    # 在应用下 自定义中间件 路径格式: 应用名.文件夹名.python文件名.自定义类名

     

    【中间件分类(必须掌握和了解即可)-自定义举例】

    (1)【必须掌握】

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

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

    如果在第一个process_request 方法就已经返回了HttpResponse对象,那么响应走的时候,不会经过所有的中间件里面的process_response,

    而只会直接走同级别的process_reponse返回

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

    from django.utils.deprecation import MiddlewareMixin
    
    
    class md(MiddlewareMixin):    # 定义类 (继承 MiddlewareMixin)
        def process_request(self,requwest):    # 请求的方法,需自带一个request形参
            print("第一个中间件的 process_request ")
    
        def process_response(self,request,response):   # 响应的方法,自带两个形参 request,response
            print("第一个中间件的 process_response ")
            return response  # 需要返回response ,否则会报错
    自定义中间件的类以及中间件方法

     

    (2)【了解即可】

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

    process_template_response
    返回的HttpResponse对象有render属性的时候才会触发
    顺序是按照配置文件中注册了的中间件从下往上依次经过

    process_exception
    当视图函数中出现异常的情况下触发
    顺序是按照配置文件中注册了的中间件从下往上依次经过

     

    二、csrf跨站请求伪造

    1、为何要使用-csrf跨站请求伪造

    """

    【csrf跨站请求伪造-举例】

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

    大学英语四六级
    考之前需要学生自己网站登陆缴费,明明交了费,却没有真正报名成功

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

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

      2、如何符合校验 —正经的网站 代码里添加 {% csrf_token %}

    两种校验方法:
    #
    form表单如何符合校验 <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> # ajax如何符合校验 // 第一种 利用标签查找获取页面上的随机字符串 {#data:{"username":'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},#} // 第二种 利用模版语法提供的快捷书写 {#data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},#} // 第三种 通用方式直接拷贝js代码(见下方) 并应用到自己的html页面上即可(静态文件配置) data:{"username":'jason'}

    <!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="/csrf/" 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>
    {% load static %}
    <script src="{% static 'js/mysetup.js' %}"></script>
    <script>
        $('#d1').click(function () {
            $.ajax({
                url:'',
                type:'post',
                // 第一种 利用标签查找获取页面上的随机字符串
                {#data:{"username":'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},#}
                // 第二种 利用模版语法提供的快捷书写
                {#data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},#}
                // 第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可
                data:{"username":'jason'},
                success:function () {
    
                }
            })
        })
    </script>
    </body>
    </html>
    transfer.html
     
    在项目文件夹内,新建静态文件夹 static,里面再新建js 文件夹,将需要导入的js文件保存在里面
    此处保存为 mysetup.js (官方文档里 已经为你写好,直接拷贝即可
    Cross Site Request Forgery protection | Django documentation | Django )

    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); } } });
    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);
        }
      }
    });
    mysetup.js

      3、csrf 相关装饰器

    """
    1.网站整体都不校验csrf的前提下,就单单几个视图函数需要校验
    2.网站整体都校验csrf 的前提下,就单单几个视图函数不校验
    """
    from django.views.decorators.csrf import csrf_protect,csrf_exempt
    from django.utils.decorators import method_decorator
    """
    csrf_protect  需要校验
        针对csrf_protect,符合我们之前所学的装饰器的三种玩法
    csrf_exempt   忽视校验
        针对csrf_exempt,只能给dispatch方法加才有效
    """
    # @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')
    
    
    
    from django.views import View
    
    # @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')

     

    三、补充知识-模块 importlib — importlib.import_module(res)

    功能:使用字符串路径 获取外部文件的数据

      例如 :myfile文件夹里,a.py 获取 b.py 里的数据 

    【a.py的代码】

    # 模块:importlib
    import importlib
    res = 'myfile.b'   变量名 = '文件夹名.文件名'
    ret = importlib.import_module(res)  # from myfile import b
    # 该方法的最小单位只能到py文件名
    print(ret)  # 获取 模块对象 b.py对象

     

    四、基于Django 中间件的一个重要编程思想 

    【核心代码-notify 文件夹里的  __init__.py】

    import settings
    import importlib
    
    
    def send_all(content):
        for path_str in settings.NOTIFY_LIST:  #'notify.email.Email'
            module_path,class_name = path_str.rsplit('.',maxsplit=1)
            # module_path = 'notify.email'  class_name = 'Email'
            # 1 利用字符串导入模块
            module = importlib.import_module(module_path)  # from notify import email
            # 2 利用反射获取类名
            cls = getattr(module,class_name)  # Email、QQ、Wechat
            # 3 生成类的对象
            obj = cls()
            # 4 利用鸭子类型直接调用send方法
            obj.send(content)

    【编程思想-概述】

    【简易模拟-消息通知】
    
    【版本一 —— 函数低级版】
    1、新建一个总的功能文件 notify.py 里面包含 微信通知方法,QQ通知方法,邮箱通知方法
    
    2、新建启动文件 start.py,用于启动功能文件
    (若需要使用通知功能,只需要在文件里导入该启动文件即可)
    但是若需要删除其中一个功能,找到启动文件,注释相应功能代码即可。
    
    
    【版本二 —— 面向对象升级版】
    
    1、在项目目录下新建一个文件夹 notify,根据功能的不同,分别创建python文件
    2、在各个功能python文件里,书写功能类(继承object 类)
    使用“鸭子类型”书写类,实现功能的重用
    
    class Wechat(object):
        def __init__(self):
            pass  # 发送微信 需要做的前期准备工作(例如 调用接口 ,功能实现等)
    
        def send(self,content):
            print('微信通知:%s'%content)
    
    
    class qq(object):
        def __init__(self):
            pass  # 发送qq 需要做的前期准备工作(例如 调用接口 ,功能实现等)
    
        def send(self,content):
            print('qq通知:%s'%content)
    
    
    
    class Email(object):
        def __init__(self):
            pass  # 发送邮箱 需要做的前期准备工作(例如 调用接口 ,功能实现等)
    
        def send(self,content):
            print('邮箱通知:%s'%content)
    
    
    3、在项目目录下新建一个启动文件 start.py
    
    4、文件夹notify,就相当于一个包,因此需要添加 __init__.py 文件
    
    5、在项目目录文件夹下,新建 settings.py 文件
    并在里面增加 功能类的字符串路径(模仿 Django 中间件的配置信息)
    
    6、完善 __init__.py 的功能代码
    
    import settings
    import importlib
    
    
    def send_all(content):
        for path_str in settings.NOTIFY_LIST:   # 即 settings里的字符串路径 例如: 'notify.email.Email',
            module_path, class_name = path_str.rsplit('.', maxsplit=1)
            # 从右边开始切,以右边第一个点号为切割点,只切一次,获取字符串路径里的 路径和方法类名
            # module_path = 'notify.email', class_name = 'Email'
            # 1 利用字符串导入模块
            module = importlib.import_module(module_path)   # 相当于 from notify import email
            # 2 利用 反射 获取类名
            cls = getattr(module, class_name)
            # 3 生成类的对象
            obj = cls()
            # 4 利用鸭子类型 直接调用 send 方法
            obj.send(content)
    
    7、【功能扩展】—— 通过 配置文件 就可以方便地操控功能
    # 需要关闭某个功能,只需要将配置文件里的功能字符串路径注释调即可
    
    #  需要增加某个功能,直接添加相关功能的python功能文件,
    #  再去配置文件settings 里注册,增加功能字符串路径
    编程流程

    【版本二--源码】 

    class Email(object):
        def __init__(self):
            pass  # 发送邮箱 需要做的前期准备工作(例如 调用接口 ,功能实现等)
    
        def send(self,content):
            print('邮箱通知:%s'%content)
    email.py
    class QQ(object):
        def __init__(self):
            pass  # 发送qq 需要做的前期准备工作(例如 调用接口 ,功能实现等)
    
        def send(self,content):
            print('qq通知:%s'%content)
    qq.py
    class Wechat(object):
        def __init__(self):
            pass  # 发送微信 需要做的前期准备工作(例如 调用接口 ,功能实现等)
    
        def send(self,content):
            print('微信通知:%s'%content)
    wechat.py
    import settings
    import importlib
    
    
    def send_all(content):
        for path_str in settings.NOTIFY_LIST:   # 即 settings里的字符串路径 例如: 'notify.email.Email',
            module_path, class_name = path_str.rsplit('.', maxsplit=1)
            # 从右边开始切,以右边第一个点号为切割点,只切一次,获取字符串路径里的 路径和方法类名
            # module_path = 'notify.email', class_name = 'Email'
            # 1 利用字符串导入模块
            module = importlib.import_module(module_path)   # 相当于 from notify import email
            # 2 利用 反射 获取类名
            cls = getattr(module, class_name)
            # 3 生成类的对象
            obj = cls()
            # 4 利用鸭子类型 直接调用 send 方法
            obj.send(content)
    __init__.py
    NOTIFY_LIST = [
        'notify.email.Email',
        'notify.qq.QQ',
        'notify.wechat.Wechat'
    ]
    
    # 需要关闭某个功能,只需要将配置文件里的功能字符串路径注释调即可
    
    #  需要增加某个功能,直接添加相关功能的python功能文件,
    #  再去配置文件settings 里注册,增加功能字符串路径
    settings.py
    import notify
    
    notify.send_all('端午再见啦~米粒')
    start.py
  • 相关阅读:
    Git 分支创建,合并, 分支切换, 分支拉取,提交
    Win7 Nodejs 安装
    .ssh github
    xxxx.IronManager was loaded by com.taobao.pandora.boot.loader.XxxxClassLoader@xxx,it should be loaded by Pandora Container...与摒弃引进别的项目的一些冲突包
    推荐一波微软家的浏览器:EDGE
    谷歌浏览器新功能 Copy Declaration
    微信支付回调数据接收不完整解决方案
    开源物联网框架EasyIot(适用于快递柜&售货机)
    开源物联网框架EasyIot场景落地(适用于快递柜、储物柜)
    海康摄像头音频方案(播放音频文件+语音对讲+语音转发)支持window/Linuxjava版本
  • 原文地址:https://www.cnblogs.com/bigorangecc/p/13063796.html
Copyright © 2011-2022 走看看