zoukankan      html  css  js  c++  java
  • Django 中间件

    Django中间件

       Django中间件会对所有的资源请求,所有的返回方式,所有的路由到视图的跳转、所有视图层的异常进行处理。

       在Django中,自带的有7个中间件,都具有不同的功能。

       目前而言了解下面这两个即可。

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',  # 插入session至数据表
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',  # 防止跨域请求
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    

       这里的每一个中间件其实都是一个模块,利用importlib模块使之能够作为字符串进行导入。

       并且,在这些自带的中间件中,都继承了MiddlewareMixin类。

       在该类中,提供了五个钩子方法,能够让我们对自定义中间件进行扩展。

    中间件钩子函数描述
    process_request 所有请求来时都会运行的方法
    process_view 所有路由匹配成功之后,跳转执行视图函数之前都会运行该方法
    process_exception 所有视图中有异常发生时运行的方法
    process_response 所有返回页面响应时运行的方法
    process_template_response 返回的HttpResponse对象具有render属性时才会触发该方法

    执行顺序

       在Django中,请求来时中间件的执行流程是自上而下,而进行响应时中间件的执行流程都是自下而上。

       每个自带中间件中的钩子方法都会依次运行

       image-20200917215230866

       不管你自定义多少中间件,永远都是这个流程。

       不过需要注意的是,如果你自定义了一个中间件,并且对其中的process_requset方法进行返回了HttpResponse,那么会同级进行返回。不同于flaskflask则还是会至下而上进行返回。

       image-20200917215808465

    自定义中间件

       自定义中间件做下面三步即可:

       1.任意目录下新建一个任意名称的.py文件夹

       2.在该文件下书写任意名称的类,但是一定要继承MiddlewareMixin,在该类下可以进行上面五种方法的覆写

       3.在settings.py中间件进行添加路径.类名

    from django.utils.deprecation import MiddlewareMixin
    

       如我在项目全局文件夹下新建了一个文件夹。叫CustomMiddleware,并且在里面新建了一个py文件customMid

       在该文件下,新建了一个类Mid

       那么我在注册的时候就直接添加上这个路径即可:

    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',
        'p1.CustomMiddleware.customMid.Mid',  # 新增的自定义中间件
    ]
    

    process_request

       process_request有一个参数,就是request,这个request和视图函数中的request是一样的(在交给Django后面的路由之前,对这个request对象可以进行一系列的操作)。

       返回值:默认为None,如果返回一个HttpResponse对象,则将直接进行向上返回。

       如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。

    class Mid(MiddlewareMixin):
    
        def process_request(self, request):
            print("process_request")
    

    process_response

       该方法有两个参数。

       多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。

    class Mid(MiddlewareMixin):
        def process_response(self, request, response):
            print("process_response")
    

    process_view

       该方法有四个参数,Django会在调用视图函数之前调用process_view方法。

       requestHttpRequest对象。

       view_funcDjango即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)

       view_args是将传递给视图的位置参数的列表.

       view_kwargs是将传递给视图的关键字参数的字典。 view_argsview_kwargs都不包含第一个视图参数(request)。

       它应该返回None或一个HttpResponse对象。

       如果返回NoneDjango将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。

       如果它返回一个HttpResponse对象,那么将不会执行Django的视图函数,而是直接在中间件中掉头,倒叙执行一个个process_response方法,最后返回给浏览器

    class Mid(MiddlewareMixin):
    
    	def process_view(self, request, view_func, view_args, view_kwargs):
            print("process_view")
    
    

    process_exception

       该方法两个参数,这个方法只有在视图函数中出现异常了才执行。

       一个HttpRequest对象

       一个exception是视图函数异常产生的Exception对象。

       它返回的值可以是一个None也可以是一个HttpResponse对象。

       如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。

       如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

    class Mid(MiddlewareMixin):
    
    	def process_exception(self, request, exception):
            print("process_exception")
    

    process_template_response

       它有两个参数,由于执行条件很苛刻,所以用的非常少。

       一个HttpRequest对象,一个response对象。

       并且这个responseTemplateResponse对象(由视图函数或者中间件产生)。

       process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。

    def index(request):  # 必须有render属性/方法,该中间件钩子方法才会执行
        def render():
            return HttpResponse("OK")
        rep = HttpResponse("OK")
        rep.render = render
        return rep
    
    class Mid(MiddlewareMixin):
       def process_template_response(self, request, response):  # 换而言之,response必须能点出render才行
            print("process_template_response")
            return response
    

    执行流程

    process_request

       img

    process_response

       img

    全总结

       img

       由于process_exception以及process_template_response的触发是有条件限制的,故此不再举例,记住他们的执行顺序是倒序即可。

       img

    实际应用

    session白名单

       由于所有的request请求都会走这个,所以我们可以对其进行session控制。

       维护一个集合(也可以做一个非关系型数据库,放缓存中),放上不需要session认证的url,称之为白名单。

       如果用户未进行登录就去访问不在白名单中的路径,则返回一个页面提示用户进行登录。

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import redirect
    
    class Mid(MiddlewareMixin):
        def process_request(self,request):
            whitelist = {"/","/admin/","/index/","/login/","/register/"} # 不需要登录就能访问的页面
            target_url = request.path
    
            for url in whitelist:
                if target_url in url:
                    return
                    
            if not request.session.get("login"): # 如果未有session,代表未登录,跳转到登录页面
                return redirect("/login/?next={0}".format(target_url))
    
    def login(request):
        """ 登录页面 """
        target_url = request.GET.get("next",None)
    
        if request.method == "POST":
            username = request.POST.get("username")
            password = request.POST.get("password")
    
            if username == "Yunya" and password == "123456":
                request.session["login"] = True
                request.session.set_expiry(3600)
            if not target_url:
                return redirect("/index/")  # 如果是直接点的登录页面,登陆完成后跳转到主页
            else:
                return redirect(target_url)  # 否则跳转到从其他页面过来的
    
        return render(request,"login.html",locals())
    

    访问频率限制

       某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过10次。

       如果要配合上面的白名单进行使用,这个应该注册在白名单上面。

    import time
    from django.shortcuts import redirect
    from django.shortcuts import HttpResponse
    from django.utils.deprecation import MiddlewareMixin
    
    class Mid2(MiddlewareMixin):
        # 访问IP池
        visit_ip_pool = {}
    
        def process_request(self, request):
            # 获取访问者IP
            ip = request.META.get("REMOTE_ADDR")
            # 获取访问当前时间
            visit_time = time.time()
            # 判断如果访问IP不在池中,就将访问的ip时间插入到对应ip的key值列表,如{"127.0.0.1":[时间1]}
            if ip not in Mid2.visit_ip_pool:
                Mid2.visit_ip_pool[ip] = [visit_time]
                return 
            # 然后在从池中取出时间列表
            history_time = Mid2.visit_ip_pool.get(ip)
            # 循环判断当前ip的时间列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
            while history_time and visit_time-history_time[-1] > 60:
                history_time.pop()
            # 如果访问次数小于10次就将访问的ip时间插入到对应ip的key值列表的第一位置,如{"127.0.0.1":[时间2,时间1]}
            print(history_time)
            if len(history_time) < 10:
                history_time.insert(0, visit_time)
                return None
            else:
                # 如果大于10次就禁止访问
                return HttpResponse("访问过于频繁,还需等待%s秒才能继续访问" % int(60-(visit_time-history_time[-1])))
    
    

    CSRF

    跨域伪造请求

       跨域伪造请求我举一个例子:

       有一个钓鱼网站,和银行的转账页面一模一样。

       但是唯一不同的地方在于,你在钓鱼网站上输好信息后点击提交,它并不会将对方卡号进行提交,而是将骗子卡号进行提交(隐藏的input框)。这个时候银行后端收到这一条信息,你的钱就转到骗子哪儿去了。

       image-20200917235214445

       如何解决这个问题?可以使用CSRF来防止跨域伪造请求。

       image-20200917235951799

    CSRF中间件

       在Django中,有一个中间件就是干这个事儿的,派发随机字符串,验证随机字符串。

    'django.middleware.csrf.CsrfViewMiddleware',
    

       我们打开它,并且在页面中添加上{% csrf_token %}来获取到这一随机字符串,在页面上就会显示出来。

        <form action="" method="POST">
            {% csrf_token %}
            <p><input type="text" placeholder="username" name="username"></p>
            <p><input type="text" placeholder="password" name="password"></p>
            <p><button type="submit">登录</button></p>
        </form>
    

       注意!这个标签会生成一个input框,一定要将他放在form表单中。

       并且!每次刷新页面都会生成不同的字符串。

    <input type="hidden" name="csrfmiddlewaretoken" value="yoW7bYRlhbHDcDI2KugGgHpNvjFsvZj47PNKGGXHbth2pCfITEul8NkJzN4xoUXI">
    

       那么加上这个随机字符串后,就可以提交POST请求了。

    Ajax请求

       Ajax提交的话,该怎么做?

       以下有三种办法。

       方式一

       通过获取隐藏的<input>标签中的csrfmiddlewaretoken值,放置在data中发送。

    $.ajax({
      url: "http://127.0.0.1:8000",
      type: "POST",
      data: {
        "username": "Yunya",
        "password": 123456,
        "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val()  // 使用JQuery取出csrfmiddlewaretoken的值,拼接到data中
      },
      success: function (data) {
        console.log(data);
      }
    })
    

       方式二

       请求的键永远都是csrfmiddlewaretoken,我们只要把value输入为正确的随机字符串即可。

    $.ajax({
      url: "/http://127.0.0.1:8000/",
      type: "POST",
      data: {"username": "Q1mi", "password": 123456,"csrfmiddlewaretoken":"{{csrf_token}}"},
      success: function (data) {
        console.log(data);
      }
    })
    

       方式三

       通过静态文件,为所有ajax发送请求时自动添加上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);
        }
      }
    });
    

       前端使用时记得导入这个静态文件:

    {% load static %}
    <script src={% static 'js/csrf.js' %}>
    
    $.ajax({
      url: "/http://127.0.0.1:8000/",
      type: "POST",
      headers: {"X-CSRFToken": $.cookie('csrftoken')},  // 从Cookie取csrf_token,并设置ajax请求头
      data: {"username": "Q1mi", "password": 123456},
      success: function (data) {
        console.log(data);
      }
    })
    

    视图验证

       我们可以在视图中,为某个函数单独设置需要csrf校验,或者取消单独某个函数的csrf校验。

       需要导入以下两个模块。

    from django.views.decorators.csrf import csrf_protect # 单独校验
    from django.views.decorators.csrf import csrf_exempt  # 取消校验
    

       特别注意!如果你是使用CBV,那么取消验证时只能这样设置csrf_exempt:

    @method_decorat(csrf_exempt,name="dispatch")
    class Test(View):
    	def get(self,request):
    		pass
    		
    	def post(self,request):
    		pass
    

       关于如何为CBV添加装饰器,你需要导入以下两个模块。

    from django.views import View # 使用CBV的模块,必须继承该类
    from django.utils.decorators import method_decorator  # 添加装饰器的模块
    
  • 相关阅读:
    银行卡号每隔4位插入空格
    IE6-8下自定义标签的表现
    Sql Server尝试读取或写入受保护的内存。这通常指示其他内存已损坏
    儿童编程教学scratch 3.0
    Shell 教程入门
    自定义vs2005代码段
    解决Adobe ReaderXI自动关闭问题
    WPF——给button添加背景图片
    WPF 异步加载数据
    Caliburn.Micro中的WindowManager
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13711629.html
Copyright © 2011-2022 走看看