Django 中间件
Django中间件
Django
中间件会对所有的资源请求,所有的返回方式,所有的路由到视图的跳转、所有视图层的异常进行处理。
在Django
中,自带的有7个中间件,都具有不同的功能。
目前而言了解下面这两个即可。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # 插入session至数据表
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # 防止跨域请求
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
这里的每一个中间件其实都是一个模块,利用importlib
模块使之能够作为字符串进行导入。
并且,在这些自带的中间件中,都继承了MiddlewareMixin
类。
在该类中,提供了五个钩子方法,能够让我们对自定义中间件进行扩展。
中间件钩子函数 | 描述 |
---|---|
process_request | 所有请求来时都会运行的方法 |
process_view | 所有路由匹配成功之后,跳转执行视图函数之前都会运行该方法 |
process_exception | 所有视图中有异常发生时运行的方法 |
process_response | 所有返回页面响应时运行的方法 |
process_template_response | 返回的HttpResponse对象具有render属性时才会触发该方法 |
执行顺序
在Django
中,请求来时中间件的执行流程是自上而下,而进行响应时中间件的执行流程都是自下而上。
每个自带中间件中的钩子方法都会依次运行
不管你自定义多少中间件,永远都是这个流程。
不过需要注意的是,如果你自定义了一个中间件,并且对其中的process_requset
方法进行返回了HttpResponse
,那么会同级进行返回。不同于flask
,flask
则还是会至下而上进行返回。
自定义中间件
自定义中间件做下面三步即可:
1.任意目录下新建一个任意名称的
.py
文件夹2.在该文件下书写任意名称的类,但是一定要继承
MiddlewareMixin
,在该类下可以进行上面五种方法的覆写3.在
settings.py
中间件进行添加路径.类名
from django.utils.deprecation import MiddlewareMixin
如我在项目全局文件夹下新建了一个文件夹。叫CustomMiddleware
,并且在里面新建了一个py
文件customMid
。
在该文件下,新建了一个类Mid
。
那么我在注册的时候就直接添加上这个路径即可:
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',
'p1.CustomMiddleware.customMid.Mid', # 新增的自定义中间件
]
process_request
process_request
有一个参数,就是request
,这个request
和视图函数中的request
是一样的(在交给Django
后面的路由之前,对这个request
对象可以进行一系列的操作)。
返回值:默认为
None
,如果返回一个HttpResponse
对象,则将直接进行向上返回。如果是
HttpResponse
对象,Django
将不执行视图函数,而将相应对象返回给浏览器。
class Mid(MiddlewareMixin):
def process_request(self, request):
print("process_request")
process_response
该方法有两个参数。
多个中间件中的process_response
方法是按照MIDDLEWARE
中的注册顺序倒序执行的,也就是说第一个中间件的process_request
方法首先执行,而它的process_response
方法最后执行,最后一个中间件的process_request
方法最后一个执行,它的process_response
方法是最先执行。
class Mid(MiddlewareMixin):
def process_response(self, request, response):
print("process_response")
process_view
该方法有四个参数,Django
会在调用视图函数之前调用process_view
方法。
request
是HttpRequest
对象。
view_func
是Django
即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
view_args
是将传递给视图的位置参数的列表.
view_kwargs
是将传递给视图的关键字参数的字典。view_args
和view_kwargs
都不包含第一个视图参数(request
)。它应该返回
None
或一个HttpResponse
对象。如果返回
None
,Django
将继续处理这个请求,执行任何其他中间件的process_view
方法,然后在执行相应的视图。如果它返回一个
HttpResponse
对象,那么将不会执行Django
的视图函数,而是直接在中间件中掉头,倒叙执行一个个process_response
方法,最后返回给浏览器
class Mid(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
print("process_view")
process_exception
该方法两个参数,这个方法只有在视图函数中出现异常了才执行。
一个
HttpRequest
对象一个
exception
是视图函数异常产生的Exception
对象。它返回的值可以是一个
None
也可以是一个HttpResponse
对象。如果是
HttpResponse
对象,Django
将调用模板和中间件中的process_response
方法,并返回给浏览器,否则将默认处理异常。如果返回一个
None
,则交给下一个中间件的process_exception
方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
class Mid(MiddlewareMixin):
def process_exception(self, request, exception):
print("process_exception")
process_template_response
它有两个参数,由于执行条件很苛刻,所以用的非常少。
一个
HttpRequest
对象,一个response
对象。并且这个
response
是TemplateResponse
对象(由视图函数或者中间件产生)。
process_template_response
是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()
方法(或者表明该对象是一个TemplateResponse
对象或等价方法)。
def index(request): # 必须有render属性/方法,该中间件钩子方法才会执行
def render():
return HttpResponse("OK")
rep = HttpResponse("OK")
rep.render = render
return rep
class Mid(MiddlewareMixin):
def process_template_response(self, request, response): # 换而言之,response必须能点出render才行
print("process_template_response")
return response
执行流程
由于process_exception
以及process_template_response
的触发是有条件限制的,故此不再举例,记住他们的执行顺序是倒序即可。
实际应用
session白名单
由于所有的request
请求都会走这个,所以我们可以对其进行session
控制。
维护一个集合(也可以做一个非关系型数据库,放缓存中),放上不需要session
认证的url
,称之为白名单。
如果用户未进行登录就去访问不在白名单中的路径,则返回一个页面提示用户进行登录。
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class Mid(MiddlewareMixin):
def process_request(self,request):
whitelist = {"/","/admin/","/index/","/login/","/register/"} # 不需要登录就能访问的页面
target_url = request.path
for url in whitelist:
if target_url in url:
return
if not request.session.get("login"): # 如果未有session,代表未登录,跳转到登录页面
return redirect("/login/?next={0}".format(target_url))
def login(request):
""" 登录页面 """
target_url = request.GET.get("next",None)
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
if username == "Yunya" and password == "123456":
request.session["login"] = True
request.session.set_expiry(3600)
if not target_url:
return redirect("/index/") # 如果是直接点的登录页面,登陆完成后跳转到主页
else:
return redirect(target_url) # 否则跳转到从其他页面过来的
return render(request,"login.html",locals())
访问频率限制
某些IP
访问服务器的频率过高,进行拦截,比如限制每分钟不能超过10次。
如果要配合上面的白名单进行使用,这个应该注册在白名单上面。
import time
from django.shortcuts import redirect
from django.shortcuts import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class Mid2(MiddlewareMixin):
# 访问IP池
visit_ip_pool = {}
def process_request(self, request):
# 获取访问者IP
ip = request.META.get("REMOTE_ADDR")
# 获取访问当前时间
visit_time = time.time()
# 判断如果访问IP不在池中,就将访问的ip时间插入到对应ip的key值列表,如{"127.0.0.1":[时间1]}
if ip not in Mid2.visit_ip_pool:
Mid2.visit_ip_pool[ip] = [visit_time]
return
# 然后在从池中取出时间列表
history_time = Mid2.visit_ip_pool.get(ip)
# 循环判断当前ip的时间列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
while history_time and visit_time-history_time[-1] > 60:
history_time.pop()
# 如果访问次数小于10次就将访问的ip时间插入到对应ip的key值列表的第一位置,如{"127.0.0.1":[时间2,时间1]}
print(history_time)
if len(history_time) < 10:
history_time.insert(0, visit_time)
return None
else:
# 如果大于10次就禁止访问
return HttpResponse("访问过于频繁,还需等待%s秒才能继续访问" % int(60-(visit_time-history_time[-1])))
CSRF
跨域伪造请求
跨域伪造请求我举一个例子:
有一个钓鱼网站,和银行的转账页面一模一样。
但是唯一不同的地方在于,你在钓鱼网站上输好信息后点击提交,它并不会将对方卡号进行提交,而是将骗子卡号进行提交(隐藏的input
框)。这个时候银行后端收到这一条信息,你的钱就转到骗子哪儿去了。
如何解决这个问题?可以使用CSRF
来防止跨域伪造请求。
CSRF中间件
在Django
中,有一个中间件就是干这个事儿的,派发随机字符串,验证随机字符串。
'django.middleware.csrf.CsrfViewMiddleware',
我们打开它,并且在页面中添加上{% csrf_token %}
来获取到这一随机字符串,在页面上就会显示出来。
<form action="" method="POST">
{% csrf_token %}
<p><input type="text" placeholder="username" name="username"></p>
<p><input type="text" placeholder="password" name="password"></p>
<p><button type="submit">登录</button></p>
</form>
注意!这个标签会生成一个input
框,一定要将他放在form
表单中。
并且!每次刷新页面都会生成不同的字符串。
<input type="hidden" name="csrfmiddlewaretoken" value="yoW7bYRlhbHDcDI2KugGgHpNvjFsvZj47PNKGGXHbth2pCfITEul8NkJzN4xoUXI">
那么加上这个随机字符串后,就可以提交POST
请求了。
Ajax请求
Ajax
提交的话,该怎么做?
以下有三种办法。
方式一
通过获取隐藏的<input>
标签中的csrfmiddlewaretoken
值,放置在data
中发送。
$.ajax({
url: "http://127.0.0.1:8000",
type: "POST",
data: {
"username": "Yunya",
"password": 123456,
"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用JQuery取出csrfmiddlewaretoken的值,拼接到data中
},
success: function (data) {
console.log(data);
}
})
方式二
请求的键永远都是csrfmiddlewaretoken
,我们只要把value
输入为正确的随机字符串即可。
$.ajax({
url: "/http://127.0.0.1:8000/",
type: "POST",
data: {"username": "Q1mi", "password": 123456,"csrfmiddlewaretoken":"{{csrf_token}}"},
success: function (data) {
console.log(data);
}
})
方式三
通过静态文件,为所有ajax
发送请求时自动添加上csrftoken
及其随机字符串。
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);
}
}
});
前端使用时记得导入这个静态文件:
{% load static %}
<script src={% static 'js/csrf.js' %}>
$.ajax({
url: "/http://127.0.0.1:8000/",
type: "POST",
headers: {"X-CSRFToken": $.cookie('csrftoken')}, // 从Cookie取csrf_token,并设置ajax请求头
data: {"username": "Q1mi", "password": 123456},
success: function (data) {
console.log(data);
}
})
视图验证
我们可以在视图中,为某个函数单独设置需要csrf
校验,或者取消单独某个函数的csrf
校验。
需要导入以下两个模块。
from django.views.decorators.csrf import csrf_protect # 单独校验
from django.views.decorators.csrf import csrf_exempt # 取消校验
特别注意!如果你是使用CBV
,那么取消验证时只能这样设置csrf_exempt
:
@method_decorat(csrf_exempt,name="dispatch")
class Test(View):
def get(self,request):
pass
def post(self,request):
pass
关于如何为CBV
添加装饰器,你需要导入以下两个模块。
from django.views import View # 使用CBV的模块,必须继承该类
from django.utils.decorators import method_decorator # 添加装饰器的模块