中间件
1. Django中间件介绍
【1】什么是Django中间件
Django中间件是一个轻量级、可重用的组件,用于处理Django请求和响应的过程。
它提供了对请求和响应进行全局处理的机制,可以在请求达到视图之前进行预处理或在响应返回给客户端之前进行后处理。
中间件是按照顺序依次执行的,每个中间件都可以对请求和响应进行修改、补充或处理。
在Django的settings.py配置文件中,通过MIDDLEWARE设置来定义中间件的顺序。
【2】作用:
认证和授权:
中间件可以在请求到达视图之前进行用户认证和权限验证,确保只有经过授权的用户才能访问敏感资源。
请求和响应处理:
中间件可以在请求到达视图之前对请求进行预处理
例如添加请求头信息、检查请求参数的合法性等操作。
同时,在视图函数返回响应给客户端之前,中间件还可以对响应进行后处理
例如添加额外的响应头、包装响应数据等操作。
异常处理:
中间件还可以捕获视图函数中可能抛出的异常,并做相应的处理
例如记录异常日志、返回自定义错误信息等。
性能优化:
通过中间件,可以对请求进行性能监测、缓存处理、压缩响应等操作,提升网站的整体性能。
【二】Django中间件是Django的门户
请求发来的时候需要先经过中间件才能到达真正的Django后端
响应返回的时候,最后也需要进过中间件返回发送出去
【三】Django中间件源码分析
【1】默认的五个中间件详解
(1)SecurityMiddleware
django.middleware.security.SecurityMiddleware:
安全中间件负责处理与网站安全相关的任务
例如设置HTTP头部,防止跨站脚本攻击(XSS),点击劫持等。
它可以通过配置自定义安全策略来确保网站的安全性。
(2)SessionMiddleware
django.contrib.sessions.middleware.SessionMiddleware:
会话中间件负责处理用户会话的创建之间存储和检索用户数据。
它基于浏览器提供的Cookie或URL传递的会话ID进行会话跟踪,并将会话数据存储在后端数据库或缓存中,以实现用户状态的跨请求保持。
(3)CommonMiddleware
django.middleware.common.CommonMiddleware:
通用中间件提供了一些常见而关键的HTTP请求处理功能
例如,根据请求的HTTP头信息设置语言、时区等。
此外,它还处理静态文件的serving,包括收集静态文件,为其生成URL,并在开发模式下提供静态文件的serving。
(4)CsrfViewMiddleware
django.middleware.csrf.CsrfViewMiddleware:
CSRF(Cross-Site Request Forgery)中间件用于防止跨站请求伪造攻击。
它在每个POST请求中验证一个CSRF标记,确保请求是通过合法的表单提交得到的,从而保护用户免受恶意站点的攻击。
(5)AuthenticationMiddleware
django.contrib.auth.middleware.AuthenticationMiddleware:
认证中间件负责处理用户身份认证相关的任务
例如将认证信息关联到请求对象上,为每个请求提供一个user对象,以便在请求处理过程中轻松地获取和使用用户身份信息。
(6)MessageMiddleware
django.contrib.messages.middleware.MessageMiddleware:
消息中间件用于在请求处理过程中存储和传递临时的、一次性的用户消息。
它允许在HTTP重定向之间跨请求传递消息,例如成功或错误提示,以改善用户体验。
(7)XFrameOptionsMiddleware
django.middleware.clickjacking.XFrameOptionsMiddleware:
点击劫持中间件用于防止页面被嵌入到其他网站中,从而提供一定的点击劫持保护。
它通过设置X-Frame-Options HTTP头部来限制页面的显示方式,从而防止恶意网页通过iframe等方式嵌入当前网页。
2. 中间件方法
2.1 必须掌握的中间件方法
(1)process_request:
(1)执行顺序
- 请求来的时候需要经过每一个中间件的 process_request 方法
- 结果的顺序是按照配置文件中注册的中间件从上往下的顺序执行的
(2)没有定义process_request
- 如果没有定义这个方法,就跳过这个中间件
(3)定义了返回值
- 如果在自定义中间件中定义了返回值(三板斧),那么请求将不再继续执行,而是直接原路返回(校验失败不允许访问)
(4)总结
- process_request 方法就是用来 做全局相关的所有限制功能
- 该方法在每个请求到达视图之前被调用,可以对请求进行预处理。
- 例如,进行身份验证、访问控制或请求日志记录等操作。
- 它接收一个HttpRequest对象作为参数,并且没有返回值。
示例:
class AuthenticationMiddleware:
def process_request(self, request):
# 在这里进行身份验证操作
if not request.user.is_authenticated:
# 如果用户未经身份验证,则返回HttpResponse或重定向到登录页面
(2)process_response:
- 响应被返回的时候需要结束每一个中间件里面的 process_response 方法
- 该方法有两个额外的参数
- request
- response
- 该方法必须返回 HttpResponse 对象
- 默认是response
- 支持自定义
- 顺序是按照配置文件中注册过的中间件从下往上依次经过
- 如果没有定义,则跳过,校验下一个
- 该方法在每个请求结束并且响应返回到客户端之前被调用。
- 可以在此处对响应进行处理
- 例如添加额外的头信息、修改响应内容等。
- 它接收一个HttpRequest对象和HttpResponse对象作为参数,并且必须返回一个HttpResponse对象。
示例:
class CustomResponseMiddleware:
def process_response(self, request, response):
# 在这里对响应进行处理
response['X-Custom-Header'] = 'Custom Value'
return response
2.2 需要了解的中间件方法:
(1)process_view:
- 路由匹配成功后执行视图函数之前
- 会自动执行中间件里面的该方法
- 顺序是按照配置文件中注册的中间件从上而下的顺序执行
- 该方法在请求到达视图之前被调用,在视图函数执行前执行。
- 可以在此处进行一些操作
- 如修改请求参数或进行记录等。
- 它接收一个HttpRequest对象和一个视图函数作为参数,并且可以返回一个HttpResponse对象或None。
示例:
class LoggingMiddleware:
def process_view(self, request, view_func, view_args, view_kwargs):
# 在这里记录日志
logger.info(f"Request received: {request.path}")
# 返回None,继续执行原视图函数
return None
(2)process_template_response:
- 返回的 HttpResponse 对象有 render 属性的时候才会触发
- 顺序是按照配置文件中注册了的中间件从下往上依次经过
- 该方法在视图函数返回一个TemplateResponse对象时调用。
- 可以在此处修改模板响应
- 例如添加全局的上下文数据或进行额外的渲染操作。
- 它接收一个HttpRequest对象和一个TemplateResponse对象作为参数,并且必须返回一个TemplateResponse对象。
示例:
class GlobalContextMiddleware:
def process_template_response(self, request, response):
# 在这里添加全局的上下文数据
response.context_data['global_data'] = "Global Value"
return response
(3)process_exception:
- 当视图函数中出现异常的情况下触发
- 顺序是按照配置文件中注册了的中间件从下往上依次经过
- 该方法在视图函数抛出异常时被调用。
- 可以在此处捕获异常并进行处理
- 例如返回一个定制的错误页面或进行日志记录等。
- 它接收一个HttpRequest对象和一个异常对象作为参数,可以返回一个HttpResponse对象来替代原始的异常响应。
示例:
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")
3. 自定义中间件
3.1 process_request`
【1】路由层
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/',views.index),
]
【2】视图层
def index(request):
print("这是视图函数index")
return HttpResponse("index 的返回值")
【3】配置文件
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.mymiddle.my_middle.MyMiddle',
# 谁先注册就先执行谁
'app01.mymiddle.my_middle.MyMiddle2',
]
【4】自定义中间件
# 引入父类
from django.utils.deprecation import MiddlewareMixin
class MyMiddle(MiddlewareMixin):
def process_request(self, request):
print("这是第一个自定义中间件中的 process_request 方法")
class MyMiddle2(MiddlewareMixin):
def process_request(self, request):
print("这是第二个自定义中间件中的 process_request 方法")
4. 总结
(1)执行顺序
- 请求来的时候需要经过每一个中间件的 process_request 方法
- 结果的顺序是按照配置文件中注册的中间件从上往下的顺序执行的
(2)没有定义process_request
- 如果没有定义这个方法,就跳过这个中间件
(3)定义了返回值
- 如果在自定义中间件中定义了返回值(三板斧),那么请求将不再继续执行,而是直接原路返回(校验失败不允许访问)
(4)总结
- process_request 方法就是用来 做全局相关的所有限制功能
5. pathlib模块
【一】引入
我们在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',
]
这种路径构造方式,我们就可以通过importlib模块实现
【二】推导过程
以多平台发送消息为例
【1.0】引入
定义一个包
def wechat(content):
print(f"wechat接收到的通知:>>>{content}")
def QQ(content):
print(f"QQ接收到的通知:>>>{content}")
def email(content):
print(f"email接收到的通知:>>>{content}")
启动文件中启动包
from send_message import *
def send_all(content):
wechat(content=content)
QQ(content=content)
email(content=content)
if __name__ == '__main__':
send_all("这是一条测试信息")
wechat接收到的通知:>>>这是一条测试信息
QQ接收到的通知:>>>这是一条测试信息
email接收到的通知:>>>这是一条测试信息
【2.0】升级
(1)功能部分
先分别创建不同的消息功能文件
在同一个文件夹下
创建三个功能文件
class WeChat(object):
def __init__(self):
# 发送消息前的准备工作
# 比如掉接口/初始化配置等
pass
def send(self, content):
print(f"WeChat 发送的消息 :>>>{content}")
class QQ(object):
def __init__(self):
# 发送消息前的准备工作
# 比如掉接口/初始化配置等
pass
def send(self, content):
print(f"QQ 发送的消息 :>>>{content}")
class email(object):
def __init__(self):
# 发送消息前的准备工作
# 比如掉接口/初始化配置等
pass
def send(self, content):
print(f"email 发送的消息 :>>>{content}")
在上面的文件内创建初始化文件
import settings
import importlib
def send_all(content):
# 拿到每一个包的路径
for path_str in settings.MODEL_LIST:
model_path, class_name = path_str.rsplit('.', maxsplit=1)
# model_path : model.email
# class_name : email
# (1)利用字符串导入模块
# models : 模块对象
models = importlib.import_module(model_path)
# (2)利用反射拿到类名
cls = getattr(models, class_name)
# (3)生成类的对象
obj = cls()
# (4)利用鸭子类型直接调用send发送消息
obj.send(content)
if __name__ == '__main__':
send_all('1')
(3)调用部分
在外部定义一个配置文件
MODEL_LIST = [
'model.email.email',
'model.QQ.QQ',
'model.WeChat.WeChat',
]
在外部的真正功能文件
import model
model.send_all('这是测试消息')
email 发送的消息 :>>>这是测试消息
QQ 发送的消息 :>>>这是测试消息
WeChat 发送的消息 :>>>这是测试消息
(4)小结
遵从Python中的鸭子类型
可以在功能文件中自定义功能文件添加或者注释
在 settings.py 文件中相关的路径注释掉或添加上去即可
6. CSRF跨域请求伪造
【一】csrf跨站请求伪造详解
CSRF(Cross-Site Request Forgery)跨站请求伪造是一种常见的网络攻击方式。
攻击者通过诱导受害者访问恶意网站或点击恶意链接
将恶意请求发送到目标网站上
利用受害者在目标网站中已登录的身份来执行某些操作
从而达到攻击的目的。
举个例子
假设受害者在一家网银网站上登录账户,然后继续浏览其他网页。
同时,攻击者通过电子邮件等方式向受害者发送了一封包含恶意链接的邮件。
当受害者点击该链接时,潜在的威胁就会变得非常现实。
该链接指向一个由攻击者操纵的网站,该网站上的恶意代码会自动向网银网站发送一个请求,请求转账到攻击者的账户。
由于受害者在网银网站中已经登录,所以该请求会被认为是合法的,这样攻击者就可以成功地进行转账操作。
要保护自己免受CSRF攻击,网站开发者可以采取以下措施:
使用CSRF令牌:
在用户的请求中添加随机生成的令牌,并将该令牌保存在用户会话中。
每次提交请求时都会验证该令牌,以确保请求是合法的。
启用SameSite属性:
将Cookie的SameSite属性设置为Strict或Lax,以限制跨站请求。
这可以在一定程度上缓解CSRF攻击。
严格验证请求来源:
服务器端可以验证请求的来源是否为预期的网站域名
例如检查HTTP Referer头部。
使用验证码:
在敏感操作(如转账、更改密码等)上使用验证码
增加用户身份验证的防护。
【二】csrf跨域请求伪造
钓鱼网站
搭建一个类似正规网站的页面
用户点击网站链接,给某个用户打钱
打钱的操作确确实实提交给了中国银行的系统,用户的钱也确实减少
但是唯一不同的是,账户打钱的账户不是用户想要打钱的目标账户,变成了其他用户
内部本质
在钓鱼网站的页面针对对方账户,只给用户提供一个没有name属性的普通input框
然后在内部隐藏一个已经写好带有name属性的input框
如何避免上面的问题
csrf跨域请求伪造校验
网站在给用户返回一个具有提交数据功能的页面的时候会给这个页面加一个唯一标识
当这个页面后端发送post请求的时候,我们后端会先校验唯一标识
如果成功则正常执行
如果唯一标识不符合则拒绝连接(403 forbidden)
【三】csrf校验
【介绍】
csrf校验是一种用于防止跨站请求伪造(Cross-Site Request Forgery)攻击的安全措施。
form表单中进行csrf校验:
添加CSRF Token字段:
在form表单中添加一个隐藏字段,用于存储CSRF Token的值。
后端服务器在渲染表单时生成一个CSRF Token,并将其存储在会话中或者以其他方式关联到当前用户。
当用户提交表单时,前端将CSRF Token的值包含在请求中。
后端在验证表单数据时,检查请求中的CSRF Token是否与存储的Token匹配,如果不匹配,则拒绝请求。
设置Cookie:
后端服务器在渲染表单时,在客户端设置一个包含随机生成的CSRF Token的Cookie。
当用户提交表单时,表单数据会被一同发送到服务器,并自动包含该Cookie。
后端在验证表单数据时,检查请求中的CSRF Token是否与Cookie中的值匹配,如果不匹配,则拒绝请求。
双重Cookie校验:
后端服务器在渲染表单时,在Cookie中设置一个随机生成的CSRF Token,并将其存储在会话中或以其他方式关联到当前用户。
当用户提交表单时,表单数据会被一同发送到服务器,请求头或请求参数中携带一个包含CSRF Token的自定义字段。
后端在验证表单数据时,同时检查请求中的CSRF Token和Cookie中的值是否匹配,如果不匹配,则拒绝请求。
【1】form表单如何校验
在form表单上面加上csrf_token
{% csrf_token %}
<form action="" method="post">
<p>username:<input type="text" name="username"></p>
<p>transfer_user<input type="password" name="password"></p>
<p>money<input type="text" name="money"></p>
<input type="submit">
</form>
在页面标签中会自动出现一个标签
<input type="hidden" name="csrfmiddlewaretoken" value="zQaNPZsy1tVmLdqC7GIDOOOfR7yT9YfO58lJ5yrjZfTw2edZTrVYUllOVMnkwXKe">
【2】ajax如何校验
方式一
利用标签查找获取页面上的随机字符串
键必须叫 csrfmiddlewaretoken
<button id="b1">ajax请求提交</button>
<script>
$("#b1").click(function () {
$.ajax({
url: '',
type: 'post',
// (1) 利用标签查找获取页面上的随机字符串
data: {
"username": "dream",
"csrfmiddlewaretoken":$("input[name='csrfmiddlewaretoken']").val()
},
success: function () {
}
})
})
</script>
方式二
利用模板语法进行快捷引入
<button id="b1">ajax请求提交</button>
<script>
$("#b1").click(function () {
$.ajax({
url: '',
type: 'post',
// (2) 利用模板语法提供的快捷书写
data: {"username": "dream", "csrfmiddlewaretoken": "{{ csrf_token }}"},
success: function () {
}
})
})
</script>
方式三
定义一个js文件并引入
导入该配置文件之前,需要先导入jQuery,因为这个配置文件内的内容是基于jQuery来实现的
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);
}
}
});
<button id="b1">ajax请求提交</button>
<script>
$("#b1").click(function () {
$.ajax({
url: '',
type: 'post',
// (3) 定义外部js文件并引入到本地
data: {"username": "dream"},
success: function () {
}
})
})
</script>
7. CSRF验证装饰器
########### CSRF相关的装饰器验证
from django.views.decorators.csrf import csrf_protect, csrf_exempt
# csrf_protect:给视图函数添加CSRF验证保护
# csrf_exempt:取消视图的csrf验证保护
# @csrf_protect
# @csrf_exempt
# def login(request):
# if request.method == "POST":
# return HttpResponse("post")
# return render(request, "login.html", locals())
###CBV添加csrf验证装饰器
# csrf_protect:给视图函数添加CSRF验证保护
# csrf_exempt:取消视图的csrf验证保护
# @method_decorator(csrf_exempt, name="post") # 取消验证保护器在类视图上面无效
# class Login(View):
# # @method_decorator(csrf_exempt) # 放在类中重写的 dispatch方法生效
# def dispatch(self, request, *args, **kwargs):
# return super().dispatch(request, *args, **kwargs)
#
# def get(self, request, *args, **kwargs):
# return render(request, "login.html", locals())
#
# # @method_decorator(csrf_exempt) # 取消验证保护器在类中的视图函数上面无效
# def post(self, request, *args, **kwargs):
# return HttpResponse("post")
# @method_decorator(csrf_protect, name="post") # 增加验证保护器在类视图上面有效
class Login(View):
@method_decorator(csrf_protect) # 放在类中重写的 dispatch方法生效
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return render(request, "login.html", locals())
# @method_decorator(csrf_protect) # 增加验证保护器在类中的视图函数上面有效
def post(self, request, *args, **kwargs):
return HttpResponse("post")
8. Auth模块
- 命令行创建超级管理员
python3.10 manage.py createsuperuser
- 代码创建
from django.contrib import auth
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
def register(request):
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
confirm_password = request.POST.get("confirm_password")
# 校验两次密码是否一致
if password != confirm_password:
return HttpResponse("两次密码不一致")
# 用户注册
# 如果走自己的模型表语句是不成立的,创建的密码是明文
# obj = User.objects.create(username=username, password=password)
# 创建普通用户
obj = User.objects.create_user(username=username, password=password)
# 创建超级管理员
# obj = User.objects.create_superuser(username=username, password=password)
print(obj)
return redirect("login")
return render(request, "register.html", locals())
def login(request):
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
# 直接调用原始的查询语句是不成立的,因为密码是秘文
# User.object.filter(username=username)
user_obj = auth.authenticate(username=username, password=password)
# print(user_obj)
# 保存用户状态,auth.login(request, user_obj)
if not user_obj:
return HttpResponse("密码不正确")
auth.login(request, user_obj)
return render(request, "login.html", locals())
def home(request):
# 确保当前用户是已经登陆过后的用户才能访问
# print(request.user)
# request.user获取到的是上面保存用户状态的用户对象
# 正常登陆获取的是上面保存的user_obj对象
# AnonymousUser匿名用户
# 第二种获取用户对象的登陆状态 request.user.is_authenticated
# 返回的是布尔值,登陆过是true,没有登陆过是false
is_user_login = request.user.is_authenticated
print(is_user_login)
if is_user_login:
return HttpResponse("home")
# 第一种方法
# auth有写好的登陆认证装饰器
# @login_required(login_url='login') # 局部的登陆认证装饰器
# def index(request):
# return HttpResponse("index")
# 第二种全局settings配置登陆路由
@login_required
def index(request):
return HttpResponse("index")
# 注销
@login_required
def logout(request):
# auth.logout(request) auth自带的注销用户登陆状态
auth.logout(request)
return redirect("login")
@login_required
def change_info(request):
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
confirm_password = request.POST.get("confirm_password")
new_password = request.POST.get("new_password")
# 校验两次密码是否一致
if password != confirm_password:
return HttpResponse("两次密码不一致")
# 校验原来的,密码是否一致
if not request.user.check_password(password):
return HttpResponse("密码不正确!")
# 修改密码
request.user.set_password(new_password)
# 修改完密码需要sava保存
request.user.save()
return redirect("login")
return render(request, "change_info.html", locals())
9. 基于原来的Auth表扩展新字段
【1】方式一
from django.db import models
from django.contrib.auth.models import User, AbstractUser
# Create your models here.
# 扩展 auth_user 表
# 第一种方式 : 一对一关系(不推荐)
class UserDetail(models.Model):
phone = models.CharField(max_length=32)
user = models.OneToOneField(to='User', on_delete=models.CASCADE)
【2】方式二
from django.db import models
from django.contrib.auth.models import User, AbstractUser
# 第二种方式 : 面向对象的继承
class UserInfo(AbstractUser):
'''
如果继承了AbstractUser
那么在执行数据库迁移命令的时候,auth_user表就不会被创建
而 UserInfo 会在 auth_user表 的基础上添加自定义扩展的字段
优点:
直接通过自己定义的表快速完成操作及扩展
前提
(1)在执行之前没有执行过数据库迁移命令
auth_user 表没有被创建
如果当前库已经被创建,则需要更换新的库
(2)继承的表里面不要覆盖 AbstractUser 里面的字段名
表里面有的字段不要动,只扩展额外的字段即可
(3)需要再配置文件中声明Django要使用 UserInfo 替代 auth_user
AUTH_USER_MODEL = 'app01.UserInfo' ---'应用名.表名'
'''
phone = models.CharField(max_length=32)
需要再配置文件中声明 Django 要使用 UserInfo 替代 auth_user
AUTH_USER_MODEL = 'app01.UserInfo' ---'应用名.表名'
如果自己写表代替了 auth_user
auth模块功能正常使用,参考的表也由 auth_user 变成了 UserInfo
标签:请求,中间件,request,Auth,django,模块,csrf,def
From: https://www.cnblogs.com/Formerly/p/18102498