5.中间件
中间件的介绍
官方定义:中间件就是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量。低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个Django中间件都负责一些特定的功能。
说直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作。中间件的本质就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
# from django.middleware.security import SecurityMiddleware # django自带七个中间件 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', ] """ django中间件是django的门户 1.请求来的时候需要经过中间件才能到达真正的django后端,进而进行路由匹配 2.响应走的时候需要经过中间件才真正离开django后端 """
自定义中间件
中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
-
process_request(self,request)
-
process_view(self, request, view_func, view_args, view_kwargs)
-
process_template_response(self,request,response)
-
process_exception(self, request, exception)
-
process_response(self, request, response)
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。
自定义中间件案例
""" 1.在项目名或者应用名下创建一个任意名称的文件夹 2.在该文件夹内创建一个任意名称的py文件 3.在该py文件内需要书写类(这个类必须继承MiddlewareMixin) 然后在这个类里面就可以自定义五个方法了 (这五个方法并不是全部都需要书写,用几个写几个) 4.需要将类的路径以字符串的形式注册到配置文件中才能生效 """
中间件中的五种方法
# pricess_request 1.中间件process_request方法是在执行视图函数之前执行的 2.不同中间件之间传递的request都是同一个对象 3.请求来的时候需要经过每一个中间件里面的process_request方法,顺序是按照配置文件中注册的中间件从上往下的顺序执行 4.如果中间件中没有里面没有定义该方法,则直接跳过执行下一个中间件 5.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行,而是直接原路返回(校验失败不允许访问),并将HttpResponse返回的值返回给浏览器 # process_response 1.响应走的时候需要经过每一个中间件里面的process_response方法 2.process_response方法有两个额外参数request,response 3.process_response方法必须返回一个HttpResponse对象(没有则报错) 默认返回的就是response(response是视图函数返回的HttpResponse对象,也就是django后端返回给浏览器的内容)。如果不返回response而返回其他对象,则浏览器不会拿到django后台给它的视图,而是我的中间件中返回的对象 4.执行顺序是按照配置文件中注册的中间件从下往上的顺序执行 # process_view(了解) 路由匹配成功之后执行视图函数之前,会自动执行中间件里面的process_vies方法 执行顺序是按照配置文件中注册的中间件从上往下的顺序执行 # process_template_response(了解) 返回的HttpResponse对象有render属性才会触发 执行顺序是按照配置文件中注册的中间件从下往上的顺序执行 # process_exception(了解) 当视图函数中出现异常触发 执行顺序是按照配置文件中注册的中间件从下往上的顺序执行
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class MyMiddleware1(MiddlewareMixin): def process_request(self,request): print('这是第一个自定义中间件里面的process_request方法') print(request) # return HttpResponse('baby') def process_response(self,request,response): print('这是第一个自定义中间件里面的process_response方法') print(request) print(response) return response def process_view(self,request,view_name,*args,**kwargs): print(view_name,args,kwargs) print('这是第一个自定义中间件里面的process_view方法') def process_template_response(self,request,response): print('这是第一个自定义中间件里面的process_template_response方法') return response def process_exception(self,request,exception): print('这是第一个自定义中间件里面的process_ecxeption方法') class MyMiddleware2(MiddlewareMixin): def process_request(self,request): print(request) print('这是第二个自定有中间件里面的process_request方法') def process_response(self,request,response): print('这是第二个自定义中间件里面的process_response方法') # return HttpResponse('自定义中间件中process_response自定义的返回值') return response def process_view(self,request,view_name,*args,**kwargs): print('这是第二个自定义中间件里面的process_view方法') def process_template_response(self,request,response): print('这是第二个自定义中间件里面的process_template_response方法') return response def process_exception(self,request,exception): print('这是第二个自定义中间件里面的process_exception方法')
这是第一个自定义中间件里面的process_request方法 <WSGIRequest: GET '/index/'> <WSGIRequest: GET '/index/'> 这是第二个自定有中间件里面的process_request方法 <function index at 0x0000019686429090> ((), {}) {} 这是第一个自定义中间件里面的process_view方法 这是第二个自定义中间件里面的process_view方法 app01中的index视图 这是第二个自定义中间件里面的process_response方法 这是第一个自定义中间件里面的process_response方法 <WSGIRequest: GET '/index/'> <HttpResponse status_code=200, "text/html; charset=utf-8">
csrf跨站请求伪造
""" 钓鱼网站 搭建一个跟正规网站一摸一样的界面(中国银行) 用户不小心进入到我们的网站,用户给某个人打钱 打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了 但是唯一不同的是大钱的账户不再是用户想要打的账户而是变成了一个莫名其妙的账户
内部本质
我们在钓鱼网站的页面针对对方的账户,只给用户提供了一个没有name属性的普通input框,
然后我们在内部隐藏了一个已经写好name和value的input框
如何规避上述问题
csrf跨站请求伪造检验
网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加上一个唯一标识
当这个页面朝后端发送post请求的时候,后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden),如果成功则正常执行 """
钓鱼网站
from django.contrib import admin from django.urls import path,re_path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), re_path(r'^transfer/', views.transfer), ]
def transfer(request): return render(request,'transfer.html')
<body> <h3>这是一个钓鱼网站</h3> <form action="http://127.0.0.1:8000/transfer/" method="post"> <p>username: <input type="text" name="username"></p> <p>targer_user: <input type="text"></p> <input type="text" name="target_user" value="jiang" style="display: none"> <p>money: <input type="text" name="money"></p> <input type="submit"> </form> </body>
正经网站
from django.contrib import admin from django.urls import path,re_path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), # 转账 re_path(r'^transfer/', views.transfer), ]
def transfer(request): if request.method == 'POST': username = request.POST.get('username') target_user = request.POST.get('target_user') money = request.POST.get('money') print('%s给%s转了%s元' % (username,target_user,money)) return render(request,'transfer.html')
<body> <h3>这是一个正规网站</h3> <form action="" method="post"> <p>username: <input type="text" name="username"></p> <p>target_user: <input type="text" name="target_user"></p> <p>money: <input type="text" name="money"></p> <input type="submit"> </form> </body>
如何符合校验
form表单如何符合校验
<form action="" method="post"> {% csrf_token %} <p>username:<input type="text" name="username"></p> <p>target_user:<input type="text" name="target_user"></p> <p>money:<input type="text" name="money"></p> <input type="submit"> </form>
ajax如何符合校验
<button id="d1">ajax请求</button> {% load static %} <script src="{% static 'js/mysetup.js' %}"></script> <script> $('#d1').click(function () { $.ajax({ url:'', type:'post', // 第一种 利用标签查找获取页面上的随机字符串 {#data:{"username":'jiang','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},#} // 第二种 利用模版语法提供的快捷书写 {#data:{"username":'jiang','csrfmiddlewaretoken':'{{ csrf_token }}'},#} // 第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可 data:{"username":'jiang'}, success:function () { } }) }) </script>
直接拷贝即可
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); } } });
csrf相关装饰器
""" 1.网站整体都不校验csrf,就单单几个视图函数校验 2.网站整体都校验csrf,就单单几个视图函数不校验 """ from django.views.decorators.csrf import csrf_protect,csrf_exempt from django.utils.decorators import method_decorator """ csrf_protect 需要校验 针对csrf_protect符合我们之前所学的装饰器的三种玩法 csrf_exempt 忽视校验 针对csrf_exempt只能给dispatch方法加才有效 """ # @csrf_exempt # @csrf_protect def transfer(request): if request.method == 'POST': username = request.POST.get('username') target_user = request.POST.get('target_user') money = request.POST.get('money') print('%s给%s转了%s元'%(username,target_user,money)) return render(request,'transfer.html') from django.views import View # @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')
补充知识点
# 方法一 # from myfile import b # print(b) # print(b.name) # 方法二 import importlib res = 'myfile.b' ret = importlib.import_module(res) # from myfile import b # 该方法最小只能到py文件名 print(ret) # b<module 'myfile.b' from 'E:\\Users\\jiangxiansen\\PycharmProjects\\python3.10\\djangoProject3\\ab_module\\myfile\\b.py'>
正常思路
def wechat(content): print('微信通知:%s'%content) def qq(content): print('qq通知:%s'%content) def email(content): print('邮箱通知:%s'%content)
from notify import * def send_all(content): wechat(content) # qq(content) email(content) if __name__ == '__main__': send_all('啥时候放长假') # 当你不需要qq功能的时候,找到所有使用send_all的地方把qq注掉
高级用法
import settings import importlib def send_all(content): for path_str in settings.NOTIFY_LIST: # 'notify.email.Email' module_path, class_name = path_str.rsplit('.',maxsplit=1) # module_path = 'notify.email' class_name = Email # 1.利用字符串导入模块 module = importlib.import_module(module_path) # from notify import email # 2.利用反射获取类名 cls = getattr(module, class_name) # 3.生成类对象 obj = cls() # 4.利用鸭子类型直接调用send方法 obj.send(content)
class Email(object): def __init__(self): pass # 发送邮箱需要做的前期准备工作 def send(self, content): print('邮箱通知:%s' % content)
class Msg(object): def __init__(self): pass # 发送短信需要做的前期准备工作 def send(self, content): print('短信通知:%s' % content)
class QQ(object): def __init__(self): pass # 发送QQ需要做的前期准备工作 def send(self, content): print('QQ通知:%s' % content)
class Wechat(object): def __init__(self): pass # 发送微信需要做的前期准备工作 def send(self, content): print('微信通知:%s' % content)
NOTIFY_LIST = [ 'notify.email.Email', 'notify.msg.Msg', 'notify.qq.QQ', 'notify.wechat.Wechat', ]
import notify notify.send_all('快下课了')