day70 django中间件

一、django的七个中间件

django中间件是浏览器和服务端交互的第一个门栏,请求来的时候需要通过中间件,响应走的时候也需要进过中间件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware'
    from django.middleware.security import 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',
]

# 以上代码相当于中间件的导入,通过一些内部的操作转换成下面的代码
'django.middleware.security.SecurityMiddleware'
from django.middleware.security import SecurityMiddleware

# 我们可以去这些中间件的源码里去找
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
		prpcess_respone
		
	2 了解即可
		process_view
		processs_template_respone
		process_exception
'''

二、如何自定义中间件

1 自定义中间件的创建方式

  1. 先再项目下或者应用下创建一个任意名称的文件夹
  2. 在项目文件下内创建一个任意名称的py文件
  3. 在该py文件内书写类,这个类必须继承MiddlewareMixin,然后在这个类中自定制我们的需要的django提供的五个方法,用什么写什么,没有必须要写的
  4. 将类的路径以字符串的形式注册到配置文件中生效

2 django提供自定制中间件的五个方法

2.1 必须掌握两个方法

process_request

特点:

  • 所有请求来到服务端的时候先要依次(按照书写顺序的先后)经过所有中间件的process_request方法(没有就跳过)
  • 如果这个函数返回了一个HttpRespone对象,那么这个请求就会从这个函数开始原路返回(会先执行同中间件的process_respone,再依次往上返回)
  • 这些特点可以让这个方法作为全局相关的所有限制功能
class Mymid1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件的process_request')

process_response

特点:

  • 所有响应返回的时候都要依次(与书写顺序相反)经过所有中间件的process_response方法(没有就跳过)
  • 必须返回respons对象
    • 返回的相当于是应该响应的数据
  • 或者返回一个HttpResponse对象
    • 返回的相当于是把应该相应的数据替换成我们自己写的数据(偷梁换柱)

ps:flask也有中间件,它的规律是只要返回了数据就必须要经过所有中间件里类似process_response方法

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render,HttpResponse

class Mymid1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件的process_request')

    def process_response(self,request,response):
        print('我是第一个中间件的process_respone')
        return HttpResponse('123') # 截胡

class Mymid2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第二个中间件的process_request')

    def process_response(self, request, response):
        print('我是第二个中间件的process_respone')
        return response # 正常返回

2.2 了解方法

process_view

  • 在路由匹配成功之后,执行视图函数之前会执行此方法
  • 所以只会在请求的时候执行
  • 执行顺序是中间件从上往下
def process_view(self,request,view_name,args,kwargs):
    # print(view_name,args,kwargs)
    print('我是第一个中间件的process_view')

process_template_response

  • 当视图函数返回的HttpRespone对象中有render属性的时候会触发
  • 执行顺序是中间件从下往上
# 中间件中
def process_template_response(self, request, response):
    print('我是第二个中间件的process_template_response')
    return response

# 视图函数中
def index(request):
    print('我是视图函数index')
    obj = HttpResponse('index')

    def render():
        print('内部的render')
        return HttpResponse("O98K")
    obj.render = render
    return obj

process_exception

  • 当视图函数出现异常的时候触发(比如写了一个名字但是没定义)
  • 顺序按照中间件从下往上
def process_exception(self, request, exception):
    print('我是第二个中间件的process_exception')
    print(exception) # 报错信息

三、csrf跨站请求伪造校验

1 案例及解决方案

案例:钓鱼网站

​ 用户在一个缴费钓鱼网站给自己制定的用户转钱,结果转到了另一个人账号上

原因:

​ 用户在打开缴费钓鱼网站的时候不会发现区别,钓鱼网站的页面会做的和正规页面一模一样,唯一的区别就是钓鱼网站内部有一个隐藏的input框去代替我们输入的对方账号的input框,最后向缴费服务端发送数据的时候,提交的是隐藏的数据。

如何避免上述问题

解决方案:csrf跨站请求伪造校验

原理:当页面朝后端发送post请求的时候,会顺带发送一个唯一标识码,这个唯一标识码是在渲染页面的时候服务端给页面的数据,如果唯一标识不对,直接拒绝访问(403 forbbiden),如果成功则正常执行

2 如何符合校验

form表单符合校验代码

<form action="" method="post">
    {% csrf_token %}
    <input type="submit">
</form>

ajax符合校验代码

{% csrf_token %}
<button id="d1">提交</button>
 
// 方式三:推荐使用,导入一个写好的js文件去自动获取csrf
{% load static %}
<script src="{% static 'js/mysetup.js' %}"></script>
<script>
    $('#d1').click(function () {
        $.ajax({
            url:'',
            type:'post',
            // 方式一:利用标签获取页面上的验证码
            data:{'username':'hz','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
            // 方式二:利用模版语言便捷写法
            data:{'username':'hz','csrfmiddlewaretoken':{% csrf_token %}},

            data:{'username':'hz'},
            success:function () {

            }
        })
    })
</script>

mysetup.js代码

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);
    }
  }
});

3 相关装饰器

'''
以下两种情况用到两种不同的装饰器
1 网站整体都不校验csrf,就单独几个视图函数需要校验
2 网站整体都不校验csrf,就单独几个视图函数不校验
'''
from django.views.decorators.csrf import csrf_protect,csrf_exempt
'''
csrf_protect:需要校验,在CBV种三种装饰器方法都可以使用
csrf_exempt:忽视校验,在CBV中只能使用给dispatch加装饰器的方法
'''

@csrf_exempt
@csrf_protect
def index(request):
    print('我是index函数')
    # print(index)
    if request.method=='POST':
        print(request.POST.get('username'))
        return HttpResponse('post index')
    return render(request,'index.html')


# CBV
# @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')

四、重要补充知识点

补充

# 通过一个模块实现和settins的中间件一样以字符串形式导入模块
# 这个方法最小只能到py文件名
import importlib
res = 'myfile.b'
# 下面等价于:from myfile import b
ret = importlib.import_module(res)

思想

利用已学习的知识模仿django中间件导入

# 创建一个包里面存有我们要使用的类

# mymid.qq
class QQ():
    def __init__(self):
        pass

    def send(self,msg):
        print(f'qq:{msg}')

# mymid.wechat
class Wechat():
    def __init__(self):
        pass

    def send(self,msg):
        print(f'wechat:{msg}')
        
# 创建一个settings去存储这些类的路径
# settings.py
MYMID_DIR = [
    'mymid.qq.QQ',
    'mymid.wechat.Wechat',
]

# 重点!!!!!!
# mymid.__init__.py
import importlib
import settings

def send_all(msg):
    for mymid in settings.MYMID_DIR:
        # 1 先用切片的方式获取模块路径和具体类名
        module_path,cls_name = mymid.rsplit('.',maxsplit=1)
        # print(module_path,cls_name)
        # 2 通过importlib模块以字符串的形式导入指定模块
        res = importlib.import_module(module_path)
        # 3 通过反射的方法获取类名
        cls = getattr(res,cls_name)
        obj = cls()
        # 4 利用鸭子类型直接调用send方法
        obj.send(msg)
        
        
# start.py启动文件
import mymid
mymid.send_all('吃饭了')
原文地址:https://www.cnblogs.com/hz2lxt/p/13067622.html