之前说了可以通过加装饰器来完成用户的登录验证,如果没有登录就跳转到登录页面。登陆完后再跳转回用户想访问的页面。但是以后还有很多的视图函数需要加装饰器,特别麻烦。正好,Django的中间件给我们提供了一种全局的控制效果。
什么是中间件?
中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
中间件类似于django的门卫,数据在进入和离开时都需要经过中间件 。
它能用来控制用户访问频率,做全局登陆校验,用户访问白名单,黑名单等……
需要注意的是,中间件是框架级的,它往往作用于全局,如果用的不当会影响整体代码的性能,因此要慎用。
中间件在配置文件settings中的配置文件:
MIDDLEWARE配置项是一个列表(列表是有序的),列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。
django默认有七个中间件,但是django暴露给用户可以自定义中间件并且里面可以写五种方法。
拿出其中的中间件并修改其格式,进去看一眼源码:
from django.middleware.security import SecurityMiddleware
发现基本都有5种方法:
- process_request:请求来的时候从上往下依次执行每一个中间件里面的process_request
- process_response:响应走的时候会从下往上依次执行每一个中间件里面的process_response
- process_view:路由匹配成功,执行视图函数之前自动触发(从上往下依次执行)
- process_exception:当视图函数报错的时候自动触发(从下往上依次执行)
- process_template_response:视图函数返回的对象中有render()方法的时候触发。。或者标明该对象是一个TemplateResponse对象或等价方法(从下往上依次执行)
执行顺序:
一、自定义中间件
先在应用下新建一个任意名称的文件夹,在此文件夹下新建一个任意名称的文件。
然后要导入:from django.utils.deprecation import MiddlewareMixin
最后要在settings中注册
自定义的中间件:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
def process_request(self, request):
print('我是M1里面的process_request方法')
def process_response(self, request, response):
print('我是M1里面的process_response方法')
return response # 必须将response形参接收到的数据返回,否则直接报错
def process_view(self, request, view_func, view_args, view_kwargs):
print('我是M1里面的process_view方法')
print(view_func.__name__, view_func)
def process_exception(self, request, exception):
print('我是M1里面的process_exception方法')
print(exception)
def process_template_response(self,request,response):
print('我是M1里面的process_template_response方法')
return response
class M2(MiddlewareMixin):
def process_request(self, request):
print('我是M2里面的process_request方法')
def process_response(self, request, response):
print('我是M2里面的process_response方法')
return response # 必须将response形参接收到的数据返回,否则直接报错
def process_view(self, request, view_func, view_args, view_kwargs):
print('我是M2里面的process_view方法')
print(view_func.__name__, view_func)
def process_exception(self, request, exception):
print('我是M2里面的process_exception方法')
print(exception)
def process_template_response(self,request,response):
print('我是M2里面的process_template_response方法')
return response
process_request方法:
process_request有一个参数,就是request,这个request和视图函数中的request是一样的(在交给Django后面的路由之前,对这个request对象可以进行一系列的操作)。
由于request对象是一样的,所以我们可以对request对象进行一系列的操作,包括request.变量名=变量值,这样的操作,我们可以在后续的视图函数中通过相同的方式即可获取到我们在中间件中设置的值。
它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
总结一下:
- 中间件的process_request方法是在执行视图函数之前执行的。
- 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
- 不同中间件之间传递的request都是同一个对象
process_response方法:
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行。
定义process_response方法时,必须给方法传入两个形参,request和response。request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象(也就是说这是Django后台处理完之后给出一个的一个具体的视图)。该方法的返回值(必须要有返回值)也必须是HttpResponse对象。如果不返回response而返回其他对象,则浏览器不会拿到Django后台给他的视图,而是我的中间件中返回的对象。
process_view方法:
process_view(self, request, view_func, view_args, view_kwargs)
该方法有四个参数:
request是HttpRequest对象。
view_func是Django即将调用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
view_args是将传递给视图的位置参数的列表.
view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。
Django会在调用视图函数之前调用process_view方法。
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,那么将不会执行Django的视图函数,而是直接在中间件中掉头,倒叙执行一个个process_response方法(从下面最后一个中间件开始),最后返回给浏览器
process_exception方法:
process_exception(self, request, exception)
该方法两个参数:
一个HttpRequest对象
一个exception是视图函数异常产生的Exception对象。
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
process_template_response方法:
process_template_response(self, request, response)
它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
它要调用的视图函数要有一个render()方法:
def index(request): print("app01 中的 index视图") def render(): print("in index/render") return HttpResponse("O98K") rep = HttpResponse("OK") rep.render = render return rep
利用中间件,实现登录认证装饰器的功能:
自定义一个登录认证的中间件:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class Check_Login(MiddlewareMixin):
def process_request(self,request):
next_url=request.path_info
if not next_url.startswith('/login/'):
is_login=request.session.get('login','')
if not is_login:
return redirect('/login/?next={}'.format(next_url))
二、csrf跨站请求伪造
一开始我们对csrf这个中间件是注释的,因为在提交post请求的时候,会报错。
如果没有这个csrf的认证,那么就会有一定的风险。比如钓鱼网站。
某个恶意的网站上有一个指向你的网站的链接,如果某个用户已经登录到你的网站上了,那么当这个用户点击这个恶意网站上的那个链接时,就会向你的网站发来一个请求,你的网站会以为这个请求是用户自己发来的,其实呢,这个请求是那个恶意网站伪造的。
为了避免上面情况的出现,Django引用了CSRF防护机制;Django第一次响应来自某个客户端的请求时,会在服务器端随机生成一个 token,并把这个 token 放在 cookie 里。然后每次 POST 请求都会带上这个 token,这样就能避免被 CSRF 攻击。如果POST请求中没有token随机字符串,则返回403拒绝服务
如何去写这个特殊的字符串呢?模版语法有一个固定的写法{% csrf_token %},必须写在form表单内
我们通过代码来模拟一下 钓鱼网站:
先来搞一个页面:这里指定往正规页面提交数据,其实我自己启动的是8001端口。others框只是给用户看个乐呵,实际上提交的时候,对方账户就是下面属性为display:none的标签。
<h1>钓鱼网站</h1>
<form action="http://127.0.0.1:8000/transfer/" method="post">
<p>username:<input type="text" name="username"></p>
<p>money:<input type="text" name="money"></p>
<p>others:<input type="text"></p>
<input type="text" name="others" value="jason" style="display:none">
<input type="submit">
</form>
搞个视图函数:
def transfer(request):
return render(request,'transfer.html')
下面是正经网站:
<h1>正经的网站</h1>
<form action="/index3/" method="post">
<p>username:<input type="text" name="username"></p>
<p>money:<input type="text" name="money"></p>
<p>others:<input type="text" name="others"></p>
<input type="submit">
</form>
视图函数:由于没有csrf限制,所以只要接收到就会转账。这里的others就是上面钓鱼网站偷偷塞的非法账户。
def transfer(request):
if request.method == 'POST':
username = request.POST.get("username")
money = request.POST.get('money')
others = request.POST.get('others')
print('%s 给 %s 转了 %s'%(username,others,money))
return render(request,'transfer.html')
说了这么多,预防很简单,只要在form表单里写一句就够了。就是给我自己正规的页面给一个特殊字符串,进行匹配。
<h1>正经的网站</h1>
<form action="/index3/" method="post">
{% csrf_token %}
<p>username:<input type="text" name="username"></p>
<p>money:<input type="text" name="money"></p>
<p>others:<input type="text" name="others"></p>
<input type="submit">
</form>
加完这句{% csrf_token %},我们到前端检查,发现<input type="hidden" name="csrfmiddlewaretoken" value="2vzoo1lmSWgLTdI2rBQ4PTptJQKRQTRwnqeWRGcpdAQGagRp8yKg8RX2PdkF4aqh">
其中value是动态生成的,每一次刷新都不一样。
ajax如何设置csrf_token?
<form>
{% csrf_token %}
</form>
<button>ajax</button>
<script>
$('button').click(function () {
$.ajax({
url:'',
type:'post',
data:{'name':'shj','csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()},
success:function (data){
console.log(data)
}
})
})
</script>
函数中使用:
只需要导入csrf_exempt, csrf_protect
- csrf_exempt:局部禁用
- csrf_protect:局部使用
FBV使用:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt
def index1(request):
return HttpResponse('ok')
@csrf_protect
def index2(request):
return HttpResponse('ok')
CBV使用:
先看csrf_exempt,它有两种,本质上都是给dispatch加。
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt,name='dispatch') # 第一种
class Index3(View):
# @method_decorator(csrf_exempt) # 第二种
def dispatch(self, request, *args, **kwargs):
super().dispatch(request,*args,**kwargs)
def get(self,request):
return HttpResponse('get')
def post(self,request):
return HttpResponse('post')
csrf_protect:它就像之前说的CBV加装饰器一样,三种方式都可以加。既可以局部,也可以全局。
from django.utils.decorators import method_decorator
@method_decorator(csrf_protect,name='get') # 第一种
class Index3(View):
# @method_decorator(csrf_protect) # 第二种
def dispatch(self, request, *args, **kwargs):
super().dispatch(request,*args,**kwargs)
# @method_decorator(csrf_protect) # 第三种
def get(self,request):
return HttpResponse('get')
def post(self,request):
return HttpResponse('post')