【一】Django框架之生命周期流程图
【二】介绍
【1】概述
- Django 中的中间件(Middleware)是一个轻量级 、底层 的"插件"系统,用来全局 地改变 Django 的输入 或输出。
- 每个中间件组件负责处理特定的全局任务,例如处理会话、处理跨站请求伪造保护、实现缓存处理等。
【2】主要作用
-
请求处理:
- 在 Django 处理视图之前,中间件可以对请求进行预处理。
- 例如,某些中间件可以更改 HTTP 请求头,或者在请求对象上设置其他属性。
-
响应处理:
- 在 Django 完成视图处理后,中间件可以对响应进行后处理。
- 例如,某些中间件可以更改 HTTP 响应头,或者在响应内容中添加额外的数据。
-
异常处理:
- 如果视图抛出异常,中间件可以捕获这些异常并进行处理。
- 例如,某些中间件可以将异常记录到日志中,或者显示一个友好的错误页面。
-
全局任务:
- 中间件可以处理那些需要在每个请求和响应中都执行的任务。
- 例如,Django 的 SessionMiddleware 在每个请求开始时加载会话数据,并在每个响应结束时保存会话数据。
-
大致顺序
- 中间件是按照在
settings.py
文件的MIDDLEWARE
设置中定义的顺序执行的。 - 当一个请求到来时,Django 会按照从上到下的顺序应用每个中间件,然后调用相应的视图。
- 当视图处理完成后,Django 会按照从下到上的顺序应用每个中间件,然后返回响应。
- 中间件是按照在
【三】默认的中间件
【1】七大中间件
(1)SecurityMiddleware
-
django.middleware.security.SecurityMiddleware
-
安全中间件
- 负责处理与网站安全相关的任务
-
例如:
- 自动转换非HTTPS请求到HTTPS(如果
SECURE_SSL_REDIRECT
设置为True
) - 添加
HTTP Strict Transport Security
(HSTS)头部(如果SECURE_HSTS_SECONDS
设置的值大于0) - 以及防止点击劫持(如果
X_FRAME_OPTIONS
设置为'DENY'
或'SAMEORIGIN'
)。
- 自动转换非HTTPS请求到HTTPS(如果
-
它可以通过配置自定义安全策略来确保网站的安全性。
(2)SessionMiddleware
django.contrib.sessions.middleware.SessionMiddleware
- 会话中间件
- 它在每个请求开始时加载会话数据,并在每个响应结束时保存会话数据。
(3)CommonMiddleware
django.middleware.common.CommonMiddleware
- 通用中间件
- 提供了一些常见而关键的HTTP请求处理功能
- 例如,根据请求的HTTP头信息设置语言、时区等。
(4)CsrfViewMiddleware
django.middleware.csrf.CsrfViewMiddleware
- 跨站请求伪造中间件CSRF(Cross-Site Request Forgery)
- 用于防止跨站请求伪造攻击。
- 它在每个POST请求中验证一个CSRF标记,确保请求是通过合法的表单提交得到的,从而保护用户免受恶意站点的攻击。
(5)AuthenticationMiddleware
django.contrib.auth.middleware.AuthenticationMiddleware
- 认证中间件
- 负责处理用户身份认证相关的任务
- 这个中间件为每个请求添加一个
user
属性,表示当前的登录用户。如果用户没有登录,request.user
将是一个AnonymousUser
实例。否则,request.user
将是一个User
实例。
(6)MessageMiddleware
django.contrib.messages.middleware.MessageMiddleware
- 消息中间件
- 用于在请求处理过程中存储和传递临时的、一次性的用户消息。
- 它允许在HTTP重定向之间跨请求传递消息,例如成功或错误提示,以改善用户体验。
(7)XFrameOptionsMiddleware
django.middleware.clickjacking.XFrameOptionsMiddleware
- 点击劫持中间件
- 用于防止页面被嵌入到其他网站中,从而提供一定的点击劫持保护。
- 它通过设置X-Frame-Options HTTP头部来限制页面的显示方式,从而防止恶意网页通过iframe等方式嵌入当前网页。
【2】中间件方法
(1)process_request
process_request(self, request)
- 他会在Django开始处理每个请求之前被调用
- 这个方法接收一个request参数,这参数是一个HttpResponse对象,包含关于当前请求的所有信息
- 这个方法可以修改 request对象,或者直接返回一个HttpResponse对象来终止请求的处理
- 这个参数如果是None,Django将会继续处理这个请求
python
# Django 1.10 以前的旧的中间件风格
from django.http import HttpResponseRedirect
from django.urls import reverse
class AuthenticationMiddleware:
def process_request(self, request):
# 在这里进行身份验证操作
if not request.user.is_authenticated:
# 如果用户未经身份验证,则返回HttpResponse或重定向到登录页面
return HttpResponseRedirect(reverse('login'))
python
# Django 1.10 以后的新的中间件风格,它需要定义 __call__ 方法。__call__ 方法在每个请求上都会被调用
from django.http import HttpResponseRedirect
from django.urls import reverse
class AuthenticationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 在这里进行身份验证操作
if not request.user.is_authenticated:
# 如果用户未经身份验证,则重定向到登录页面
return HttpResponseRedirect(reverse('login'))
# 如果用户已经通过身份验证,继续处理请求
# get_response 是一个回调函数,当我们在中间件中调用它时,Django 会继续处理请求,调用视图函数并生成响应。
response = self.get_response(request)
return response
(2)process_response
process_response(self, request, response)
- 它会在 Django 完成处理每个请求之后被调用。
- 这个方法接收两个参数:
request
是一个HttpRequest
对象,包含了关于当前请求的所有信息 ;response
是一个HttpResponse
对象,包含了当前请求的响应。 - 这个方法可以修改
response
对象,或者返回一个全新的HttpResponse
对象。 - 无论这个方法返回什么,这个返回值都将作为请求的最终响应。
python
# # Django 1.10 以前的旧的中间件风格
class CustomResponseMiddleware:
def process_response(self, request, response):
# 在这里对响应进行处理
response['X-Custom-Header'] = 'Custom Value'
return response
python
# # Django 1.10 以后的新的中间件风格,它需要定义 __call__ 方法。__call__ 方法在每个请求上都会被调用
class CustomResponseMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 在这里,可以添加在请求被处理之前需要执行的逻辑
response = self.get_response(request)
# 在这里对响应进行处理
response['X-Custom-Header'] = 'Custom Value'
return response
-
每个中间件都是依次处理的,而不是并行处理的。尽管在新的中间件风格(Django 1.10 及更高版本)中,
__call__
方法同时处理进入和离开的请求,但这并不意味着中间件是并行运行的。当一个请求到达 Django 应用时,它会按照
MIDDLEWARE
列表的顺序,依次通过每个中间件的__call__
方法。当在__call__
方法中调用get_response(request)
时,控制权会传递给下一个中间件,直到所有的中间件都运行完毕,然后才会执行视图函数。在视图函数执行完毕并生成响应后,控制权会按照与进入时相反的顺序,回到每个中间件的
__call__
方法。在这个阶段,中间件可以修改响应或执行其他后处理操作。所以,尽管
__call__
方法看起来同时处理进入和离开的请求,但实际上,中间件仍然是按照特定的顺序依次执行的。这就是为什么 Django 中间件有时被描述为像洋葱一样的结构:每个中间件都像洋葱的一层,请求和响应都必须穿过所有的层。
(3)process_view
process_view(request, view_func, view_args, view_kwargs)
- 路由匹配成功后执行视图函数之前
- 这个方法的参数包括请求对象、视图函数、视图函数的参数和关键字参数。
- 如果这个方法返回一个 HttpResponse 对象 ,视图函数将不会被调用。
python
import logging
logger = logging.getLogger(__name__)
class LoggingMiddleware:
def process_view(self, request, view_func, view_args, view_kwargs):
# 在这里记录日志
logger.info(f"Request received: {request.path}")
# 返回None,继续执行原视图函数
return None
(4)process_template_response
process_template_response(request, response)
- 这个方法在视图函数处理完请求并生成了一个模板响应对象后 返回的 HttpResponse 对象有 render 属性的时候才会触发
- 顺序是按照配置文件中注册了的中间件从下往上依次经过。
- 这个方法的参数包括请求对象和视图函数返回的响应对象。必须返回一个TemplateResponse对象,通常是传入的响应对象或者一个新的响应对象。
(5)process_exception
process_exception(request, exception)
- 这个方法在视图函数或者其他的中间件方法抛出异常时运行。
- 顺序是按照配置文件中注册了的中间件从下往上依次经过
- 例如 返回一个定制的错误页面 或进行日志记录等。
- 这个方法的参数包括请求对象和抛出的异常对象。
- 如果这个方法返回一个 HttpResponse 对象 ,这个响应将会被发送到客户端,而且其他的
process_exception
方法将不会被调用。
python
class SimpleMiddleware:
def process_exception(self, request, exception):
# 在这里处理异常
# 如果返回 HttpResponse 对象,Django 将停止调用其他的 process_exception 方法
# 如果返回 None 或者没有明确的返回值,Django 将继续调用其他的 process_exception 方法
return None
python
class ErrorHandlerMiddleware:
def process_exception(self, request, exception):
# 在这里处理异常
if isinstance(exception, CustomException):
# 如果自定义异常,返回一个定制的错误页面
return render(request, 'error.html', {'error': str(exception)})
else:
# 默认情况,返回一个500服务器错误
return HttpResponseServerError("Internal Server Error")
【四】自定义中间件
【1】简单示例
(1)自定义
- 自定义中间件位置
- 可以在项目的根目录 或者在应用目录 下创建任意名称的文件夹
- 创建py文件
- py文件的名 字也是任意的
- 创建自定义中间件类
- 需要继承MiddlewareMixin混入类
- 版本
- 在 Django 1.10 之前,中间件需要定义一些特定的方法,如
process_request(request)
、process_response(request, response)
等,并且需要继承MiddlewareMixin
混入类 - 在 Django 1.10 及之后的版本中,推荐的写法是定义一个只有两个方法(
__init__
和__call__
)的类。
- 在 Django 1.10 之前,中间件需要定义一些特定的方法,如
python
from django.utils.deprecation import MiddlewareMixin
class MyMiddleWare1(MiddlewareMixin):
def process_request(self, request):
print("第一个中间件的process_request")
def process_response(self, request, response):
print("第一个中间件的process_response")
return response
class MyMiddleWare2(MiddlewareMixin):
def process_request(self, request):
print("第二个中间件的process_request")
def process_response(self, request, response):
print("第二个中间件的process_response")
return response
(2)添加到配置文件
- 在settings文件的
- MIDDLEWARE列表里面添加自定中间件
- 整体是字符串格式
- 路径之间通过
.符号
连接 - 多个中间件之间以
,逗号
连接
- 这里按照中间件1然后中间件2的顺序注册
python
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.mymiddleware.my_middleware.MyMiddleWare1',
'app01.mymiddleware.my_middleware.MyMiddleWare2',
]
(3)测试
- 视图函数
python
def home(request):
print("访问主页")
return HttpResponse("主页")
- 输出结果
python
第一个中间件的process_request
第二个中间件的process_request
访问主页
第二个中间件的process_response
第一个中间件的process_response
【2】测试process_request
(1)django框架
- 如果将第一个中间件的process_request
- 添加返回值HttpResponse
- 那么将直接走当前中间件的process_response
python
def process_request(self, request):
print("第一个中间件的process_request")
print("第一个中间价process_request拦截")
return HttpResponse("中间件一拦截")
- 结果
python
第一个中间件的process_request
第一个中间价process_request拦截
第一个中间件的process_response
(2)flask框架
- flask框架的中间件也有类似的方法
- 但是Flask 的中间件处理方式与 Django 略有不同。
- 方法名字不同:
- Flask 使用称为 "before_request" 和 "after_request" 的装饰器来处理请求前和请求后的操作。
- 处理结果不同:
- 在 Flask 中,如果在第一个中间件的
before_request
中返回了一个Response
对象,那么 Flask 将立即停止处理后续的before_request
函数,并且立即开始处理所有已注册的after_request
函数。 - 和djanfo相同的是都是倒叙
- 在 Flask 中,如果在第一个中间件的
- 方法名字不同:
【五】跨站请求伪造
【1】介绍
(1)概述
- 跨站请求伪造
- (Cross-Site Request Forgery,CSRF)是一种网络攻击方式
- 攻击者通过伪造用户的请求 来让被攻击的网站执行非预期的操作。
- 例如:
- 假设你已经登录了一个网站,并且在这个网站上有一些敏感操作,比如更改密码或者转账。
- 一个 CSRF 攻击可能会在你访问一个恶意网站时,伪造一个请求到这个网站,尝试更改你的密码或者转账。
- 因为你已经登录了这个网站,所以这个请求可能会被这个网站接受,并执行这个操作。
(2)html示例
html
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
</head>
<body>
<form action="没被攻击的网站" method="post">
<input type="text" placeholder="请输入转账目标用户">
<input type="text" name="hiddenField" value="实际被更改的收款方" style="display: none;">
<input type="submit" value="Submit">
</form>
</body>
(3)防止 CSRF 攻击措施
- 使用 CSRF 令牌 :
- 在每个需要用户交互的请求中,服务器都会生成一个唯一的 CSRF 令牌,并将其嵌入到表单中。
- 当用户提交表单时,服务器会检查请求中的 CSRF 令牌是否与之前生成的令牌匹配。
- 如果不匹配,服务器就会拒绝这个请求。因为攻击者无法预测 CSRF 令牌,所以这可以有效地防止 CSRF 攻击。
- 检查 Referer 头 :
- 服务器可以检查每个请求的 Referer 头,以确保它来自于一个可信的源。
- 如果一个请求来自于一个不可信的源,服务器就可以拒绝这个请求。
- 使用 SameSite Cookie 属性 :
- 这是一个相对较新的方法,通过设置 Cookie 的 SameSite 属性,可以限制 Cookie 只能在同一个站点中使用。
- 这可以防止攻击者在其他站点中使用用户的 Cookie。
- 使用验证码 :
- 在执行敏感操作时,要求用户输入验证码,可以防止 CSRF 攻击,因为攻击者无法预测验证码。
- 使用双重 Cookie 验证:
- 在这种方法中,当用户登录时,服务器会发送两个 Cookie:一个是常规的会话 Cookie,另一个是 CSRF Cookie,它包含一个随机生成的 CSRF 令牌。
- 然后,每当用户发送一个修改数据的请求(例如 POST 请求)时,客户端都需要在请求的参数中包含这个 CSRF 令牌。
- 服务器会检查这个 CSRF 令牌是否与 CSRF Cookie 中的令牌匹配。如果不匹配,服务器就会拒绝这个请求。
【2】POST请求校验CSRF
- 首先取消settings文件中的注释
- 开启csrf检查
(1)form表单提交csrf
- 在form表单里面添加
{% csrf_token%}
html
<form action="" method="post">
{% csrf_token %}
<p>username: <input type="text" name="username"></p>
<p>password: <input type="password" name="password"></p>
<button class="" id="b1">提交</button>
</form>
- 前端源码会自动出现一个标签
- name是csrfmiddlewaretoken
- value是实时刷新的csrf令牌
html
<input type="hidden" name="csrfmiddlewaretoken" value="xKeMNRcsTZ1ws40kq8MXKSAfdsVIu9pVIwQZaOdYpAPyPXKXYr6CMYlVN8p5bbcn">
- 后端可以拿到的数据
- 堕落一个csrf令牌
python
<QueryDict: {'csrfmiddlewaretoken': ['xKeMNRcsTZ1ws40kq8MXKSAfdsVIu9pVIwQZaOdYpAPyPXKXYr6CMYlVN8p5bbcn'], 'username': [''], 'password': ['']}>
(2)使用ajax提交csrf
- 通过jquery语法捕获表单的内容传递给后端
html
<script>
$(document).ready(function () {
$("#b1").on('click', function (event) {
event.preventDefault();
$.ajax({
url: "",
type: "post",
data: {
username: $("input[name='username']").val(),
password: $("input[name='password']").val(),
csrfmiddlewaretoken: $("input[name='csrfmiddlewaretoken']").val(),
},
success: function () {
}
})
})
}
)
</script>
- 使用jquery的serialize()方法
html
<script>
$(document).ready(function () {
$("#b1").on('click', function (event) {
event.preventDefault();
let formData = $("form").serialize()
$.ajax({
url: "",
type: "post",
data: formData,
success: function () {
}
})
})
}
)
</script>
- 使用模板语法取值
html
<script>
$(document).ready(function () {
$("#b1").on('click', function (event) {
event.preventDefault();
$.ajax({
url: "",
type: "post",
data: {
username: $("input[name='username']").val(),
password: $("input[name='password']").val(),
csrfmiddlewaretoken: "{{ csrf_token }}",
},
success: function () {
}
})
})
}
)
</script>
(3)添加Js文件
- 在static文件夹内添加js文件
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);
}
}
});
- ajax发送
- 此时后端可以拿到传输的数据
- 不过csrf令牌不在以出现在request.POST中
html
{% load static %}
<script src="{% static 'js/csrf_check.js' %}"></script>
<script>
$(document).ready(function () {
$("#b1").on('click', function (event) {
event.preventDefault();
$.ajax({
url: "",
type: "post",
data: {
username: $("input[name='username']").val(),
password: $("input[name='password']").val(),
},
success: function () {
}
})
})
}
)
</script>
【3】CSRF装饰器
- 校验csrf总体有两种策略
- 网站整体都校验csrf,部分不校验csrf
- 网站整体都不校验csrf,部分校验csrf
(1)介绍
- 导入
python
from django.views.decorators.csrf import csrf_exempt, csrf_protect
- csrf_protect装饰器
- 以确保视图函数在处理 POST 请求时进行 CSRF 验证。
- 这对于某些视图函数来说是非常有用的,特别是当全局设置中禁用了 CSRF 中间件,但你仍然想要在某些特定的视图函数中启用 CSRF 保护。
- 如果请求中没有有效的CSRF令牌或令牌校验失败,Django将返回403 Forbidden响应。
- csrf_exempt装饰器
- 可以使特定的视图函数免于 CSRF 验证。
- 这在某些情况下是非常有用的,例如,当全局设置中启用了 CSRF 中间件,但你想要在某些特定的视图函数中禁用 CSRF 保护。
- 比如与第三方系统进行集成、开放API接口等
(2)FBV使用
- 没有特殊要求和特殊情况
- 直接使用即可
(3)CBV使用
- 在给CBV添加装饰器中
- 三种方式都可以使用
@csrf_protect
装饰器 - 针对
@csrf_exempt
装饰器只能给dispath
方法,其他无效
- 三种方式都可以使用
【六】中间件思想importlib
【1】中间件的导入方式
- 在settings配置文件中
- MIDDLEWARE列表里面添加自定中间件
- 整体是字符串格式
- 路径之间通过
.符号
连接 - 多个中间件之间以
,逗号
连接
- 优势
- 想要添加具有同样方法的类,直接创建文件以后,添加配置即可
- 不想使用某个类的方法了,直接注释掉配置的响应配置
- 总结
- 动态加载
- 灵活、可拓展
python
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',
]
【2】分析
-
这种方式中
-
在最后一个.点符号之前,都是文件路径
-
最后一个.点符号之后,就是类名了
-
这不就是
pythonfrom 路径 import 类名
-
-
有这么一个模块
importlib
- 他提供了动态导入模块的方式
- 即程序运行时,而不是在编译时
python
import importlib
# 动态导入 math 模块
math = importlib.import_module('math')
# 现在可以使用 math 模块了
print(math.sqrt(16)) # 输出:4.0
【3】使用反射函数和importlib
(1)模仿创建多个类文件
- my_class/qq.py
python
class QQ(object):
def send(self, content):
print(f"QQ发送消息:>>>{content}")
- my_class/dingding.py
python
class DingDing(object):
def send(self, content):
print(f"DingDing发送消息:>>>{content}")
- my_class/wechat.py
python
class WeChat(object):
def send(self, content):
print(f"WeChat发送消息:>>>{content}")
(2)模仿创建配置文件settings
- my_class/settings.py
python
MODEL_LIST = [
'dingding.DingDing',
'qq.QQ',
'wechat.WeChat',
]
(3)配置__init__.py
my_class/__init__.py
python
import os
import sys
from my_class import settings
import importlib
# 添加当前文件夹到工作目录
sys.path.append(os.path.dirname(__file__))
# 创建共同的方法接口
def send_all(content):
for path_str in settings.MODEL_LIST:
# 最右按照点符号侧切分一次,得到路径和类名
model_path, class_name = path_str.rsplit('.', maxsplit=1)
# 导入每个类的文件对象
model = importlib.import_module(model_path)
# 使用反射方法拿到类名
cls = getattr(model, class_name)
# 实例化类
obj = cls()
# 执行统一方法
obj.send(content)
if __name__ == '__main__':
send_all("hello world")
# DingDing发送消息:>>>hello world
# QQ发送消息:>>>hello world
# WeChat发送消息:>>>hello world
(4)外部文件使用
python
import my_class
my_class.send_all("Hi")
# DingDing发送消息:>>>Hi
# QQ发送消息:>>>Hi
# WeChat发送消息:>>>Hi