django中间件
目录
Django中间件简介
通过之前学过的django请求生命周期流程图中可以发现,django中间件是django的门户
1.请求来的时候需要先经过中间件才能到达真正的django后端
2.响应走的时候最后也需要经过中间件才能发送出去
研究django中间件代码规律
- django自带7个中间件(settings.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',
]
- 研究django自带中间件
1.如何查看django中间件的源码
django中间的源码是列表存字符串,如何查看源码呢?
其实这个字符串就是导入的模块,使用了importlib模块就可以使用字符串导入模块,后面会进行介绍,所以列表中的每一行都是在导入模块,比如:
'django.middleware.security.SecurityMiddleware'
等同于:from django.middleware.security import SecurityMiddleware
如此,就能看见每一个django中间件的源码
2.研究django中间件的代码规律(以下是部分中间件的简略的代码)
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))
3.django自带中间件的源码都是类,每个类下面都有process_xxx方法
django支持自定义中间件并且暴露给程序员五个可以自定义的方法
1.必须掌握
process_request
process_response
2.了解即可
process_view
process_template_response
process_exception
下面就通过如何自定义中间件研究一下中间件的执行顺序
- 如何自定义中间件及中间件执行规律
如何自定义中间件?--- 准备工作
- 首先在项目名或者应用名下创建一个任意名称的文件夹
- 在该文件夹内创建一个任意名称的py文件
- 在该py文件内需要书写类,这个类必须继承MiddlewareMixin(因为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',
'你自己写的中间件的路径1',
'你自己写的中间件的路径2',
'你自己写的中间件的路径3',
]
自定义中间件代码实例
- settings.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',
# 自定义中间件
'app01.mymidleware.mm.MyMiddle1',
'app01.mymidleware.mm.MyMiddle2',
]
- mymidleware.mm.py文件
from django.utils.deprecation import MiddlewareMixin
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class MyMiddle1(MiddlewareMixin):
def process_request(self,request):
print('我是第一个中间件的process_request')
def process_response(self,request,response):
'''
:param request:
:param response: django后端返回给浏览器的内容
:return:
'''
print('我是第一个中间件的process_response')
# 将后端内容返回给浏览器客户端,不返回就相当于拦截了后端的内容,不给浏览器返回后端内容
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print('view')
def process_template_response(self,request,response):
print('template')
return response
class MyMiddle2(MiddlewareMixin):
def process_request(self,request):
print('我是第二个中间件的process_request')
def process_response(self, request, response):
print('我是第二个中间件的process_response')
# 也可以返回其他内容给客户端
return HttpResponse('就是不给你看后端数据')
- views.py(主要是process_template_response方法需要render方法)
from django.shortcuts import render,HttpResponse
# Create your views here.
def index(request):
obj = HttpResponse('index')
def render():
print('我执行了嘿')
return HttpResponse('98k')
obj.render = render
return obj
中间件中方法的执行规律
- process_request
1.请求来的时候需要经过每一个中间件里面的process_request方法
结果的顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
而是直接原路返回(校验失败不允许访问...)
process_request方法就是用来做全局相关的所有限制功能
- process_response
1.响应走的时候需要经过每一个中间件里面的process_response方法
该方法有两个额外的参数request,response
2.该方法必须返回一个HttpResponse对象
1.默认返回的就是形参response,
2.也可以自己返回自己的
3.不返回的话浏览器客户端会报错:'NoneType' object has no attribute 'get'
3.顺序是按照配置文件中注册了的中间件从下往上依次经过
如果你没有定义的话 直接跳过执行下一个
- 研究:如果在第一自定义中间件的process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况?
在第一自定义中间件的process_request方法就已经返回了HttpResponse对象,这个中间件后面所有中间件的process_request process_response都不会执行了,就是原路返回
但是在flask框架中也有中间件,但是它的规律就是只要返回数据就必须经过所有的中间件
- process_view
路由匹配成功之后,执行视图函数之前,会自动执行中间件里面的该方法
顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
- process_template_response
返回的HttpResponse对象有render属性的时候才会触发
顺序是按照配置文件中注册了的中间件从下往上依次执行
- process_exception
当视图函数中出现异常的情况下触发
顺序是按照配置文件中注册了的中间件从下往上依次经过
csrf
csrf跨站请求伪造 --- 钓鱼网站
搭建一个与正规网站一模一样的界面(中国银行)
用户不小心进入了自己写的中国银行网站(钓鱼网站),给某人打钱
打钱的操作确实是提交给了中国银行的系统,用户的钱也确实变少了
但是,唯一不同的是收钱的账户不是用户想要打钱的账户,而是变成了一个莫名其妙的账户
钓鱼网站的内部本质
在钓鱼网站的页面针对对方账户,只给用户提供一个没有name属性的普通input框
钓鱼网站内部隐藏一个已经写好的name 和 value的input框
假网站提交数据的时候是需要向真网站的服务端提交数据
- 代码简单实现钓鱼网站
- 正经网站
后端views.py
def money(request):
if request.method == 'POST':
username = request.POST.get('name')
money = request.POST.get('money')
tag = request.POST.get('tag')
print(f'{username} to {tag} 金额 {money}')
return render(request,'money.html')
前端请求页面
<body>
<p>我是正经网站</p>
<form action="" method="post">
<p>name<input type="text" name="name"></p>
<p>tag<input type="text" name="tag"></p>
<p>money<input type="text" name="money"></p>
<input type="submit" value="转账">
</form>
</body>
- 假正经网站
<p>我是假正经网站</p>
<form action="http://127.0.0.1:8000/bank/" method="post">
<p>name<input type="text" name="name"></p>
<p>tag<input type="text" ></p>
<input type="text" name="tag" value="jason" style="display: none">
<p>money<input type="text" name="money"></p>
<input type="submit" value="转账">
</form>
</body>
如何规避上述钓鱼网站的问题---csrf校验
crf跨站请求校验(中间件中csrf那一行就不需要注释了)
网站在给用户返回一个具有提交数据功能的页面的时候会给页面加上一个唯一标识
当这个页面朝后端发送post请求的时候 后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden)如果成功则正常执行
- form表单发送post请求时如何进行校验
在form表单内部加% csrf_token %}即可
<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请求发送post请求如何进行校验
- 方式一:获取页面上服务端给页面的唯一标识(input框,有name属性(csrfmiddlewaretoken),以及value值)
<body>
<p>我是正经网站</p>
<form action="" method="post">
{% csrf_token %}
<p>name<input type="text" name="name"></p>
<p>tag<input type="text" name="tag"></p>
<p>money<input type="text" name="money"></p>
</form>
<button id="d1">ajax请求</button>
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:{'name':'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
success:function () {}
}
)
})
</script>
</body>
- 方式二:借助模版语法
<body>
<p>我是正经网站</p>
<form action="" method="post">
{% csrf_token %}
<p>name<input type="text" name="name"></p>
<p>tag<input type="text" name="tag"></p>
<p>money<input type="text" name="money"></p>
</form>
<button id="d1">ajax请求</button>
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:{'name':'jason','csrfmiddlewaretoken':'{{csrf_token}}'},
success:function () {}
}
)
})
</script>
</body>
- 方式三:通用方式,直接拷贝js代码并应用到自己的html页面上即可,配置一下静态文件
<body>
<p>我是正经网站</p>
<form action="" method="post">
{% csrf_token %}
<p>name<input type="text" name="name"></p>
<p>tag<input type="text" name="tag"></p>
<p>money<input type="text" name="money"></p>
</form>
<button id="d1">ajax请求</button>
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:{'name':'jason'},
success:function () {}
}
)
})
</script>
- 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);
}
}
});
如何实现项目部分视图函数校验,部分不校验
- 需求
1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验
- 装饰器方法介绍
csrf_protect 需要校验
csrf_exempt 忽视校验
- 实现上述需求需要给视图函数加上述两种装饰器
- FBV实现需求,在对应的视图函数上加对应的装饰器即可
# @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')
- CBV实现需求:如何给CBV加装饰器
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
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')
针对csrf_protect:三种加装饰器的方式都可以
针对csrf_exempt:只能给dispatch加装饰器