• 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  # 添加装饰器的模块
    
  • 相关阅读:
    BNUOJ 12756 Social Holidaying(二分匹配)
    HDU 1114 Piggy-Bank(完全背包)
    HDU 2844 Coins (多重背包)
    HDU 2602 Bone Collector(01背包)
    HDU 1171 Big Event in HDU(01背包)
    HDU 2571 命运 (入门dp)
    HDU 1069 Monkey and Banana(最长递减子序列)
    HDU 1160 FatMouse's Speed (最长上升子序列)
    HDU 2594 KMP
    POJ 3783 Balls --扔鸡蛋问题 经典DP
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13711629.html
Copyright © 2020-2023  润新知