Django中间件

中间件

中间件介绍

什么是中间件?

  • 在WSGI协议中,中间件的两端分别是Web服务器和Web应用,对于Web服务器而言,中间件充当的Web应用,对于Web应用而言,中间件充当的是Wen服务器。 因此我们可以把中间件看做一个插件,这个插件可以用来修改django全局输入和输出(在Django中就是一个中间件类),我们可以将自己想要在请求生命周期中要实现的业务逻辑放在中间件中,从而介入请求和响应的处理过程。

中间件的应用

  • 在目前接触到的项目中,中间件主要用来做用户登录认证权限认证,在Vue前后端分离的项目中,中间件还可以用来解决跨域问题(使用的是CORS配合中间件批量解决的,后面有这个东西的源码)

啥时候用中间件咧?

  • 中间件默认对所有经过WSGI之后的请求进行处理,因此,当对批量的请求做操作的时候,选择中间件, 当仅对较少的请求做处理的时候,选择装饰器

django中的中间件

django内置中间件

常见的有

  1. SessionMiddleware
    • 用来开启session支持,这个中间件在process_request和process_response中实现了对session数据的增删改查操作
  2. CsrfViewMiddleware
    • 添加跨站点请求伪造的保护,通过向POST表单添加一个隐藏的表单字段,并检查请求中是否有正确的值
  3. AuthenticationMiddleware
    • 当我们使用了django内置的认证组件(或者django restframe work)时,向每个接收到的HttpRequest对象添加user属性

django项目中csrf攻击的解决与CsrfViewMiddleware中间件

csrftoken

  • 当在配置中注册了该CsrfViewMiddleware中间件的时候,客户端发起的POST请求就必须提供一个token字符串,在CsrfViewMiddleware中间件的process_view方法中对该token数据进行验证,一旦验证失败,就会返回一个403的响应

    • 这串数据存放在在模板中使用{% csrftoken %}生成的一个隐藏的input标签中。
  • 如果在前端我们发送Ajax请求的话,还需要注意客户端发送的Content-type类型,当content-type为application/json的时候,我们需要将token数据放在请求头中,当为application/x-www-form-urlencode的时候,直接放在请求体中即可(因为csrfmiddleware中间件会通过请求头和请求体两种方式去获取token数据)

    • 当服务端接收到这个请求的时候,在经过csrfmiddleware中间件的时候,就会取出请求头或者请求体中存放的tken数据与服务端生成的数据是否相同,如果相同,那么csrf验证就会通过,如果验证失败,就会返回一个响应状态码为403的response

为什么是process_view?

  • 对呀,为什么是在process_view中处理token验证而不是在process_request中呢?
    • 翻看了一下CsrfViewMiddleware中间件的源码,发现其实django还提供了两个装饰器,分别是csrf_exemptcsrf_protect,当我们使用csrf_exempt装饰一个视图函数的时候,这个视图函数就可以免去csrftoken的验证环节,而csrf_protect则恰恰相反。
    • 要想判断一个视图函数是否被这两个装饰器装饰了,就不能使用process_request了,因为从process_requst中我们没法获取到视图函数对象,在process_view中,通过参数中的callback我们可以获取到对应的视图函数对象,在CsrfViewMiddleware源码中,是通过反射getattr来实现csrf_exempt的。csrf_exempt装饰器会给内层函数绑定一个csrf_exempt属性,且值为True,中间件的process_view中通过反射判断是否对该视图函数进行csrftoken验证,如果返回True,那么process_view就会直接返回一个None来跳过验证
def csrf_exempt(view_func):
    """Mark a view function as being exempt from the CSRF view protection."""
    # view_func.csrf_exempt = True would also work, but decorators are nicer
    # if they don't have side effects, so return a new function.
    def wrapped_view(*args, **kwargs):
        return view_func(*args, **kwargs)
    wrapped_view.csrf_exempt = True
    return wraps(view_func)(wrapped_view)

# -------------------- 分割线 -----------------------
    def process_view(self, request, callback, callback_args, callback_kwargs):
        if getattr(request, 'csrf_processing_done', False):
            return None

        # Wait until request.META["CSRF_COOKIE"] has been manipulated before
        # bailing out, so that get_token still works
        if getattr(callback, 'csrf_exempt', False):
            return None

        # 其他无关的忽略

