每个中间件可以理解为项目中的一个独立的功能。
中间件作为Django项目的门户。
只要是涉及到项目全局的功能,首先应该考虑使用中间件。
Django内部有7个内置的中间件。
Django支持自定义中间件,提供了5个可以自定义的方法。
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',
]
上面相当于从模块中导入类
from django.middleware.security import SecurityMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.middleware.common import CommonMiddleware
from django.middleware.csrf import CsrfViewMiddleware
from django.contrib.auth.middleware import AuthenticationMiddleware
from django.contrib.messages.middleware import MessageMiddleware
from django.middleware.clickjacking import XFrameOptionsMiddleware
7个内置的中间件类的特点
Django支持自定义中间件,提供了暴露给程序员5个可以自定义的方法。
重要的方法
process_request
process_response
了解的方法
process_view
process_template_response
process_exception
创建自定义中间件步骤:
MIDDLEWARE = [
...
'app01.mymiddleware.mymiddleware.MyMiddleware',
]
mymiddleware.py
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
print('执行自定义中间件的process_request')
process_request
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
pass
process_response
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_response(self, request, response):
...
return response
process_view
触发时机:路由匹配成功之后,执行视图函数之前。
顺序是按照配置文件中注册的中间件从上向下依次执行。
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_view(self, request, view_name, *args, **kwargs):
pass
process_template_response
触发条件:视图函数返回的HttpResponse对象中有一个render()方法。
顺序是按照配置文件中注册的中间件从下向上依次执行。
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_template_response(self, request, response):
...
return response
views.py
def index(request):
obj = HttpResponse('test1')
def render():
...
return HttpResponse('test2')
obj.render = render
return obj
process_exception
触发条件:视图函数中出现错误抛出异常时。
顺序是按照配置文件中注册的中间件从上向下依次执行。
参数exception是报错提示信息。
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_exception(self, request, exception):
pass
跨站请求伪造(csrf,Cross-site Request Forgery)
与钓鱼网站有关。
钓鱼网站本质
用假网站冒充正规网站。
钓鱼网站现象
首先搭建一个与正规网站一模一样的网站,即钓鱼网站,这里以银行网站为例。
用户不小心进入了这个假的银行网站,进行转账操作。
转账操作提交到了真正的银行系统,用户的存款也减少了。
但是转账的目标账户不是用户的目标账户,而是另一个账户。
钓鱼网站核心流程
在钓鱼网站的转账页面中,针对目标账户的输入框,只给用户提供了一个没有name属性的input标签。
在内部使用hidden属性隐藏一个已填好name和value的input标签,name是目标用户字段,value是设定好的账户。
这样用户转账的目标账户实际是内部设定好的账户。
钓鱼网站提交的目标是实际的银行系统,银行系统后端接收到的参数表明用户向钓鱼网站设定的账户进行转账操作。
防止钓鱼网站的思路:后端系统需要判断识别出请求是否来自本后端提供的网页。
跨站请求伪造校验思路
后端系统向用户返回一个具有提交表单功能的页面时,会在页面上添加一个隐藏的唯一标识。
这个唯一标识每次请求都不相同。
页面向后端系统提交表单时会发送这个唯一标识,这样后端系统能够通过唯一标识判断出请求是否合法。
如果请求的唯一标识不存在,后端系统会拒绝此次请求,返回403 Forbidden错误。
Django后端通过内置中间件CsrfViewMiddleware进行csrf校验。
前端提交post请求方式包括:
<form action="" method="post">
{% csrf_token %}
...
form>
在form表单中添加{% csrf_token %}
,它会渲染成隐藏的input标签。
<form action="" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="token唯一标识">
...
form>
方式1,通过查找标签获取页面上的唯一标识。
<script>
$('#btn1').click(function () {
$.ajax({
url: '',
method: 'post',
data: {
...,
'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken]'.val())
},
success: function(args) {
}
})
})
script>
方式2,使用模版语法。
<script>
$('#btn1').click(function () {
$.ajax({
url: '',
method: 'post',
data: {
...,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(args) {
}
})
})
script>
方式3,引入js文件
方式3不依赖于模版语法,推荐。
static文件夹/js文件夹/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);
}
}
});
代码详见:Djagno官方文档中关于CSRF的内容
settings.py
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
{% load static %}
<script src="{% static 'js/mysetup.js' %}">script>
<script>
$('#btn1').click(function () {
$.ajax({
url: '',
method: 'post',
data: {
...
},
success: function(args) {
}
})
})
script>
需求:
csrf相关装饰器
csrf_protect 需要校验
csrf_exempt 忽视校验
csrf_protect与csrf_exempt使用方式相同。
from django.views.decorators.csrf import csrf_protect, csrf_exempt
@csrf_protect
def test_csrf_protect(request):
pass
@csrf_exempt
def test_csrf_exempt(request):
pass
对于csrf_protect,有三种方式添加装饰器。
方式1
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
from django.views import View
class MyCsrfTest(View):
def get(self, request):
pass
@method_decorator(csrf_protect)
def post(self, request):
pass
方式2
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
from django.views import View
@method_decorator(csrf_protect, name='post')
class MyCsrfTest(View):
def get(self, request):
pass
def post(self, request):
pass
方式3
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
from django.views import View
class MyCsrfTest(View):
@method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfTest, self).dispatch(request, *args, **kwargs)
def get(self, request):
pass
def post(self, request):
pass
对于csrf_exempt,只能作用于dispatch方法才能生效。
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
class MyCsrfTest(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfTest, self).dispatch(request, *args, **kwargs)
def get(self, request):
pass
def post(self, request):
pass
另一种为dispatch方法添加csrf_exempt装饰器的方式
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
@method_decorator(csrf_exempt, name='dispatch')
class MyCsrfTest(View):
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfTest, self).dispatch(request, *args, **kwargs)
def get(self, request):
pass
def post(self, request):
pass
from target_folder import target_file
print(target_file.value)
另一种导入模块的方式,使用importlib模块以字符串的形式导入指定模块。
使用importlib模块导入的最小单位为文件,不能导入文件内的名字。
import importlib
TARGET_FILE_STR = 'target_folder.target_file'
target_module = importlib.import_module(TARGET_FILE_STR)
print(target_module.value)
from target_folder import target_file
等价于
importlib.import_module('target_folder.target_file')
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',
]
需求:发送消息,发送方式包括邮件、QQ、微信。
方式1
notify.py
def send_email(content):
print(content)
print('发送邮件。')
def send_wechat(content):
print(content)
print('发送微信。')
def send_qq(content):
print(content)
print('发送qq。')
start.py
from notify import *
def send_all(content):
send_email(content)
send_wechat(content)
send_qq(content)
if __name__ == '__main__':
send_all('Test')
此时,如果需要取消qq发送,需要在函数send_all中将函数send_qq注释掉。
但是需要修改源代码,不便于管理人员操作。
方式2,基于中间件的编程思想
创建notify文件夹,内部创建3个功能py文件和1个__init__.py。
byemail.py
class ByEmail():
def __init__(self):
# 发送邮件的准备工作
pass
def send(self, content):
print(content)
print('发送邮件。')
bywechat.py
class ByWechat():
def __init__(self):
# 发送微信的准备工作
pass
def send(self, content):
print(content)
print('发送微信。')
byqq.py
class ByQQ():
def __init__(self):
# 发送QQ的准备工作
pass
def send(self, content):
print(content)
print('发送QQ。')
定义包需要__init__.py,中间件的编程思想的核心
import settings
import importlib
def send_all(content):
for each_path_str in settings.NOTIFY_LIST:
# 1. 获取每一个模块路径以及对于的类名。
module_path, class_name = each_path_str.rsplit('.', maxsplit=1)
# 2. 利用importlib模块以字符串的形式导入模块。
each_module = importlib.import_module(module_path)
# 3. 利用反射获取类
each_class = getattr(each_module, class_name)
# 4. 通过类实例化对象
each_obj = each_class()
# 5. 利用鸭子类型调用对象的send方法。
each_obj.send(content)
在项目根目录下创建start.py和settings.py。
settings.py
NOTIFY_LIST = [
'notify.byemail.ByEmail',
'notify.bywechat.ByWechat',
'notify.byqq.ByQQ',
]
start.py
import notify
notify.send_all('Test')
此时,如果需要取消qq发送,管理员可以去配置文件中将对应的配置注释掉。
如果需要添加skype发送功能,首先需要先创建新的py文件。
byskype.py
class BySkype():
def __init__(self):
# 发送skype的准备工作
pass
def send(self, content):
print(content)
print('发送skype。')
然后去配置文件settings.py中对skype发送功能进行注册。
settings.py
NOTIFY_LIST = [
'notify.byemail.ByEmail',
'notify.bywechat.ByWechat',
'notify.byqq.ByQQ',
'notify.byskype.BySkype', # 注册skype发送功能。
]
这样做,当添加新功能时,不需要修改项目主要的逻辑代码。
思想总结