Django的中间件
- 1.中间件的相关概述
- 2.自定义中间件
- 3.中间件版登录验证
- 4.访问频次限制
- 5.Django请求流程图
- 6.CSRF中间件
1.中间件的相关概述
1.1 中间件的引入
之前我们通过给视图函数加装饰器来判断是用户是否登录,把没有登录的用户请求跳转到登录页面。我们通过给几个特定视图函数加装饰器实现了这个需求。但是以后添加的视图函数可能也需要加上装饰器,这样是不是稍微有点繁琐。可以通过中间件来解决这个问题
1.2 中间件
- 官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
- 但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
- 说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在处理请求的特定的时间去执行这些方法。
1.3 Django中settings.py文件中的MIDDLEWARE配置项
#setting是文件中的中间件配置 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.自定义中间件
中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。
#定义两个中间件 from django.utils.deprecation import MiddlewareMixin #中间件MD1 class MD1(MiddlewareMixin): pass #中间件MD2 class MD2 (MiddlewareMixin): pass
#在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件: 'app01.middlewares.middleware_test.MD1', 'app01.middlewares.middleware_test.MD2'
1.1 process_request
class MD1(MiddlewareMixin): def process_request(self,request): print(id(request)) print('from MD1 process_request') # return HttpResponse("HHHHH") # return None #--------------------------------------------------------- class MD2 (MiddlewareMixin): def process_request(self, request): # print(id(request)) print('from MD2 process_request')
- 执行时间,在视图函数执行之前
- 参数:request ->> 跟视图函数中的是同一个
- 执行顺序:按照注册顺序,顺序执行
- 返回值:None:正常流程 / HttpResponse对象,不执行后面中间件的process_request方法,不执行视图函数,直接执行当前中间件的process_response方法,后面正常走
1.2 process_response
class MD1(MiddlewareMixin): def process_request(self,request): print(id(request)) print('from MD1 process_request') # return HttpResponse("HHHHH") # return None def process_response(self,request,response): # print(id(response)) print('from MD1 process_response') return response #---------------------------------------------------- class MD2 (MiddlewareMixin): def process_request(self, request): # print(id(request)) print('from MD2 process_request') def process_response(self, request, response): # print(id(response)) print('from MD2 process_response') return response
- 执行时间:在视图函数执行之后
- 参数:request -->> 与视图函数中的是同一个 /response-->>视图函数中传递的响应对象
- 执行顺序:按照注册顺序,倒叙执行
- 返回值:HttpResponse对象:必须是响应对象
1.3 process_view(self, request, view_func, view_args, view_kwargs)
参数:
- request -->> 跟视图函数中的是同一个
- view_func -->> 视图函数
- view_args -->> 视图函数的位置参数
- view_kwargs-->> 视图函数的关键字参数
执行顺序:按照注册顺序 顺序执行
返回值:None: 正常流程
HttpResponse对象:不执行后面中间中的process_view方法,不执行视图函数,直接执行最后一个中间件中的process_response方法,后面正常走
1.4
class MD1(MiddlewareMixin): def process_request(self,request): print(id(request)) print('from MD1 process_request') # return HttpResponse("HHHHH") # return None def process_response(self,request,response): # print(id(response)) print('from MD1 process_response') return response def process_view(self,request,view_func,view_args,view_kwargs): # print(view_func) # print(view_args) # print(view_kwargs) print('from MD1 process_view') # return HttpResponse('kkkkkk') def process_exception(self,request,exception): print ('from MD1 process_exception') print(exception) #------------------------------------------------------------------------- class MD2 (MiddlewareMixin): def process_request(self, request): # print(id(request)) print('from MD2 process_request') def process_response(self, request, response): # print(id(response)) print('from MD2 process_response') return response def process_view(self,request,view_func,view_args,view_kwargs): # print(view_func) # print(view_args) # print(view_kwargs) print('from MD2 process_view') def process_exception(self, request, exception): print('from MD2 process_exception') print(exception) # return HttpResponse('SSSSSS')
参数:
- request -->> 跟视图函数中的是同一个
- exception-->> 异常对象
执行顺序:按照注册顺序 倒叙执行
返回值:None: 正常流程
HttpResponse对象:不执行后面中间中的process_exception方法,直接执行最后一个中间件中的process_response方法,后面正常走
1.5
#在MD1下写 def process_template_response(self,request,response): print('from MD1 process_template_response') return response
参数:request -->> 跟视图函数中的是同一个
response -->> 视图函数中传递的响应对象
执行顺序:按照注册顺序 倒叙执行
返回值:必须返回response对象
1.6 五种方法的执行方向
3.中间件版登录验证
(1)middlewares.py
#除此之外还需在setting.py文件中进行注册
class LoginMD(MiddlewareMixin): white_lst = ['/login/',] #白名单 black_lst = ['/black/',] #黑名单 def process_request(self,request): return_url = request.path_info #黑名单限制访问 if return_url in self.black_lst: return HttpResponse('This is an illegal URL ') #白名单或者登录用户不做限制 elif return_url in self.white_lst or request.session.get('user'): return None else: return redirect('/login/?return_url={}'.format(return_url))
(2) views.py
from django.shortcuts import render, HttpResponse,redirect def index(request): return HttpResponse('index页面') def home(request): return HttpResponse('home页面') def login(request): if request.method =="POST": user = request.POST.get('user') pwd = request.POST.get('pwd') if user == 'wcx' and pwd == '123': #设置session request.session['user'] = user #获取跳到登录页面之前的URL return_url = request.GET.get('return_url') #如果有就跳转到登录之前请求网址的URL if return_url: return redirect(return_url) #否则跳转到的index页面 else: return redirect('/index/') return render(request,'login.html') def logout(request): # 删除所有当前请求相关的session request.session.delete() return redirect("/login/")
(3)urls 文件
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.login, name='login'), url(r'^index/$', views.index, name='index'), url(r'^home/$', views.home, name='home'),
(4) 相关要求
AuthMD中间件注册后,所有的请求都要走AuthMD的process_request方法。 如果URL在黑名单中,则返回This is an illegal URL的字符串; 访问的URL在白名单内或者session中有user用户名,则不做阻拦走正常流程; 正常的URL但是需要登录后访问,让浏览器跳转到登录页面。 注:AuthMD中间件中需要session,所以AuthMD注册的位置要在session中间的下方。
4.访问频次限制
练习: 基于中间件实现访问频率限制功能,每5秒同一用户最多访问3次
4.1 用户是基于IP去区分
import time visit_record={} class Limit(MiddlewareMixin): def process_request(self,request): #获取ip ip = request.META.get('REMOTE_ADDR') # 获取访问记录(初始设置为空) history = visit_record.get(ip,[]) if not history: visit_record[ip] =history #获取当前时间 now_time = time.time() #临时列表用于存储要从history中删除的访问记录 new_record=[] for i in history: #当新访问时的时间与以前访问记录的时间差大于5秒,将记录添加到临时的列表里 if now_time-i >5: new_record.append(i) #将原来的访问记录列表中 for i in new_record: history.remove(i) #当访问记录中记录条数大于三条 if len(history)>=3: #将访问进行限制 return HttpResponse('访问频次太快') else: #当小于三条记录,就可以继续访问 history.insert(0,now_time)
优化:
import time visit_record={} class Limit(MiddlewareMixin): def process_request(self,request): #获取ip ip = request.META.get('REMOTE_ADDR') # 获取访问记录(初始设置为空) history = visit_record.get(ip,[]) if not history: visit_record[ip] =history #获取当前时间 now_time = time.time() while history and now_time - history[-1] > 5: history.pop() if len(history)>=3: #将访问进行限制 return HttpResponse('访问频次太快') else: #当小于三条记录,就可以继续访问 history.insert(0,now_time)
4.2 用户是基于session 区分(此时不同浏览器都可以访问)
import time class Limit(MiddlewareMixin): def process_request(self,request): #获取访问记录 history = request.session.get('history',[]) #获取当前时间 now_time = time.time() while history and now_time - history[-1]>5: history.pop() if len(history)>=3: return HttpResponse('访问频次太快') history.insert(0,now_time) request.session['history'] = history
5.Django请求流程图
6.CSRF中间件
csrf(跨站请求伪造)
6.1 两个关于csrf的装饰器
#在django的views文件中导入两个装饰器 from django.views.decorators.csrf import csrf_exempt,csrf_protect #当没有将csrf中间件注释掉时 #给视图加上装饰器后,当前的视图不需要CSRF校验 @csrf_exempt #将csrf中间件注释掉后 #给视图加上装饰器后,当前的视图需要CSRF校验 @csrf_protect
6.2 关于csrf的中间件
'django.middleware.csrf.CsrfViewMiddleware'
CsrfViewMiddleware的源码相关
class CsrfViewMiddleware(MiddlewareMixin): #与普通的中间件相同,都是一个继承MiddlewareMixin 的类 #主要来说类中同样定义了 process_request 和process_view方法
(1)process_request
#1. 在globaling settings中CSRF_COOKIE_NAME = 'csrftoken' cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] #2.将csrf token的值添加到 request.META中 request.META['CSRF_COOKIE'] = csrf_token
(2) process_view
- 为视图函数加装饰器,csrf_exempt,不进行CSRF校验
if getattr(callback, 'csrf_exempt', False): return None
- 请求方式 是'GET', 'HEAD', 'OPTIONS', 'TRACE' 也不进行校验
if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if getattr(request, '_dont_enforce_csrf_checks', False):
- 此时的csrf_token
csrf_token = request.META.get('CSRF_COOKIE')
- 当csrf 存在时候
if request.method == "POST": try: request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
if request_csrf_token == "": # Fall back to X-CSRFToken, to make things easier for AJAX, # and possible for PUT/DELETE. request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
# 获取提交的csrfmiddlewaretoken的值 request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') 如果或许不到csrfmiddlewaretoken的值 再尝试从请求头中获取X_CSRFTOKEN的值 —— 》request_csrf_token
- 将 request_csrf_token 与 csrf token进行比较,成功则通过校验,不成功则拒绝