配置和使用中间件

setting.py

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

关于settings中间件的配置顺序

  • settings文件中middleware配置顺序非常重要,因为一个中间件可能依赖于另外一个,比如一些我们定义的用于用户认证或者权限认证的中间件,在他们的process_request方法中可能会用到session。因此这些中间件必须在SessionMiddleware之后运行
  • 综上,我们自定义的中间件尽量放在列表的后面

中间件的工作过程

  1. 客户端发起请求
  2. 请求首先到达实现了WSGI协议的Web服务器,然后Web服务器处理请求,并转发请求
  3. 由于定义了中间件,对于Web服务器而言,中间件就相当于Web应用,所以请求会交给中间件处理。
    1. 请求首先被中间件的process_request方法处理
    2. 然后被中间件的process_view方法处理
    3. 接着才会执行请求对应的视图函数,视图函数进一步处理请求,并返回一个响应
    4. 然后按照中间件列表中相反的顺序,执行中间件的process_template_resposne方法
    5. 最后按照中间件列表中相反的顺序,执行中间件的process_response方法
  4. response到达Web服务器,经过Web服务器处理之后,最终发送给用户浏览器

创建中间件

  • 中间件本质上就是一个类,在Django的新版本中,创建的类需要继承自MiddlewareMixin
  • 在不同的版本中,继承的类名可能会发生变化,因此我们在创建中间件类的时候,应该直接将MiddlewareMixin类的整个内容拷贝。(版本变动找不到MiddlewareMixin的时候,可以通过已有的中间件去寻找)
class MiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MiddlewareMixin, self).__init__()

    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request)
        if not response:
            response = self.get_response(request)
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response
中间件类中可以定义哪些方法
  • 在中间件类中可以定义5中方法:
    1. process_request(self, request)
      • 返回值
        1. None:执行下一个中间件的process_request
        2. HttpResponse:直接返回,依次执行当前中间件之前中间件的process_response方法
    2. process_response(self, request, response)
      • 返回值
        • 必须返回一个HttpResonse对象或者streamingHttpResponse对象
    3. process_view(self, request. view_func, view_args, view_kwargs)
      • 返回值
        1. None:执行下一个中间件的process_view方法
        2. HttpResposne
    4. process_template_resposne
      • 返回值
        • 必须返回一个实现了render方法的响应对象
    5. process_exception
      • 返回值
        1. None,触发默认的异常处理机制
        2. HttpResponse对象,接着调用template_respone和resposne
创建中间件类
  • process_request(self, request)

  • process_response(self, request, response)

  • 注意

    • 这里的request与response对应此次链接的客户端请求和服务端响应,因此我们可以通过request的属性来控制中间件的作用范围
    • process_request的返回值问题
      • 如果没有返回值(return None),将继续执行下一个中间件的process_request方法或视图函数
      • 如果有返回值,执行自己的process_response和列表中位于当前中间件之前的process_response方法
# setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app.apps.AppConfig',
    'md.mymd.M1',
    'md.mymd.M2'
]
class M1(MiddlewareMixin):
    def process_request(self, request):
        print('in M1 process_request')

    def process_response(self, request, response):
        print('in M1 process_response')


class M2(MiddlewareMixin):
    def process_request(self, request):
        print('in M2 process_request')

    def process_response(self, request, response):
        print('in M2 process_response')
# views.py
def index(request):
    return HttpResponse('test middleware')
使用request.path_info限制中间件的作用范围
  • 用户登陆成功之后,我们要给该用户设置session,如果下次,当进入登陆界面的时候,向服务端发送的Get请求,
class MyMd1(MiddlewareMixin):
    def process_request(self, request):
        from django.conf import settings
        from django.shortcuts import redirect

        if request.path_info == '/login/':
            return None

        if not request.session.get(settings.LOGINAUTH, default=False):
            return redirect(to='/login/')
原文地址:https://www.cnblogs.com/geogre123/p/10001201.html