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  # 添加装饰器的模块
posted @ 2020-09-19 20:39  云崖先生  阅读(358)  评论(1编辑  收藏  举报